7、SpringIOC容器当中Bean的加载方式

Spring底层原理解析

1、Bean 的加载方式(8种)

1.1、XML配置文件

1、XML配置文件的方式

在往先使用Spring对组件进行管理的时候,最初学习的就是通过配置文件的方式,将需要被Spring管理的类,通过配置的方式配置到Spring的IOC容器中,需要使用的时候从IOC容器当中获取--getBean

1.1、关于手动配置Bean的一些属性回顾

image-20220408113131841

1.2、手动配置Bean的一些图解(就不写代码了)

  • 自定义的Bean组件
    • 这里创建了两个Bean,一个Bean设置了id,全局唯一性的标识;第二个则没有设置
    • image-20220408113257550
  • 第三方Bean 的配置
    • image-20220408205208223

1.3、获取Bean对象

image-20220408113543200

  • 第一步还是获取咱们的上下文对象,这个上下文对象的目的是为了加载我们配置文件的内容,得到我们Spring的一个容器对象,通过这个IOC容器对象,我们可以通过get的方式来获取到Bean对象
  • 获取Bean对象的方式上面表示了两种,一种是通过id属性名获取,另一种就是传递一个类对象,通过这两种方式获取到容器当中管理的Bean

1.4、获取容器中所有Bean的名称

image-20220408204927564

image-20220408205009851

image-20220408205145914

1.2、注解

1、注解的配置方式

image-20220408205344039

2、如何加载注解?componentscan

image-20220408205918977

你随便写上这点注解,spring就能加载吗?如果这样就能加载是不是spring启动的时候就会把这些注解,计算机上的类库都加载一遍?那也太繁琐了

为了降低spring的工作强度,他为我们设置了一个新的东西,你需要再配置文件当中配置一个,指定加载组件的位置,componentscan--组件扫描(com.waves),当前包及其子包,写上了这个就会把注解配置为组件,这样就可以被spring容器加载到了

3、第三方组件如何通过Bean配置?(@Configuration)

定义一个配置类

image-20220408211530159

1.3、包扫描注解(@ComponentScan)

作为一个配置文件,上面会有一些基础的配置信息,全新的概念出来了,配置类

不过这个对于Spring的开发来说我个人感觉还是比较鸡肋的,或许我对spring的了解还是少了点,因为配置文件当中有些东西的配置是不可或缺的

image-20220408212429385

1、注解配置的

image-20220408212633880

2、配置类的加载AnnotationCinfigurationApplicationConxtext

image-20220408214446282

3、细节

如果想让某一个类参与到扫描,要么将其注册为组件(@Component,要么将其配置为@Configuration),你如果不加,spring自己都不知道这个玩意儿被扫描到了,那么其中的内容自然也不会扫描成为组件

image-20220409233602326

image-20220409233907845

1.4、FactoryBean(静态工厂)

举个简单例子,我这里有个Bean注解的方法,那么这个方法的返回值会被声明为一个组件加载到容器当中,那么?这个返回的对象一定是这个类型的嘛?不一定

image-20220410005317208

1、创建一个工厂类,这个类用来生产Dog对象

  • 返回的对象是dog
  • 返回的类型是dog.class;也可以是一个接口
public class DogFactory implements FactoryBean<Dog> {

    // 你需要返回的工厂对象
    public Dog getObject() throws Exception {
        return new Dog();
    }

    // 你这个对象是以接口的类型出现还是实现类的形式出现
    public Class<?> getObjectType() {
        // 直接给一个Dog类型就可以
        return Dog.class;
    }

    // 是否是单事例
    public boolean isSingleton() {
        return false;
    }
}

2、在一个配置类中写入一个方法,这个方法用于加载我们刚刚创建的类

  • 这里我们首先加载一下声明的Dog对象

    • // 组件扫描类
      @ComponentScan("com.waves")
      public class MyConfiguration {
      
          @Bean
          public Dog dog1(){
              return new Dog();
          }
      }
      
  • 启动,我们查看下容器当中Bean组件的名字

    • image-20220410005736420

    • 并且这个Bean的类型是什么?是Dog

    • image-20220410005751414

3、通过Bean注解的方式加载我们刚刚配置的那个工厂类看看?

  • // 加载工厂类
    @Bean
    public DogFactory dogFactory(){
        return new DogFactory();
    }
    
  • 启动方法查看Bean的名字和返回的类型

    • image-20220410010012592

说明什么?虽然我在Bean注解当中配置的是DogFactory

image-20220410010122247

但是,我这个工厂类继承了FactoryBean这个接口

