7、SpringIOC容器当中Bean的加载方式
Spring底层原理解析
1、Bean 的加载方式(8种)
1.1、XML配置文件
1、XML配置文件的方式
在往先使用Spring对组件进行管理的时候,最初学习的就是通过配置文件的方式,将需要被Spring管理的类,通过配置的方式配置到Spring的IOC容器中,需要使用的时候从IOC容器当中获取--getBean
1.1、关于手动配置Bean的一些属性回顾
1.2、手动配置Bean的一些图解(就不写代码了)
- 自定义的Bean组件
- 这里创建了两个Bean,一个Bean设置了id,全局唯一性的标识;第二个则没有设置
- 第三方Bean 的配置
1.3、获取Bean对象
- 第一步还是获取咱们的上下文对象,这个上下文对象的目的是为了加载我们配置文件的内容,得到我们Spring的一个容器对象,通过这个IOC容器对象,我们可以通过get的方式来获取到Bean对象
- 获取Bean对象的方式上面表示了两种,一种是通过id属性名获取,另一种就是传递一个类对象,通过这两种方式获取到容器当中管理的Bean
1.4、获取容器中所有Bean的名称
1.2、注解
1、注解的配置方式
2、如何加载注解?componentscan
你随便写上这点注解,spring就能加载吗?如果这样就能加载是不是spring启动的时候就会把这些注解,计算机上的类库都加载一遍?那也太繁琐了
为了降低spring的工作强度,他为我们设置了一个新的东西,你需要再配置文件当中配置一个,指定加载组件的位置,componentscan--组件扫描(com.waves),当前包及其子包,写上了这个就会把注解配置为组件,这样就可以被spring容器加载到了
3、第三方组件如何通过Bean配置?(@Configuration)
定义一个配置类
1.3、包扫描注解(@ComponentScan)
作为一个配置文件,上面会有一些基础的配置信息,全新的概念出来了,配置类
不过这个对于Spring的开发来说我个人感觉还是比较鸡肋的,或许我对spring的了解还是少了点,因为配置文件当中有些东西的配置是不可或缺的
1、注解配置的
2、配置类的加载AnnotationCinfigurationApplicationConxtext
3、细节
如果想让某一个类参与到扫描,要么将其注册为组件(@Component,要么将其配置为@Configuration),你如果不加,spring自己都不知道这个玩意儿被扫描到了,那么其中的内容自然也不会扫描成为组件
1.4、FactoryBean(静态工厂)
举个简单例子,我这里有个Bean注解的方法,那么这个方法的返回值会被声明为一个组件加载到容器当中,那么?这个返回的对象一定是这个类型的嘛?不一定
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组件的名字
-
-
并且这个Bean的类型是什么?是Dog
-
-
3、通过Bean注解的方式加载我们刚刚配置的那个工厂类看看?
-
// 加载工厂类 @Bean public DogFactory dogFactory(){ return new DogFactory(); }
-
启动方法查看Bean的名字和返回的类型
说明什么?虽然我在Bean注解当中配置的是DogFactory
但是,我这个工厂类继承了FactoryBean这个接口
这个工厂类造出来的对象不是它本身,而是这个它对应的这个泛型对象(Dog)
对象在哪?
类型是什么?
不是说你的方法配个Bean就一定能造出这个Bean的,这个Bean可能会是其他对象
4、为什么会有这种需求?
因为我们可以在这里,返回对象的时候
做一系列的初始化工作,例如我们之前Spirng阶段学习的datasource对象,他是不是需要配置什么URL,Driver,Username,Password那些的,我们可以在返回这个对象之前将这些属性提前给他设计好,这样是不是我们拿到这个对象的时候,比我们直接new出来的Datasource要好很多,因为本身这个返回的datasource对象就已经携带了各个属性
1.5、@Import注解--引入
内部的参数是一个Class类型的数组,在参数内容中将想要注入到容器当中的Bean的类写进去,那么Spring容器当中就会存在这个我们导入的Bean
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、查看打印结果
可以看到,打印出来的结果,容器当中目前存在两个对象,一个配置类,另一个是我们引入的Dog对象所注册的Bean,但是我们的这个Dog对象的id名称是什么?原先我们通过注解注册的组件名,要么是以类名为id;要么是以方法体(@Bean)为id名;
而这个容器当中存在的对象的id名称是一个标准的全限定名,全路径名称,并且我们引入的这个dog组件确实是一个对象,因为他有自己的内存地址
3、无入侵式编程(侵入式)
spring提倡的是一个思想,也就是上方所述的,你项目当中使用了springboot,那么这个项目就具有spring的功能,这是毋庸置疑的,而去除掉spring功能之后,我对你的项目没有任何影响,该怎么做还是怎么做,这就是无侵入式编程
我Import一个外部的类,只要一进来,我这个容器当中就有这个Bean,叫啥名字不影响,你想要拿到这个Bean,按类型注入,名称注入,都可以获取到;该技术可以有效的降低spring技术的解耦合,这是框架内部进场使用的一个技术,同时也是spring整合其他技术的时候用的比较常用的一种加载Bean的手段
4、加载配置类
查看打印结果
Import可以导入配置类,并且,配置类的全限定名会变为Import格式下的,并且,配置类当中声明的Bean也可以被加载出来(getDog())
5、去除Configuration注解
我现在去掉了配置类的这个注解,那么可以被加载到吗?可以的,那么他内部声明的Bean会被加载到吗?
答案也是可以的,也就是说Import注解引入的配置类,可以去除掉@Configuration注解
6、去除掉的后果
但是去除掉了这就是一个普通的Bean了,不是代理对象方式的Bean了,所以调用方法当中的Dog对象也并不是同一个对象
1.6、registerBean--上下文对象加载完毕之后注册Bean
这个一般是用于上下文对象加载完毕之后,我们如果还想注册Bean的话可以使用这种方式
参数名称为:这个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
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 +
'}';
}
}
最后注册的cat对象被保留了下来
4、registerBean更简单的方式注册
通过这种方式注册,他的id名称是自己类名的首字母小写
1.7、基于ImportSelector接口(选择器)实现的注册Bean
需要类去实现一个接口,且,该类需要和Import标签配合使用,该类可以控制组件的加载,通过各种判定去选择加载Bean
在这里我们看到,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这个配置类,在该类中导入我们创建的选择器工厂
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、打印结果
结论:引入继承了ImportSelector类,该类并不会被注册为一个组件对象,但是它内部加载的,返回的数组中存储的全限定名的对象,会被注册为一个组件,注册的组件在容器中的id名称就是自身的全限定名,并且,这个组件肯定是被注册了的,通过id名称和class获取到的组件是同一组件,内存地址相同
4、方法体参数--AnnotationMetadata
这个实现方法的参数我们可以理解为是数据源,具体的数据源的相关内容,可以私下去了解下数据库中关于数据源的一些讲解
我们在实现方法中加入一条打印语句,该语句打印这个参数
可以看到,打印出来的参数的内存地址是SpringConfig3这个配置对象,也就是说,该方法的数据源对象就是SpringConfig3metadata所描述的对象就是SpringConfig3
4.1、该数据源是否存在 某某注解(hasAnnotation)
我在SpringConfig3的类上加上一个@Configuration注解
// 这是一个全新的配置类
@Import(MyImportSelector.class)
@Configuration
public class SpringConfig3 {
}
那么这个注解的全限定名就是
我们在参数内部写入这个注解的全限定名
打印输出查看结果
结论,该方法可以判断这个类上是否包含某个注解,但是不能判断数据源类的内部是否包含某个注解,比如这其中的Bean注解他是无法判断的,通过这个方法
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;
}
既然是个集合,那我是不是可以通过条件控制注册的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;
}
不包含,我注册了组件Cat,包含我就注册Dog
5、总结
这个方法可以用来控制需要加载的Bean,动态Bean的加载组件,源码中会大量使用,一定要了解他有什么与众不同的地方
1.8、ImportBeanDefinitionRegistrar(Bean的注册器定义)
1、BeanDefinition
拓展:在Spring的容器当中,我们所有的Bean实际上都是由这个来帮我们创建的;Bean的id默认值的生成器是由BeanNameGenerator生成的
2、覆盖registerBeanDefinitions方法
在源码中看到这俩方法都会被默认实现,我们如果想要使用的话,就要覆盖掉这些方法了,我们使用第一种,第二种多了一个BeanName的生成器
public class MyRigistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
}
}
3、问题所在
我们方法的参数需要数据源对象,那么第二个参数是什么?
原先使用的方法是通过返回一个String类型的数组,通过这个数组的类的全限定名去给我们到容器当中注册Bean
那么这个方法没有返回值,他是如何帮我们注册Bean的?就是通过--BeanDefinitionRegistry这个对象来创建的,如何创建?
4、BeanDefinitionRegistry
他不是让你给某个需要被注册的组件的类名,而是需要你给他一个真正的BeanDefinition对象,他来帮你注册
4.1、构建BeanDefinition
那肯定需要创建这个对象嘛,然后BeanDefinition去帮我们去容器当中注册Bean
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 + " ");
}
}
查看打印结果
5、总结
对比继承自ImportSelector,该方法开放了Bean的定义过程,用它来注册我们的实名Bean,也就是Bean的命名权限也由我们自己定义
1.9、BeanDefinitionRegistryPostProcessor(Bean定义注册器的后处理器)
我这创建一个接口,内部多定义几个实现类,等会会用到
内部写了一个save方法
1、将实现类注册为组件,从IOC容器当中获取,调用save方法
新建一个配置类,这个配置类将实现类注册为组件--import的方式导入
从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();
}
查看打印结果
2、需求分析
我现在通过register的方式
往容器当中加入一个实现类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);
运行结果
结论:没有报错,说明并没有冲突,并且我们register注册的方式,优先级会比import的这种方式要高,是因为位置的关系嘛?
3、调换位置
这个时候我的Register类是在前面了
查看结果
4、加入ComponentScan组件扫描注解,将Service包下的全部注册为组件
运行结果
这种方式比@Component@Service@Controller注解的优先级别还要高
5、问题的衍生
这就相当于,springboot中会有许多默认的内嵌技术;例如tomcat,如果项目的人比较多,会出现这样一种情况,我刚刚在案例中使用的是registrar的技术进行的创建对象,如果这时候有人和我用相同的技术对这个接口(IUserService)的实现类,进行了组件的注册
并且好巧不巧,都加入了这个Import中
请问,先加入的生效还是后加入的生效?
后加入的生效
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当中引用
结果
7、换个位置看看?
8、结论
其实这是一种后处理机制,Post,在什么之后,Processor处理器,处理的意思,叫做后处理Bean的注册,当我们的BeanDefinition注册组件完毕之后,他会对之前注册的内容进行一次全覆盖,他的优先级是最高的,高于Registrar的注册方式,如果有俩呢?那肯定涉及一个先后顺序
有什么好处?保障性工作,加入你前面的那些东西,不管处理没处理,我到最后一步,不管你前面写的什么,我说了算,我想加载的东西,才是最后要的东西,你要用前面各种各样的机制写,都可能被替换掉,但唯独用它写,是不会被替代的
1.10、总结
2、代理对象proxyBeanMethod
2.1、@Configuration注解的含义
之前也学习过,@Configuration注解的含义是将标注的这个类,标记为配置类,配置类也是组件的一种,可以被注入到spring的容器当中
所谓的配置类其实就是内部声明了其他的Bean
并且可以在容器当中查看到
而@Configuration注解标注的类,一般是用于做配置的,他其中的方法的返回值,可以通过@Bean注解在IOC容器当中注入
@Configuration
public class SpringConfig1 {
@Bean
public Dog getDog(){
return new Dog();
}
}
容器当中,该方法的方法名作为组件的名称
2.2、对比
1、我将DogFactory工厂创建的Dog对象;容器中本身存在的Dog类注册的对象;配置类中通过@Bean注解生成的Dog对象,三者的内存地址做一个判断
2、三者的内存地址做一个比较
System.out.println(app.getBean("dog")+" "+app.getBean("getDog")+" "+app.getBean("dogFactory"));
都是Dog对象,返回的内存地址完全不同
2.3、proxyBeanMethod属性
这个属性是@Configuration注解的一个属性,返回条件是布尔类型的
默认值为true,表示使用代理对象的方式创建我们的这个配置类对象(SpringConfig1)
1、这个几把代理对象有什么用?
我提个观点,他是个对象,那我是不是可以调用其中的方法,也就是我们的那个getDog?
2、查看这个对象的地址
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);
}
结论
当@Configuration组件使用代理对象的方式(proxyBeanMethod=true)的方式,获取到内部加载的容器对象,那么这些对象实际上调用的就是在Spring容器当中创建的Dog对象(你配置类里面的哈,如果外面还有那肯定内存地址不一样)
2.4、proxyBeanMethod==false
不使用代理对象的方式创建这个对象(SpringConfig)
首先是内存地址变了,没有使用代理对象,我们拿到的就是一个普通的对象
其次,代理对象中的getDog获取到的Dog对象,并不是之前的单事例了,变成了多事例
每次拿到的Dog对象都是一个新的对象
2.5、这个属性的作用
如果我们设置为默认值,那么当我们运行对应的这个方法
这个方法在spring的容器当中加载过Bean,那么当我们重复调用这个方法的时候,就是从容器当中去获取到这个创建过的Bean,不再去重新创建
如果我们设置为false,那么当我们通过这个对象当中的这个方法(getDog())的时候,每执行一次,就会将这个return new Dog();这条语句跑一次,也就会得到一个全新的对象
这样的好处是,确保我们每次从容器当中获取到的这个对象是单事例的,其实跟最初spring的时候设置的那个单事例or多事例的属性是一样的,但是我们配置类中往往会创建第三方的Bean,所以会这样去使用
3、Bean的加载控制
对Bean的加载过程,产生一个控制的作用,根据项目的功能不同,业务不同,我们会根据情况去加载对应的Bean,并非是那种直接加载固定的Bean(三层架构),目的是对程序实现对应的需求
3.1、编程的形式控制Bean
1、创建三个实体类(空架子)
2、准备我们的配置类,扫描类嘛
这里对MyRegister类进行一个引入
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);
}
}
结果
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);
}
}
运行结果
6、总结
我们可以通过编程式的方式来对我们的Bean进行一个选择性的加载
3.2、@Conditional
用它派生的注解设置各种组合条件控制Bean的加载
1、@Conditional的派生注解
为什么要使用他的派生注解?因为这个注解是Spring的注解,使用它的话需要填写的参数,需要手动配置,但我们现在的目的就是不想像编程式那样手动配置
springboot的出现解决了这个问题,使用其派生的衍生注解,我们可以达到去除手动配置的效果
2、@ConditionalOnClass
如果环境中存在某个类,那么就加载这个Bean对象
public class SpringConfig1 {
@Bean
@ConditionalOnClass(Mouse.class)
public Cat tom(){
return new Cat();
}
}
运行
3、@ConditionalOnMissingClass
如果环境中不存在这个类,就加载这个Bean,参数不能使用字节码的方式,需要String[]数组的方式
public class SpringConfig1 {
@Bean
@ConditionalOnMissingClass("com.waves.wolf")
public Cat tom(){
return new Cat();
}
}
结果
4、@ConditionalOnBean
如果环境中存在这个Bean,那我就加载
@Import(Mouse.class)
public class SpringConfig1 {
@Bean
@ConditionalOnBean(Mouse.class)
public Cat tom(){
return new Cat();
}
}
运行结果
5、@ConditionalOnWebApplication和@ConditionalOnNotWebApplication
顾名思义,web环境和非web环境
非web环境
web环境
6、可以加在在类上
我们平时开发可能不会像之前那么写,但是我们可以将注解放在我们需要被加载的Bean上面
@Component("tomcat")
@ConditionalOnBean(Mouse.class)
@ConditionalOnNotWebApplication
//@ConditionalOnWebApplication
public class Cat {
}
4、Bean的依赖属性的配置
4.1、环境的搭建
-
实体类三个,猫,老鼠,狗子
-
动画类,声明成一个组件,我需要在内部实现一个猫和老鼠的动画片
-
主角--猫和老鼠
-
play--开播
-
主启动类
-
获取上下文对象
-
通过上下文对象获取动画--组件,上演猫和老鼠大战
-
4.2、测试
4.3、主角是猫和老鼠,我直接这样写显然不妥当
1、配置实体的属性
2、修改语句输出
// 开片
public void play(){
//System.out.println("3岁的假老练和4岁的风车车打起来了");
System.out.println(cat.getAge()+"岁的"+cat.getName()+
"和"+ms.getAge()+"岁的"+ms.getName()+"打起来了");
}
3、开干
没有完成cat和mouse的值的初始化,自然是空指针异常
4.4、设置无参构造,内部初始化cat和ms的值
public CatAndMouseCartoon(){
cat = new Cat();
ms = new Mouse();
cat.setName("假老练");
cat.setAge(3);
ms.setName("风车车");
ms.setAge(4);
}
执行方法,查看结果
4.5、关于属性设置的问题
我们运行一个程序,需要使用Bean,Bean实际上是有需求去获取一些对应的配置值的,以前面的配置为例
这里使用到了Datasource,你不告诉他连接信息,username,psw等等,他能用吗,不能,所以一定会给他设置值,并且,不能像构造方法那样写,如果需要改需求,代码就耦合死了,我们可以使用配置文件的方式去解耦合
4.6、配置文件
cartoon:
cat:
name: "假老练"
age: 3
ms:
name: "风车车"
age: 4
这里需要注意,参数名需要和创建的属性名一致
4.7、测试
为什么还是空指针?因为这俩对象目前并没有get和set方法
这是我们创建的cat类
这是我们动画片组建中的对象变量cat和ms
目前他俩是没有get和set方法的
设置了data注解以后就有了
原理:卡通类中的两个对象属性,只是在其中创建了,并没有给他们赋值,无论是构造方法的时候初始化他们,亦或者是通过配置文件的方式给他俩赋值,对象在这个过程中,是被初始化了的,被初始化了,自然可以通过get的方式来获取对象的值,倘若没赋予,那自然是空指针异常
4.8、问题的提出
目前这个项目的环境构造已经是搭建的差不多了,这时候出现了一个问题,与耦合性有关系
在我的卡通类中,我的这个类,是不是与这个配置文件的注解绑定死了?如果没有这个配置文件的注解,我这个类连用都没法用,这样设计存在一些不合理的现象,如何去修改?
1、自定义属性的配置类
抽取思想,将这个注解抽取出来,怎么抽取?我专门设置一个类,这个类用于存储猫和老鼠中的猫、与老鼠的属性
@Component
@ConfigurationProperties("cartoon")
@Data
// 猫与老鼠属性配置类
public class CartoonUtil {
private Cat cat;
private Mouse ms;
}
@ConfigurationProperties的注解的前提是该类必须是一个组件才可以
2、卡通类中注入工具类
他既然是个组件,那我们可以通过注入的方式来对其进行赋值,暂时先不用,因为后面会衍生一个东西,不通过注入的方式生产他效果会更好
-
在卡通类中声明这个属性(util)
-
开启我们的构造方法,将其设置为有参构造
-
我需要这个属性,而这个属性的创建权利我交给了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
-
卡通类中配置注解--EnableConfigurationProperties
- 该注解的含义是他能够强制设定哪一个类,加载成Bean
当我加载卡通类的时候,读取工具类,并将其加载为Bean
4.11、小升华
我的卡通类是被注册为一个组件了的,无论我使用与否,他都会被加载到Spring的容器当中,这里的设定也有一些些许的不合理,我现在将其的组件去掉
在启动类中加载这个类
这样我想使用,那就可以通过import去导入,将其注册为Bean,不使用,我去掉这个注解即可
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 后端思维之高并发处理方案
· 理解Rust引用及其生命周期标识(下)
· 从二进制到误差:逐行拆解C语言浮点运算中的4008175468544之谜
· .NET制作智能桌面机器人:结合BotSharp智能体框架开发语音交互
· 软件产品开发中常见的10个问题及处理方法
· 后端思维之高并发处理方案
· 千万级大表的优化技巧
· 在 VS Code 中,一键安装 MCP Server!
· 10年+ .NET Coder 心语 ── 继承的思维:从思维模式到架构设计的深度解析
· 想让你多爱自己一些的开源计时器