粗略解析记录spring系列常用注解,该篇暂不探寻源码原理阶段。主要记录作用
1.@ConfigurationProperties和@Value
两个注解都是可以从配置文件中属性自动赋值到实体类Bean中的属性值,但是@Value只能一个一个属性的修饰,而ConfigurationProperties适合很多属性的情况下,并且还可以指定配置文件中的某一段来配置导入,需要指定perfix前缀属性,并且还支持_ - 自动转换驼峰
2.@EnableConfigurationProperties
让使用了 @ConfigurationProperties 注解的类生效,并且将该类(注解里面的value参数修饰的类)注入到 IOC 容器中,交由 IOC 容器进行管理。因为单纯的在一个Java类使用注解无法生效,前提是(需要被spring容器来管理才可以,或者可以不用该注解,而用@Configuration或@Componet 也可以让ConfigurationProperties注解生效)而该注解里面还有一个属性,该属性能设置让选中ConfigurationProperties修饰类生效
Class<?>[] value() default {}
3.条件注解@ConditionalOnClass,@ConditionalOnProperty,@ConditionalOnMissingBean,@ConditionalOnBean
常见的@Conditionalxxx
开头的注解我们称之为条件注解,常见的条件注解有如上这些,这几个注解通常会结合使用,一般都是在配置类中使用,SpringBoot各种xxxxAutoCconfiguration
都用到了这些注解,这也是SpringBoot自动装配的重要注解
3.1@ConditionalOnProperty
该注解表示在spring boot中有时候需要控制配置类是否生效,可以使用该注解来控制配置类@Configuration是否生效,简单来讲,一般是在配置类上或者是@Bean修饰的方法上,添加此注解表示一个类是否要被Spring上下文加载,若满足条件则加载,若不满足条件则不加载。
通过其两个属性name以及havingValue来实现的,其中name用来从application.properties或者yml,总之配置文件中读取某个属性值。prefix为配置文件中的前缀。(value是name属性的别名,作用都是一样的。)
如果该值(prefix.name)为空,则返回false;
如果值不为空,则将该值与havingValue指定的值进行比较,如果一样则返回true;否则返回false。
如果返回值为false,则该属性修饰的Java对象不被spring管理所以不生效;为true则生效。该逻辑优先级最高;但是还有一种特殊情况,比如不配置havingValue属性,或者故意把havingValue值写错。则会有两种不同的结果。
不配置havingValue属性:matchIfMissing为true则一定会加载配置类,matchIfMissing为false,有配置(name/prefix)加载类,无配置不加载类。
配置错误的havingValue的值:无论matchIfMissing怎么设置,都不会加载。假如havingValue值正确(有配置的name/prefix)配置文件的值相等正确。那么就是正确情况下的true,则必然加载
3.2@ConditionalOnClass,@ConditionalOnMissingClass
@ConditionalOnClass:某个class(或者注解中属性设置的所有的class)位于类路径上,才会实例化一个修饰当前Bean。即判断当前classpath下是否存在指定类(所有),若是则将当前的配置装载入spring容器。也可以这么理解该注解表明输入(xxx.class)几个class,相当于给配置文件添加了一个开关,当检测存在输入类的时候,被改注解修饰的配置类会生效;否则不会将不会实例化。也就是说,如果项目中存在该依赖,会自动开启配置类,这也是spring boot自动装配原理中很重要的注解
@ConditionalOnMissingClass:当指定的类([]所有)不存在时才会被加载
3.2@ConditionalOnBean,@ConditionalOnMissingBean
@ConditionalOnBean:当前spring容器中存在指定的实例对象时,被修饰的对应的@bean才会被spring加载
@ConditionalOnMissingBean:同上正好相反,当不存在时才会被加载。
-------------相关类似的注解还有
@ConditionalOnBean(仅仅在当前上下文中存在某个对象时,才会实例化一个Bean)
@ConditionalOnClass(某个class位于类路径上,才会实例化一个Bean)
@ConditionalOnExpression(当表达式为true的时候,才会实例化一个Bean)
@ConditionalOnMissingBean(仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean)
@ConditionalOnMissingClass(某个class类路径上不存在的时候,才会实例化一个Bean)
@ConditionalOnNotWebApplication(不是web应用)
4.@import
4.1、@Import注解须知
-
@Import只能用在类上 ,@Import通过快速导入的方式实现把实例加入spring的IOC容器中
-
加入IOC容器的方式有很多种,@Import注解就相对很牛皮了,@Import注解可以用于导入第三方包 ,当然@Bean注解也可以,但是@Import注解快速导入的方式更加便捷
-
@Import注解有三种用法
4.2、@Import的三种用法
@Import的三种用法主要包括:
1、直接填class数组方式
2、ImportSelector方式【重点】
3、ImportBeanDefinitionRegistrar方式
4.2.1、第一种用法:直接填class数组
直接填对应的class数组,class数组可以有0到多个。
语法如下:
@Import({ 类名.class , 类名.class... })
public class TestDemo {
}
对应的import的bean都将加入到spring容器中,这些在容器中bean名称是该类的全类名 ,比如com.yc.类名
4.2.2、第二种用法:ImportSelector方式【重点】
这种方式的前提就是一个类要实现ImportSelector接口,假如我要用这种方法,目标对象是Myclass这个类,分析具体如下:
创建Myclass类并实现ImportSelector接口
public class Myclass implements ImportSelector {
//既然是接口肯定要实现这个接口的方法
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[0];
}
}
分析实现接口的selectImports方法中的:
1、返回值: 就是我们实际上要导入到容器中的组件全类名【重点 】
2、参数: AnnotationMetadata表示当前被@Import注解给标注的所有注解信息【不是重点】
需要注意的是selectImports方法可以返回空数组但是不能返回null,否则会报空指针异常!
以上分析完毕之后,具体用法步骤如下:
第一步:创建Myclass类并实现ImportSelector接口,这里用于演示就添加一个全类名给其返回值
public class Myclass implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.yc.Test.TestDemo3"};
}
}
第二步:使用ImportSelector方式的Myclass类
@Import({Myclass.class})
这种方式也是springboot自动化配置@EnableAutoConfiguration的涉及到的用法。
可详见spring5学习系列之------3 给容器注册组件三 @Conditional 和 @Import 注解用法_xzjayx的博客-CSDN博客
4.2.3、第三种用法:ImportBeanDefinitionRegistrar方式
同样是一个接口,类似于第二种ImportSelector用法,相似度80%,只不过这种用法比较自定义化注册,具体如下:
第一步:创建Myclass2类并实现ImportBeanDefinitionRegistrar接口
public class Myclass2 implements ImportBeanDefinitionRegistrar {
//该实现方法默认为空
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
}
}
参数分析:
第一个参数:annotationMetadata 和之前的ImportSelector参数一样都是表示当前被@Import注解给标注的所有注解信息
第二个参数表示用于注册定义一个bean
第二步:编写代码,自定义注册bean
public class Myclass2 implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
//指定bean定义信息(包括bean的类型、作用域...)
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(TestDemo4.class);
//注册一个bean指定bean名字(id)
beanDefinitionRegistry.registerBeanDefinition("TestDemo4444",rootBeanDefinition);
}
}
5.@EnableAutoConfiguration和@Configuration
5.1@EnableAutoConfiguration
该注解是springboot中的开启自动配置的注解,简单点说就是Spring Boot根据依赖中的jar包,自动选择实例化某些配置@EnableAutoConfiguration注解通过读取spring.factories文件里面的EnableAutoConfiguration指定的类,来初始化指定类下面的所有加了@Bean的方法,并初始化这个bean。
spring-boot-autoconfigure.jar/META-INF/spring.factories 源码中
这里插一嘴如果我们想自定义springboot-starter,一定要记得在自行写resource中写建文件spring.factories ,然后执行EnableAutoConfiguration=\xxx,\xxx
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.study.spring.boot.configuration.auto.config.AutoFirstBeanConfiguration,\
com.study.spring.boot.configuration.auto.config.AutoSecondBeanConfiguration
----------------------------------------------------
这里多说几句,至于为什么是spring.factories文件,是因为在springboot启动类入口中
SpringApplication.run静态方法运行启动之时,会首选会new springApplication(arg1,arg2);在该方法用会有做设置初始化接口和监听器接口时,会有一个重要方法getSpringFactoriesInstances(xx)
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
在该方法中SpringFactoriesLoader.loadFactoryNames(type, classLoader)该方法里面会加载spring.factories,至此就springboot在启动之时就先读取并且加载了spring.factories文件内容。
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
//指定META-INF/spring.factories的文件名和位置并且加载读取
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
//解析配置文件,会放入到对应的result中做对应缓存起来
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
5.2@Configuration
该注解仅仅就是表示是一个配置类的@Component,然后被spring容器管理
两种方式的差异
- 初始化的时机,@Configuration初始化的方式总是在@EnableAutoConfiguration初始化方式之前
- @Configuration初始化的顺序和扫描的过程相关,并不能进行有效的进行指定,不方便确定文件加载的顺序
- @EnableAutoConfiguration可以通过@AutoConfigureAfter、@AutoConfigureBefore 和 @AutoConfigureOrder来指定类的加载顺序
- @Configuration初始化会先初始化所有被扫到加了@Configuration文件的@PostConstruct注解,然后再初始化这些文件里面的@Bean注解,但是@EnableAutoConfiguration是根据文件来进行初始化的,所以会初始化完一个文件的@PostConstruct注解,然后再初始化这个文件的@Bean注解,然后再接着处理另外的文件同一个来回。
所以需要提供bean给其他jar包进行使用的时候,最好使用@EnableAutoConfiguration方式(spring-boot-starters里面的都是通过这种方式来进行提供的,他的所有初始化的过程全部在spring-boot-autoconfigure项目中),因为能更好的控制类文件的加载顺序。有助于维护更佳复杂的项目。
另外需要注意一点的就是,如果@EnableAutoConfiguration提供的类名称在扫描的路径之中,spring-boot会把这些类作为configuration先进行初始化了。然后@AutoConfigureAfter@AutoConfigureBefore 和 @AutoConfigureOrder 这类指定顺序的注解都会失效