image-20220410010149926

这个工厂类造出来的对象不是它本身,而是这个它对应的这个泛型对象(Dog)

对象在哪?

image-20220410010245239

类型是什么?

image-20220410010252543

不是说你的方法配个Bean就一定能造出这个Bean的,这个Bean可能会是其他对象

4、为什么会有这种需求?

因为我们可以在这里,返回对象的时候

image-20220410010349108

做一系列的初始化工作,例如我们之前Spirng阶段学习的datasource对象,他是不是需要配置什么URL,Driver,Username,Password那些的,我们可以在返回这个对象之前将这些属性提前给他设计好,这样是不是我们拿到这个对象的时候,比我们直接new出来的Datasource要好很多,因为本身这个返回的datasource对象就已经携带了各个属性

1.5、@Import注解--引入

内部的参数是一个Class类型的数组,在参数内容中将想要注入到容器当中的Bean的类写进去,那么Spring容器当中就会存在这个我们导入的Bean

image-20220411002451443

1、查看一下该配置类当中的内容

public static void main(String[] args) {
        ApplicationContext app =
                new AnnotationConfigApplicationContext(SpringConfig4.class);
        String[] beanNames = app.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            System.out.println(beanName + " ");
        }
        // 是否这个Dog已经被注入了?
        System.out.println(app.getBean(Dog.class));
    }

2、查看打印结果

image-20220411002823190

可以看到,打印出来的结果,容器当中目前存在两个对象,一个配置类,另一个是我们引入的Dog对象所注册的Bean,但是我们的这个Dog对象的id名称是什么?原先我们通过注解注册的组件名,要么是以类名为id;要么是以方法体(@Bean)为id名;

而这个容器当中存在的对象的id名称是一个标准的全限定名,全路径名称,并且我们引入的这个dog组件确实是一个对象,因为他有自己的内存地址

3、无入侵式编程(侵入式)

spring提倡的是一个思想,也就是上方所述的,你项目当中使用了springboot,那么这个项目就具有spring的功能,这是毋庸置疑的,而去除掉spring功能之后,我对你的项目没有任何影响,该怎么做还是怎么做,这就是无侵入式编程

我Import一个外部的类,只要一进来,我这个容器当中就有这个Bean,叫啥名字不影响,你想要拿到这个Bean,按类型注入,名称注入,都可以获取到;该技术可以有效的降低spring技术的解耦合,这是框架内部进场使用的一个技术,同时也是spring整合其他技术的时候用的比较常用的一种加载Bean的手段

4、加载配置类

image-20220411003902540

查看打印结果

image-20220411004043975

Import可以导入配置类,并且,配置类的全限定名会变为Import格式下的,并且,配置类当中声明的Bean也可以被加载出来(getDog())

5、去除Configuration注解

image-20220411004144102

我现在去掉了配置类的这个注解,那么可以被加载到吗?可以的,那么他内部声明的Bean会被加载到吗?

image-20220411004236535

答案也是可以的,也就是说Import注解引入的配置类,可以去除掉@Configuration注解

6、去除掉的后果

image-20220411004415896

但是去除掉了这就是一个普通的Bean了,不是代理对象方式的Bean了,所以调用方法当中的Dog对象也并不是同一个对象

1.6、registerBean--上下文对象加载完毕之后注册Bean

这个一般是用于上下文对象加载完毕之后,我们如果还想注册Bean的话可以使用这种方式

image-20220411013932043

参数名称为:这个Bean叫什么名字;Bean的类型;可变参数(可传递可不传递)

1、注册一个Cat的组件,id为Tom

public static void main(String[] args) {
    AnnotationConfigApplicationContext app =
            new AnnotationConfigApplicationContext(SpringConfig2.class);
    // 上下文对象加载完毕后,注册Bean的方法
    app.registerBean("tom", Cat.class,1);
    System.out.println(app.getBean("tom"));
    app.registerBean("tom", Cat.class,2);
    System.out.println(app.getBean("tom"));
    app.registerBean("tom", Cat.class,3);
    System.out.println(app.getBean("tom"));
    System.out.println("--------------------------");
    String[] beanNames = app.getBeanDefinitionNames();
    for (String beanName : beanNames) {
        System.out.println(beanName + " ");
    }
}

2、通过这种方式注册的组件,多次注册,每次注册 的都是一个新的Cat

image-20220411015440235

3、为Cat设置有参构造

public class Cat {
    private int age;
    public Cat(){}
    public Cat(int age){
        this.age = age;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "age=" + age +
                '}';
    }
}

image-20220411015549297

最后注册的cat对象被保留了下来

4、registerBean更简单的方式注册

image-20220411015800597

image-20220411015812966

通过这种方式注册,他的id名称是自己类名的首字母小写

1.7、基于ImportSelector接口(选择器)实现的注册Bean

需要类去实现一个接口,且,该类需要和Import标签配合使用,该类可以控制组件的加载,通过各种判定去选择加载Bean

image-20220411143442051

在这里我们看到,ImportSelector,这个接口有两个需要被实现的方法,但是第二个方法有自己的默认实现,所以我们只需要关注第一个方法即可

1、selectImports

该方法的返回值是一个String类型的数组;我们使用的时候只需要将,需要注册到容器的Bean的全限定名,放在其中即可

// 创建我们的选择器工厂
public class MyImportSelector implements ImportSelector {
    // 这里设置一个需要被注册到容器的组件的全限定名的--数组
    private String Beans [] = {"com.waves.animol.Dog"};
	// 实现这个方法,返回一个String类型的数组
    @Override
    public String[] selectImports(AnnotationMetadata metaData) {
        return Beans;
    }
}

2、方法测试

加载我们新建的SpringConfig3这个配置类,在该类中导入我们创建的选择器工厂

image-20220411150212552

public class applicationContext2 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext app =
                new AnnotationConfigApplicationContext(SpringConfig3.class);
        String[] beanNames = app.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            System.out.println(beanName + " ");
        }
        Dog bean = app.getBean(Dog.class);
        Dog bean1 = app.getBean("com.waves.animol.Dog", Dog.class);
        System.out.println(bean==bean1);
    }
}

3、打印结果

image-20220411150228912

结论:引入继承了ImportSelector类,该类并不会被注册为一个组件对象,但是它内部加载的,返回的数组中存储的全限定名的对象,会被注册为一个组件,注册的组件在容器中的id名称就是自身的全限定名,并且,这个组件肯定是被注册了的,通过id名称和class获取到的组件是同一组件,内存地址相同

image-20220411150520530

4、方法体参数--AnnotationMetadata

这个实现方法的参数我们可以理解为是数据源,具体的数据源的相关内容,可以私下去了解下数据库中关于数据源的一些讲解

我们在实现方法中加入一条打印语句,该语句打印这个参数

image-20220411151321578


可以看到,打印出来的参数的内存地址是SpringConfig3这个配置对象,也就是说,该方法的数据源对象就是SpringConfig3metadata所描述的对象就是SpringConfig3

image-20220411150820044

4.1、该数据源是否存在 某某注解(hasAnnotation)

image-20220411151607625

我在SpringConfig3的类上加上一个@Configuration注解

// 这是一个全新的配置类
@Import(MyImportSelector.class)
@Configuration
public class SpringConfig3 {
}

那么这个注解的全限定名就是image-20220411151819921

我们在参数内部写入这个注解的全限定名image-20220411151912829

打印输出查看结果

image-20220411151934775

结论,该方法可以判断这个类上是否包含某个注解,但是不能判断数据源类的内部是否包含某个注解,比如这其中的Bean注解他是无法判断的,通过这个方法image-20220411152147304

4.2、判断注解中设置的属性值(getAnnotationAttributes)

这个方法是获取到这个注解中所包含的属性的值,返回结果为一个Map集合

@Override
public String[] selectImports(AnnotationMetadata metaData) {
    // 判断import注解的属性
    Map<String, Object> map =
            metaData.getAnnotationAttributes("org.springframework.context.annotation.Import");
    System.out.println(map);
    return Beans;
}

image-20220411152914152

既然是个集合,那我是不是可以通过条件控制注册的Bean?

4.3、判断是否包含某个注解,包含,装载A组件,不包含,装载B组件

@Override
public String[] selectImports(AnnotationMetadata metaData) {
    // 判断数据源中是否包含注解Bean
    if(!metaData.hasAnnotation("org.springframework.context.annotation.Bean")){
        return new String[] {"com.waves.animol.Cat"};
    }
    return Beans;
}

image-20220411154101893

不包含,我注册了组件Cat,包含我就注册Dog

5、总结

这个方法可以用来控制需要加载的Bean,动态Bean的加载组件,源码中会大量使用,一定要了解他有什么与众不同的地方

1.8、ImportBeanDefinitionRegistrar(Bean的注册器定义)

1、BeanDefinition

拓展:在Spring的容器当中,我们所有的Bean实际上都是由这个来帮我们创建的;Bean的id默认值的生成器是由BeanNameGenerator生成的

image-20220411155952934

2、覆盖registerBeanDefinitions方法

在源码中看到这俩方法都会被默认实现,我们如果想要使用的话,就要覆盖掉这些方法了,我们使用第一种,第二种多了一个BeanName的生成器

image-20220411160123499

public class MyRigistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {
        
    }
}

3、问题所在

我们方法的参数需要数据源对象,那么第二个参数是什么?


原先使用的方法是通过返回一个String类型的数组,通过这个数组的类的全限定名去给我们到容器当中注册Bean

image-20220411160652491

那么这个方法没有返回值,他是如何帮我们注册Bean的?就是通过--BeanDefinitionRegistry这个对象来创建的,如何创建?

4、BeanDefinitionRegistry

image-20220411161038753

他不是让你给某个需要被注册的组件的类名,而是需要你给他一个真正的BeanDefinition对象,他来帮你注册

4.1、构建BeanDefinition

那肯定需要创建这个对象嘛,然后BeanDefinition去帮我们去容器当中注册Bean

image-20220411161611344

4.2、BeanDefinition实例化

通过Build,然后rootBeanDefinition,给这个注册器权限,其中的参数就是我们需要注册到容器当中的Bean,可以为全限定名,也可以给一个class的字节码文件,给定之后通过getBeanDefinition方法,那返回值肯定是个BeanDefinition嘛

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                    BeanDefinitionRegistry registry) {
    // 构建BeanDefinition
    BeanDefinition beanDefinition = 
            BeanDefinitionBuilder.rootBeanDefinition(Dog.class).getBeanDefinition();
    registry.registerBeanDefinition("Luyi",beanDefinition);
}

4.3、试验

 public static void main(String[] args) {
        AnnotationConfigApplicationContext app =
                new AnnotationConfigApplicationContext(SpringConfig4.class);
        String[] beanNames = app.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            System.out.println(beanName + " ");
        }
    }

查看打印结果

image-20220411162210481

5、总结

对比继承自ImportSelector,该方法开放了Bean的定义过程,用它来注册我们的实名Bean,也就是Bean的命名权限也由我们自己定义

1.9、BeanDefinitionRegistryPostProcessor(Bean定义注册器的后处理器)

我这创建一个接口,内部多定义几个实现类,等会会用到

image-20220411165244243

内部写了一个save方法

image-20220411165320267

1、将实现类注册为组件,从IOC容器当中获取,调用save方法

新建一个配置类,这个配置类将实现类注册为组件--import的方式导入

image-20220411165416674

从IOC容器当中获取,调用save方法

public static void main(String[] args) {
        AnnotationConfigApplicationContext app =
                new AnnotationConfigApplicationContext(SpringConfig5.class);
        // 我们引入了IUserServiceImpl1的实现类
        String[] beanNames = app.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            System.out.println(beanName);
        }
        IUserService userService = app.getBean("userService", IUserService.class);
        userService.save();

    }

查看打印结果

image-20220411165636891

2、需求分析

我现在通过register的方式image-20220411195307819

往容器当中加入一个实现类2,将其注册为组件

public class MyRigistrar2 implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {
        // 构建BeanDefinition
        BeanDefinition beanDefinition =
                BeanDefinitionBuilder.rootBeanDefinition(IUserServiceImpl2.class).getBeanDefinition();
        registry.registerBeanDefinition("userService",beanDefinition);
    }
}

那我从容器当中拿到的这个接口的对象,他的实现类是1还是2?

IUserService userService = app.getBean("userService", IUserService.class);

运行结果

image-20220411195419437

结论:没有报错,说明并没有冲突,并且我们register注册的方式,优先级会比import的这种方式要高,是因为位置的关系嘛?

3、调换位置

这个时候我的Register类是在前面了image-20220411195607907

查看结果

image-20220411195626126

4、加入ComponentScan组件扫描注解,将Service包下的全部注册为组件

image-20220411195807115

运行结果

image-20220411195824614

这种方式比@Component@Service@Controller注解的优先级别还要高

5、问题的衍生

这就相当于,springboot中会有许多默认的内嵌技术;例如tomcat,如果项目的人比较多,会出现这样一种情况,我刚刚在案例中使用的是registrar的技术进行的创建对象,如果这时候有人和我用相同的技术对这个接口(IUserService)的实现类,进行了组件的注册

image-20220411200329081并且好巧不巧,都加入了这个Import中image-20220411200402906

请问,先加入的生效还是后加入的生效?

image-20220411200426302

后加入的生效

6、问题的解决--最后一种加载Bean的方式(BeanDefinitionRegistryPostProcessor)

有没有一个东西可以起到决定性的作用?

同样的,需要一个类去实现这个接口

public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // 构建BeanDefinition
        BeanDefinition beanDefinition =
                BeanDefinitionBuilder.rootBeanDefinition(IUserServiceImpl4.class).getBeanDefinition();
        registry.registerBeanDefinition("userService",beanDefinition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }
}

现在我将这个类,加入到Import当中引用

image-20220411200942651

结果

image-20220411201036288

7、换个位置看看?

image-20220411201106315

8、结论

其实这是一种后处理机制,Post,在什么之后,Processor处理器,处理的意思,叫做后处理Bean的注册,当我们的BeanDefinition注册组件完毕之后,他会对之前注册的内容进行一次全覆盖,他的优先级是最高的,高于Registrar的注册方式,如果有俩呢?那肯定涉及一个先后顺序

有什么好处?保障性工作,加入你前面的那些东西,不管处理没处理,我到最后一步,不管你前面写的什么,我说了算,我想加载的东西,才是最后要的东西,你要用前面各种各样的机制写,都可能被替换掉,但唯独用它写,是不会被替代的

image-20220411201537041

1.10、总结

image-20220411201752576

2、代理对象proxyBeanMethod

2.1、@Configuration注解的含义

之前也学习过,@Configuration注解的含义是将标注的这个类,标记为配置类,配置类也是组件的一种,可以被注入到spring的容器当中

所谓的配置类其实就是内部声明了其他的Bean

image-20220410232056285

并且可以在容器当中查看到

image-20220410232106542

而@Configuration注解标注的类,一般是用于做配置的,他其中的方法的返回值,可以通过@Bean注解在IOC容器当中注入

@Configuration
public class SpringConfig1 {

    @Bean
    public Dog getDog(){
        return new Dog();
    }
}

容器当中,该方法的方法名作为组件的名称

image-20220410232218551

2.2、对比

1、我将DogFactory工厂创建的Dog对象;容器中本身存在的Dog类注册的对象;配置类中通过@Bean注解生成的Dog对象,三者的内存地址做一个判断

image-20220410232925961

2、三者的内存地址做一个比较

System.out.println(app.getBean("dog")+" "+app.getBean("getDog")+" "+app.getBean("dogFactory"));

image-20220410233024541

都是Dog对象,返回的内存地址完全不同

2.3、proxyBeanMethod属性

这个属性是@Configuration注解的一个属性,返回条件是布尔类型的

image-20220410233141207

默认值为true,表示使用代理对象的方式创建我们的这个配置类对象(SpringConfig1)

1、这个几把代理对象有什么用?

我提个观点,他是个对象,那我是不是可以调用其中的方法,也就是我们的那个getDog?

image-20220410233255493

2、查看这个对象的地址

image-20220411000218702

EnhancerBySpringCGLIB----CGLIB的方式创建的代理对象,我们平时从spring容器当中拿到的这个对象,他的地址一般是xxx.xxx.xxx.对象类名@xxx的形式,如果proxyBeanMethod为true的话就是使用代理对象的方式去创建这个对象,我们从IOC容器当中拿到的这个对象就是一个代理对象

3、从容器当中获取到这个组件,然后调用getDog()方法

public static void main(String[] args) {
    // 启动引导类,加载组件扫描类,得到我们的springIOC容器对象
    ApplicationContext app =
            new AnnotationConfigApplicationContext(MyConfiguration.class);
    // 拿到咱们的配置类对象
    SpringConfig1 bean = app.getBean(SpringConfig1.class);
    // 我要多次调用getDog()这个方法
    Dog dog1 = bean.getDog();
    Dog dog2 = bean.getDog();
    Dog dog3 = bean.getDog();
    System.out.println(dog1);
    System.out.println(dog2);
    System.out.println(dog3);

}

image-20220410233709471

结论

当@Configuration组件使用代理对象的方式(proxyBeanMethod=true)的方式,获取到内部加载的容器对象,那么这些对象实际上调用的就是在Spring容器当中创建的Dog对象(你配置类里面的哈,如果外面还有那肯定内存地址不一样)

2.4、proxyBeanMethod==false

不使用代理对象的方式创建这个对象(SpringConfig)

image-20220411000409454

首先是内存地址变了,没有使用代理对象,我们拿到的就是一个普通的对象

image-20220411000444484

其次,代理对象中的getDog获取到的Dog对象,并不是之前的单事例了,变成了多事例

image-20220411000520894

每次拿到的Dog对象都是一个新的对象

2.5、这个属性的作用

如果我们设置为默认值,那么当我们运行对应的这个方法

image-20220411000733548

这个方法在spring的容器当中加载过Bean,那么当我们重复调用这个方法的时候,就是从容器当中去获取到这个创建过的Bean,不再去重新创建


如果我们设置为false,那么当我们通过这个对象当中的这个方法(getDog())的时候,每执行一次,就会将这个return new Dog();这条语句跑一次,也就会得到一个全新的对象

image-20220411001002189


这样的好处是,确保我们每次从容器当中获取到的这个对象是单事例的,其实跟最初spring的时候设置的那个单事例or多事例的属性是一样的,但是我们配置类中往往会创建第三方的Bean,所以会这样去使用

3、Bean的加载控制

对Bean的加载过程,产生一个控制的作用,根据项目的功能不同,业务不同,我们会根据情况去加载对应的Bean,并非是那种直接加载固定的Bean(三层架构),目的是对程序实现对应的需求

3.1、编程的形式控制Bean

image-20220411203157123

1、创建三个实体类(空架子)

image-20220411221805025

2、准备我们的配置类,扫描类嘛

这里对MyRegister类进行一个引入

image-20220411221827479

3、工厂对象的创建

我们可以设计一些判定条件,如果我这个项目当中加载了老鼠这个类,那么我就创建一个Cat到容器当中

类的判断有很多种形式,我们这里采用是否存在Mouse这个类*

public class MyRegistrar implements ImportBeanDefinitionRegistrar {
    private BeanDefinition beanDefinition;
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
        try {
            // 是否存在Mouse这个类
            Class<?> aClass = Class.forName("com.waves.bean.Mouse");
            System.out.println("说明耗子是存在的,要放猫了...............");
            beanDefinition =
                    BeanDefinitionBuilder.rootBeanDefinition(Cat.class).getBeanDefinition();
            registry.registerBeanDefinition("tom",beanDefinition);
        } catch (ClassNotFoundException e) {
        }
    }
}

4、准备启动类

public static void main(String[] args) {
    // 加载我们上下文的对象--注解的方式
    AnnotationConfigApplicationContext app =
            new AnnotationConfigApplicationContext(SpringConfig.class);
    String[] beanDefinitionNames = app.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        System.out.println(beanDefinitionName);
    }
}

结果

image-20220411222058292

5、老鼠不存在,存在狼

我这里有个需求,如果这里面有狼,那我就不能放猫出来了,我要让他躲起来

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
    try {
        // 如果存在狼
        Class<?> aClass = Class.forName("com.waves.bean.Wrolf");
        System.out.println("说明耗子是存在的,要放猫了...............");
        beanDefinition =
                BeanDefinitionBuilder.rootBeanDefinition(Cat.class).getBeanDefinition();
        registry.registerBeanDefinition("tom",beanDefinition);
    } catch (ClassNotFoundException e) {
        // e.printStackTrace();
        System.out.println("狼显然是不存在的,我要放狗了............");
        beanDefinition =
                BeanDefinitionBuilder.rootBeanDefinition(Dog.class).getBeanDefinition();
        registry.registerBeanDefinition("Jerry",beanDefinition);
    }

}

运行结果

image-20220411222244040

6、总结

我们可以通过编程式的方式来对我们的Bean进行一个选择性的加载

3.2、@Conditional

用它派生的注解设置各种组合条件控制Bean的加载

1、@Conditional的派生注解

为什么要使用他的派生注解?因为这个注解是Spring的注解,使用它的话需要填写的参数,需要手动配置,但我们现在的目的就是不想像编程式那样手动配置

image-20220411224334049

image-20220411224348971

springboot的出现解决了这个问题,使用其派生的衍生注解,我们可以达到去除手动配置的效果

2、@ConditionalOnClass

如果环境中存在某个类,那么就加载这个Bean对象

public class SpringConfig1 {
    @Bean
    @ConditionalOnClass(Mouse.class)
    public Cat tom(){
        return new Cat();
    }
}

运行

image-20220411224905558

3、@ConditionalOnMissingClass

如果环境中不存在这个类,就加载这个Bean,参数不能使用字节码的方式,需要String[]数组的方式

image-20220411225017193

public class SpringConfig1 {

    @Bean
    @ConditionalOnMissingClass("com.waves.wolf")
    public Cat tom(){
        return new Cat();
    }
}

结果

image-20220411225054569

4、@ConditionalOnBean

如果环境中存在这个Bean,那我就加载

image-20220411225723472

@Import(Mouse.class)
public class SpringConfig1 {

    @Bean
    @ConditionalOnBean(Mouse.class)
    public Cat tom(){
        return new Cat();
    }
}

运行结果

image-20220411225813605

5、@ConditionalOnWebApplication和@ConditionalOnNotWebApplication

顾名思义,web环境和非web环境

非web环境

image-20220411230002841

web环境image-20220411230020927

6、可以加在在类上

我们平时开发可能不会像之前那么写,但是我们可以将注解放在我们需要被加载的Bean上面

@Component("tomcat")
@ConditionalOnBean(Mouse.class)
@ConditionalOnNotWebApplication
//@ConditionalOnWebApplication
public class Cat {
}

4、Bean的依赖属性的配置

4.1、环境的搭建

  • 实体类三个,猫,老鼠,狗子

    • image-20220411235818518
  • 动画类,声明成一个组件,我需要在内部实现一个猫和老鼠的动画片

  • 主角--猫和老鼠

    • image-20220411235907730
  • play--开播

    • image-20220411235927003
  • 主启动类

    • 获取上下文对象

      • image-20220411235955143
    • 通过上下文对象获取动画--组件,上演猫和老鼠大战

      • image-20220412000040972

4.2、测试

image-20220412000059376

4.3、主角是猫和老鼠,我直接这样写显然不妥当

image-20220412000127067

1、配置实体的属性

image-20220412000410433

2、修改语句输出

// 开片
public void play(){
    //System.out.println("3岁的假老练和4岁的风车车打起来了");
    System.out.println(cat.getAge()+"岁的"+cat.getName()+
            "和"+ms.getAge()+"岁的"+ms.getName()+"打起来了");
}

3、开干

image-20220412000529689

没有完成cat和mouse的值的初始化,自然是空指针异常

4.4、设置无参构造,内部初始化cat和ms的值

public CatAndMouseCartoon(){
    cat = new Cat();
    ms = new Mouse();
    cat.setName("假老练");
    cat.setAge(3);
    ms.setName("风车车");
    ms.setAge(4);
}

执行方法,查看结果

image-20220412000746636

4.5、关于属性设置的问题

我们运行一个程序,需要使用Bean,Bean实际上是有需求去获取一些对应的配置值的,以前面的配置为例

image-20220412000914144

这里使用到了Datasource,你不告诉他连接信息,username,psw等等,他能用吗,不能,所以一定会给他设置值,并且,不能像构造方法那样写,如果需要改需求,代码就耦合死了,我们可以使用配置文件的方式去解耦合

4.6、配置文件

cartoon:
  cat:
    name: "假老练"
    age: 3
  ms:
    name: "风车车"
    age: 4

这里需要注意,参数名需要和创建的属性名一致

4.7、测试

image-20220412001545673

为什么还是空指针?因为这俩对象目前并没有get和set方法

image-20220412001657691

这是我们创建的cat类

这是我们动画片组建中的对象变量cat和ms

image-20220412001730226

目前他俩是没有get和set方法的

image-20220412001843170

设置了data注解以后就有了

image-20220412002139970

原理:卡通类中的两个对象属性,只是在其中创建了,并没有给他们赋值,无论是构造方法的时候初始化他们,亦或者是通过配置文件的方式给他俩赋值,对象在这个过程中,是被初始化了的,被初始化了,自然可以通过get的方式来获取对象的值,倘若没赋予,那自然是空指针异常

4.8、问题的提出

目前这个项目的环境构造已经是搭建的差不多了,这时候出现了一个问题,与耦合性有关系

image-20220412082358719

在我的卡通类中,我的这个类,是不是与这个配置文件的注解绑定死了?如果没有这个配置文件的注解,我这个类连用都没法用,这样设计存在一些不合理的现象,如何去修改?

1、自定义属性的配置类

抽取思想,将这个注解抽取出来,怎么抽取?我专门设置一个类,这个类用于存储猫和老鼠中的猫、与老鼠的属性

@Component
@ConfigurationProperties("cartoon")
@Data
// 猫与老鼠属性配置类
public class CartoonUtil {
    private Cat cat;
    private Mouse ms;
}

@ConfigurationProperties的注解的前提是该类必须是一个组件才可以

2、卡通类中注入工具类

他既然是个组件,那我们可以通过注入的方式来对其进行赋值,暂时先不用,因为后面会衍生一个东西,不通过注入的方式生产他效果会更好

  • 在卡通类中声明这个属性(util)

    • image-20220412084125446
  • 开启我们的构造方法,将其设置为有参构造

    • 我需要这个属性,而这个属性的创建权利我交给了spring容器,那么他自然可以帮我在这个构造方法当中注入这个对象

    • public CatAndMouseCartoon(CartoonUtil cartoon){
          this.cartoon = cartoon;
          cat = new Cat();
          ms = new Mouse();
          cat.setName("假老练");
          cat.setAge(3);
          ms.setName("风车车");
          ms.setAge(4);
      }
      

3、升华,三元表达式

那么我现在可以对这些属性进行一个动态的变化了

  • 那我现在对猫猫的的name属性进行一个动态的变化

    • 二者的对比

    • cat.setName("假老练");
      
    • cat.setName(cartoon.getCat()!=null && StringUtils.hasText(cartoon.getCat().getName()) ? cartoon.getCat().getName() :"假老练");
      
    • 首先是通过工具类获取到cat的对象不能为空,为什么?

      • 如果我将cat这里注释掉了,那么下方就会报空指针异常

      • cartoon:
        #  cat:
        #    name: "waves"
        #    age: 5
          ms:
            name: "bright"
            age: 2
        
    • 其次,通过String的工具类判断,动画工具类中的name属性是否为空,也是一样的道理

    • 如果为空,那么就将name的值,赋予为假老练

那么构造器最终就生成这样了

public CatAndMouseCartoon(CartoonUtil cartoon){
    this.cartoon = cartoon;
    cat = new Cat();
    ms = new Mouse();
    cat.setName(cartoon.getCat()!=null && StringUtils.hasText(cartoon.getCat().getName()) ? cartoon.getCat().getName() :"假老练");
    cat.setAge(cartoon.getCat()!=null && cartoon.getCat().getAge()!=null ? cartoon.getCat().getAge() :3);
    ms.setName(cartoon.getMs()!=null && StringUtils.hasText(cartoon.getMs().getName()) ? cartoon.getMs().getName() :"风车车");
    ms.setAge(cartoon.getMs()!=null && cartoon.getMs().getAge()!=null ? cartoon.getMs().getAge() :4);
}

通过这种方式,我并没有去强制绑定我的配置文件,并且配置文件中,无论是提供单个属性,多个属性,还是不提供属性,对环境都没有任何影响

4.9、问题的提出2

刚刚创建的工具类,他是一个组件,并且最终会被加载为Bean,那么如果我不用这个业务类的话(卡通),哪还有必要加载他吗?不用的话,那么是不是配置文件的注解我也不需要加了?我应该如何灵活的使用?

4.10、关联注解--@EnableConfigurationProperties

  • 取消工具类中的@Component注解

    • 我取消了这个注解,那么自然下方的读取配置文件的注解就不能生效了,因为该组件生效的前提是这个类必须为springIOC容器中所管理的Bean
    • image-20220412100012612
  • 卡通类中配置注解--EnableConfigurationProperties

    • 该注解的含义是他能够强制设定哪一个类,加载成Bean
    • image-20220412100140771

当我加载卡通类的时候,读取工具类,并将其加载为Bean

image-20220412100422312

4.11、小升华

我的卡通类是被注册为一个组件了的,无论我使用与否,他都会被加载到Spring的容器当中,这里的设定也有一些些许的不合理,我现在将其的组件去掉

image-20220412100602034

在启动类中加载这个类

image-20220412100643614

这样我想使用,那就可以通过import去导入,将其注册为Bean,不使用,我去掉这个注解即可

posted @   澜璨  阅读(366)  评论(0编辑  收藏  举报
编辑推荐:
· 后端思维之高并发处理方案
· 理解Rust引用及其生命周期标识(下)
· 从二进制到误差:逐行拆解C语言浮点运算中的4008175468544之谜
· .NET制作智能桌面机器人:结合BotSharp智能体框架开发语音交互
· 软件产品开发中常见的10个问题及处理方法
阅读排行:
· 后端思维之高并发处理方案
· 千万级大表的优化技巧
· 在 VS Code 中,一键安装 MCP Server!
· 10年+ .NET Coder 心语 ── 继承的思维:从思维模式到架构设计的深度解析
· 想让你多爱自己一些的开源计时器

喜欢请打赏

扫描二维码打赏

了解更多

点击右上角即可分享
微信分享提示