我们在看Spring Boot源码时,经常会看到一些配置类中使用了注解,本身配置类的逻辑就比较复杂了,再加上一些注解在里面,让我们阅读源码更加难解释了,因此,这篇博客主要对配置类上的一些注解的使用以及实现原理做分析,从而让阅读源码更加简单一点。
Spring boot 集成mybatis时,就有一个非常重要的配置类MybatisAutoConfiguration,这个类上配置了一堆注解,如下
@org.springframework.context.annotation.Configuration @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }) @ConditionalOnBean(DataSource.class) @EnableConfigurationProperties(MybatisProperties.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class) public class MybatisAutoConfiguration { ... }
ConditionalOnClass注解
话不多说,先来看ConditionalOnClass注解的使用。先来看
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnClassCondition.class) public @interface ConditionalOnClass { Class<?>[] value() default {}; String[] name() default {}; }
从ConditionalOnClass注解的属性可以看出,我们可以配置一个Class数组,也可以配置一个字符串数组,显然配置Class数组,肯定在编译环境中存在该类。如果配置字符串数组的话,字符串构成的类名,在编译环境中不一定存在,基于以上的可能性,我们来测试一把。
- 创建普通类
public class ConditionalOnClassAnnocation { }
- 创建ConditionalOnClassUser测试类
@ConditionalOnClass(ConditionalOnClassAnnocation.class) @Service("conditionalOnClassUser") public class ConditionalOnClassUser { public void a() { System.out.println("a"); } }
- 创建测试方法
@RequestMapping("conditionalOnClassTest") public String conditionalOnClassTest(){ Object object = SpringContextUtils.getBean("conditionalOnClassUser"); System.out.println(object); return "Sucess"; }
测试结果
从上面测试结果得知,ConditionalOnClassAnnocation类并没有在Spring容器中,只存在编译环境,因此一般我们开发业务项目时,如果ConditionalOnClassAnnocation不存在,编译都不会过,更不用谈ConditionalOnClassUser存储到容器中了。我们修改一下测试条件。
@ConditionalOnClass(name = "com.example.springbootstudy.service.ConditionalOnClassAnnocation") @Service("conditionalOnClassUser") public class ConditionalOnClassUser { public void a() { System.out.println("a"); } }
接着测试
依然没有问题,容器中仍然注入了conditionalOnClassUser对象,我们再来测试,将ConditionalOnClassAnnocation改成XXX
@ConditionalOnClass(name = "com.example.springbootstudy.service.XXX") @Service("conditionalOnClassUser") public class ConditionalOnClassUser { public void a() { System.out.println("a"); } }
显然,我们代码中不存在XXX类,再次测试。
当context.getBean(“conditionalOnClassUser”)时,容器中并没有注入conditionalOnClassUser实例。
既然Class和name是一个数组,如果配置多个会怎样呢?新加普通类
public class ConditionalOnClassAnnocation1 { }
修改测试类
@ConditionalOnClass(name = {"com.example.springbootstudy.service.ConditionalOnClassAnnocation" ,"com.example.springbootstudy.service.ConditionalOnClassAnnocation1"}) @Service("conditionalOnClassUser") public class ConditionalOnClassUser { public void a() { System.out.println("a"); } }
再次测试
从上述测试中可以看到。ConditionalOnClassAnnocation和ConditionalOnClassAnnocation1类,当前编译环境都存在,因此容器中注入了conditionalOnClassUser类,但如果我们将ConditionalOnClassAnnocation1改成当着环境中不存在的类ConditionalOnClassAnnocation2,会怎样呢?
@ConditionalOnClass(name = {"com.example.springbootstudy.service.ConditionalOnClassAnnocation" ,"com.example.springbootstudy.service.ConditionalOnClassAnnocation2"}) @Service("conditionalOnClassUser") public class ConditionalOnClassUser { public void a() { System.out.println("a"); } }
测试结果
从上面的测试中,我们得出一个结论ConditionalOnClass注解内配置的类,必需在编译环境中都存在,此时被注解的类【ConditionalOnClassUser】才会注入到容器中,编译环境中任意一个类不存在,【ConditionalOnClassUser】都不会被注入到容器中,和ConditionalOnClass注解中配置的类是否会注入到容器无关
我发现ConditionalOnMissingClass的源码和ConditionalOnClass源码是写在一块的,为了方便理解,那我们先来看ConditionalOnMissingClass的示例吧。
ConditionalOnMissingClass注解
我们先来看一看ConditionalOnMissingClass注解的源码。
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnClassCondition.class) public @interface ConditionalOnMissingClass { String[] value() default {}; }
从上述源码中我们可以看到,和ConditionalOnClass注解的区别在于ConditionalOnMissingClass没有Class[] 属性。而从字面意思上来理解,就是和ConditionalOnClass功能刚好相反,是否真的相反,看看例子再说。
- 创建普通类ConditionalOnMissingClassAnnocation和ConditionalOnMissingClassAnnocation1
public class ConditionalOnMissingClassAnnocation { } public class ConditionalOnMissingClassAnnocation1 { }
- 创建测试类ConditionalOnMissingClassUser,上面配置了ConditionalOnMissingClass注解
@Service("conditionalOnMissingClassUser") @ConditionalOnMissingClass("com.example.springbootstudy.service.ConditionalOnMissingClassAnnocation") public class ConditionalOnMissingClassUser { }
我们知道ConditionalOnMissingClassAnnocation在编译环境中定义肯定存在。
- 测试1
容器中并不存在conditionalOnMissingClassUser类,因此,初步得出结论,ConditionalOnMissingClassUser类和其注解ConditionalOnMissingClass中配置的类是你死我活的关系,只要编译环境中存在ConditionalOnMissingClass注解配置的类,则Spring容器中就不会注册ConditionalOnMissingClass类。真是这样吧,要多举几个例子看看。修改ConditionalOnMissingClass属性信息,配置一个不存在的类
@Service("conditionalOnMissingClassUser") @ConditionalOnMissingClass("com.example.springbootstudy.service.ConditionalOnMissingClassAnnocation2") public class ConditionalOnMissingClassUser { }
我们知道ConditionalOnMissingClassAnnocation2类肯定在编译环境中不存在。开始测试
- 测试2
显然当ConditionalOnMissingClass注解中配置的类在当前编译环境不存在时,则当前类会被注入到Spring容器中。
- 测试3 ,当ConditionalOnMissingClass注解中配置两个类,一个类存在,另一个类不存在时。
@Service("conditionalOnMissingClassUser") @ConditionalOnMissingClass({"com.example.springbootstudy.service.ConditionalOnMissingClassAnnocation", "com.example.springbootstudy.service.ConditionalOnMissingClassAnnocation2"}) public class ConditionalOnMissingClassUser { }
当ConditionalOnMissingClass注解中配置一个类存在,另一个类不存在时,仍然不会注册到Spring容器中。
- 测试4 ,当ConditionalOnMissingClass注解中配置两个类都不存在时。
当ConditionalOnMissingClass注解中配置的两个类都不存在时,ConditionalOnMissingClassUser类会被注入到容器中。
从测试中,我们得出一个结论,ConditionalOnMissingClass注解中配置的类,只要在编译环境中存在,则被配置注解的类就不会被注册到Spring容器中。结论刚好和ConditionalOnClass注解相反
那么如果配置了ConditionalOnMissingClass和ConditionalOnClass注解,且一个条件满足,一个条件不满足,会是什么情况呢?
创建测试类
@Service("conditionalOnMissingClassOnClassUser") @ConditionalOnMissingClass({"com.example.springbootstudy.service.ConditionalOnMissingClassAnnocation4", "com.example.springbootstudy.service.ConditionalOnMissingClassAnnocation3"}) @ConditionalOnClass(name = "com.example.springbootstudy.service.ConditionalOnMissingClassAnnocation3") public class ConditionalOnMissingClassOnClassUser { }
从示例中,我们知道ConditionalOnMissingClassAnnocation4类和ConditionalOnMissingClassAnnocation3类不存在于编译环境中,因此
@ConditionalOnMissingClass({“com.example.springbootstudy.service.ConditionalOnMissingClassAnnocation4”,
“com.example.springbootstudy.service.ConditionalOnMissingClassAnnocation3”}) 的条件为true,但是@ConditionalOnClass(name = “com.example.springbootstudy.service.ConditionalOnMissingClassAnnocation3”) 条件为false,而我们再来看看测试结果
容器中并没有注册conditionalOnMissingClassOnClassUser类,因此当类配置了多个条件注解时,类是否注册到容器中的判断条件是,条件注解之间是AND关系,因此,ConditionalOnMissingClass条件注解为true时,ConditionalOnClass条件注解为false时,最终conditionalOnMissingClassOnClassUser类没有被注册到容器中。当然举一反三,如果ConditionalOnMissingClass条件注解为true,同时ConditionalOnClass条件注解也为true时,肯定conditionalOnMissingClassOnClassUser会被注入到容器中。
@Service("conditionalOnMissingClassOnClassUser") @ConditionalOnMissingClass({"com.example.springbootstudy.service.ConditionalOnMissingClassAnnocation4", "com.example.springbootstudy.service.ConditionalOnMissingClassAnnocation3"}) @ConditionalOnClass(name = "com.example.springbootstudy.service.ConditionalOnMissingClassAnnocation") public class ConditionalOnMissingClassOnClassUser { }
测试结果:
因为ConditionalOnMissingClassAnnocation类存在,而ConditionalOnMissingClassAnnocation3类和ConditionalOnMissingClassAnnocation4类不存在。
ConditionalOnMissingClass和ConditionalOnClass注解的使用己经了解了,那么Spring源码中又是如何实现这个注解功能的呢?我们来看源码。
public Set<BeanDefinition> findCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>(); try { String packageSearchPath = "classpath*:"+ resolveBasePackage(basePackage) + '/' + this.resourcePattern; Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath); boolean traceEnabled = logger.isTraceEnabled(); boolean debugEnabled = logger.isDebugEnabled(); for (Resource resource : resources) { if (traceEnabled) { logger.trace("Scanning " + resource); } if (resource.isReadable()) { try { MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); if (isCandidateComponent(metadataReader)) { ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setResource(resource); sbd.setSource(resource); if (isCandidateComponent(sbd)) { if (debugEnabled) { logger.debug("Identified candidate component class: " + resource); } candidates.add(sbd); } else { if (debugEnabled) { logger.debug("Ignored because not a concrete top-level class: " + resource); } } } else { if (traceEnabled) { logger.trace("Ignored because not matching any filter: " + resource); } } } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to read candidate component class: " + resource, ex); } } else { if (traceEnabled) { logger.trace("Ignored because not readable: " + resource); } } } } catch (IOException ex) { throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); } return candidates; }
从上述代码中看到,只有满足条件的bean才会被加入到candidates中,因此在这个方法isCandidateComponent中应该就是判断,当前bean是否注入到容器中。我们跟进代码。
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { for (TypeFilter tf : this.excludeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return false; } } for (TypeFilter tf : this.includeFilters) { //当前类或父类或接口的注解匹配上TypeFilter配置的注解 if (tf.match(metadataReader, this.metadataReaderFactory)) { return isConditionMatch(metadataReader); } } return false; }
在上述方法中,只要返回false,bean则不会注册到容器中,那excludeFilters和includeFilters到底是什么东西呢?excludeFilters默认为空,includeFilters又是在什么时候初始化值的呢?在代码中寻寻觅觅。找到了注入方法registerDefaultFilters(),为了找到在哪里调用,我们在这个方法中打一个断点。
从registerDefaultFilters方法中得知。new AnnotationTypeFilter(Component.class)肯定会被加入到includeFilters集合中的,如果当前编译环境中存在javax.annotation.ManagedBean类,则会将new AnnotationTypeFilter(ManagedBean.class)加入到includeFilters集合中,如果当前环境存在javax.inject.Named类,则会将new AnnotationTypeFilter(Named.class)加入到includeFilters中。而registerDefaultFilters方法最初是哪里调用的呢?我们跟踪到createApplicationContext()方法,而createApplicationContext()方法是Spring Boot启动时必调的方法。我们来看看createApplicationContext()方法的实现。
protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { contextClass = Class.forName(this.webEnvironment ? "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext" : "org.springframework.context." + "annotation.AnnotationConfigApplicationContext"); } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass", ex); } } return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass); }
最终在实例化AnnotationConfigEmbeddedWebApplicationContext类时,会调用registerDefaultFilters方法。为什么是实例化AnnotationConfigEmbeddedWebApplicationContext呢?因为我们是Spring Boot项目用来做测试的,而webEnvironment为true的条件是
private boolean deduceWebEnvironment() { for (String className : "{ "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" }") { if (!ClassUtils.isPresent(className, null)) { return false; } } return true; }
当前编译环境中存在javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext类,不外乎其他的情况也会调用registerDefaultFilters()方法,来填充includeFilters的值,但分析方法一样,这里就不再过多举例说明了。
上述过程中有一个非常重要的方法match方法,比如 AnnotationTypeFilter(Component.class)注解类型过滤器。我们实例HelloServiceImpl中配置了@Service注解。而要被AnnotationTypeFilter匹配为true,那是怎样匹配的呢?先看HelloServiceImpl类是否配置了@Component注解,如果没有配置,那么看HelloServiceImpl的注解类中是否配置了Component注解,如果配置了,则匹配成功,如果没有配置,则继续递归找所有HelloServiceImpl的注解类的注解是否配置Component注解,以此类推。直到找到为止,没有找到,匹配失败。我们先来看看@Service注解的结构 。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Service { String value() default ""; }
我们看到@Service注解中配置了@Component注解,因此配置了@Service注解的类match方法返回true。
关于@Component的匹配逻辑和@Transactional关于事务的匹配逻辑还是有一定区分的。Transactional允许继承,但是@Component不允许,我们来看一个例子。
- 创建MyService注解,添加Inherited注解
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Service @Inherited public @interface MyService { String value() default ""; }
- 创建ServiceTestImpl类,实现ServiceTestI接口,在ServiceTestI接口中配置@MyService注解
@MyService public interface ServiceTestI { } public class ServiceTestImpl implements ServiceTestI{ }
- 测试
项目报错。ServiceTestImpl并没有注入。显然不能继承父接口的注解。即使注解中MyService配置了Inherited注解
接下来,我们来看Transactional注解,看一个关于事务的小例子。
- 创建Spring配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/pple_test?characterEncoding=utf-8"></property> <property name="username" value="ldd_biz"></property> <property name="password" value="Hello1234"></property> <property name="initialSize" value="1"></property> <property name="maxIdle" value="2"></property> <property name="minIdle" value="1"></property> </bean> <bean id="userService" class="com.spring_1_100.test_71_80.test73_jdbc_transaction.UserServiceImpl"> <property name="jdbcTemplate" ref="dataSource"></property> </bean> </beans>
- 配置Transactional注解
@Transactional(propagation = Propagation.REQUIRED) public interface UserService { void save(User user) throws Exception; } public class UserServiceImpl implements UserService { private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } @Override public void save(User user) throws Exception { jdbcTemplate.update("INSERT INTO lz_user (username, password, real_name, manager_id) VALUES ( ?, ?, ?, ?) ", new Object[]{user.getUsername(), user.getPassword(), user.getRealName(), user.getManagerId()}); throw new RuntimeException("bbbbbbbbbbbbb"); } }
上述代码我们需要注意的是Transactional注解配置有接口上,而不是配置在实现类UserServiceImpl上。在save()方法中,当执行插入操作以后,并且抛出异常。下面就看测试结果,看数据有没有回滚。
- 编写测试类
public class Test73 { public static void main(String[] args) throws Exception{ ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring_1_100/config_71_80/spring73.xml"); UserService userService = (UserService) ac.getBean("userService"); User user = new User(); user.setManagerId(1l); user.setPassword("1239832"); user.setUsername("zhangsan"); user.setRealName("瞿贻晓"); userService.save(user); } }
- 开始测试
程序抛出异常,同时数据没有插入到数据库
我偿注释掉UserService上的@Transactional注解
通过上面两个例子的对比,我相信大家对@Component注解的匹配有了一定的了解,他和Transactional注解的匹配规则不一样,@Transactional是匹配切面的规则,而@Component注解是匹配Scan的规则 。
言规正传,我们继续分析ConditionalOnMissingClass和ConditionalOnClass注解的实现原理。
private boolean isConditionMatch(MetadataReader metadataReader) { if (this.conditionEvaluator == null) { this.conditionEvaluator = new ConditionEvaluator(getRegistry(), getEnvironment(), getResourceLoader()); } return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata()); }
如果conditionEvaluator不存在,则直接new ConditionEvaluator()类,因此shouldSkip没有什么好说的,就是直接调用
ConditionEvaluator的shouldSkip方法。我们进入shouldSkip方法。
public boolean shouldSkip(AnnotatedTypeMetadata metadata) { return shouldSkip(metadata, null); } public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) { if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) { return false; } if (phase == null) { //从调用的shouldSkip方法中来看,第一次调用phase == null ,因此第一次肯定会进入下面的代码 //我们知道,配置了@ConditionalOnClass注解的实例的metadata肯定实现了AnnotationMetadata接口 //并且配置了ConditionalOnClass注解的实例也肯定配置了@Component注解或注解配置了@Component注解 if (metadata instanceof AnnotationMetadata && ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) { //递归调用shouldSkip方法,不过此时phase不为空,如果还是空,则出现死循环 return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION); } return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN); } List<Condition> conditions = new ArrayList<Condition>(); //获取注解ConditionalOnMissingClass或ConditionalOnClass上配置的注解Conditional的value值。也就是OnClassCondition类 for (String[] conditionClasses : getConditionClasses(metadata)) { for (String conditionClass : conditionClasses) { //实例化OnClassCondition类,并加入到conditions中 Condition condition = getCondition(conditionClass, this.context.getClassLoader()); conditions.add(condition); } } //将所有配置了@Order注解的OnClassCondition排序 AnnotationAwareOrderComparator.sort(conditions); for (Condition condition : conditions) { ConfigurationPhase requiredPhase = null; if (condition instanceof ConfigurationCondition) { requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase(); } if (requiredPhase == null || requiredPhase == phase) { //调用OnClassCondition的matchs方法 if (!condition.matches(this.context, metadata)) { return true; } } } return false; }
这个方法不难,在注释中己经说得很明确了,接下来,我们看OnClassCondition的matchs方法到底做了哪些事情。
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String classOrMethodName = getClassOrMethodName(metadata); try { ConditionOutcome outcome = getMatchOutcome(context, metadata); logOutcome(classOrMethodName, outcome); recordEvaluation(context, classOrMethodName, outcome); return outcome.isMatch(); } catch (NoClassDefFoundError ex) { throw new IllegalStateException( "Could not evaluate condition on " + classOrMethodName + " due to " + ex.getMessage() + " not " + "found. Make sure your own configuration does not rely on " + "that class. This can also happen if you are " + "@ComponentScanning a springframework package (e.g. if you " + "put a @ComponentScan in the default package by mistake)", ex); } catch (RuntimeException ex) { throw new IllegalStateException( "Error processing condition on " + getName(metadata), ex); } }
最终方法返回值是调用outcome的isMatch方法,而isMatch方法实际上是返回了outcome的match属性,因此重点就落在了outcome对象的创建上,从上面代码中可以看到,outcome是由getMatchOutcome方法返回的,那我们跟进getMatchOutcome方法。
private enum MatchType { PRESENT { @Override public boolean matches(String className, ClassLoader classLoader) { return isPresent(className, classLoader); } }, MISSING { @Override public boolean matches(String className, ClassLoader classLoader) { return !isPresent(className, classLoader); } }; 省略... } public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ClassLoader classLoader = context.getClassLoader(); ConditionMessage matchMessage = ConditionMessage.empty(); //获取实例上配置了ConditionalOnClass注解的所有属性值字符串 List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class); if (onClasses != null) { List<String> missing = getMatches(onClasses, MatchType.MISSING, classLoader); //如果编译环境中存在类不存在,则match为false if (!missing.isEmpty()) { return ConditionOutcome .noMatch(ConditionMessage.forCondition(ConditionalOnClass.class) .didNotFind("required class", "required classes") .items(Style.QUOTE, missing)); } matchMessage = matchMessage.andCondition(ConditionalOnClass.class) .found("required class", "required classes").items(Style.QUOTE, getMatches(onClasses, MatchType.PRESENT, classLoader)); } //获取ConditionalOnMissingClass注解中配置的所有值 List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class); if (onMissingClasses != null) { List<String> present = getMatches(onMissingClasses, MatchType.PRESENT, classLoader); //如果存在配置类在编译环境中存在,match为false并直接返回 if (!present.isEmpty()) { return ConditionOutcome.noMatch( ConditionMessage.forCondition(ConditionalOnMissingClass.class) .found("unwanted class", "unwanted classes") .items(Style.QUOTE, present)); } matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class) .didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE, getMatches(onMissingClasses, MatchType.MISSING, classLoader)); } //如果ConditionalOnMissingClass条件和ConditionalOnClass条件都为true,则直接返回ConditionOutcome的match为true return ConditionOutcome.match(matchMessage); }
在看代码的时候,需要注意一下。getMatches方法的第二个参数MatchType枚举,当ConditionalOnClass注解匹配是,是传入的是MatchType.MISSING,而ConditionalOnMissingClass注解匹配时传入的是MatchType.PRESENT枚举,这两个枚举的结果刚好相反。经过前面的分析,我相信大家对ConditionalOnClass注解及ConditionalOnMissingClass注解的使用己经有了深刻的理解了,下面继续接着ConditionalOnBean注解的分析。
ConditionalOnBean注解
关于ConditionalOnBean注解,我们先来看看ConditionalOnBean 注解
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnBeanCondition.class) public @interface ConditionalOnBean { // 容器中必需有该类型,当前被注解的类才会被注册到容器中 Class<?>[] value() default {}; String[] type() default {}; //容器中的bean必需配置了该注解,当前被注解的类才会被加载到容器中 Class<? extends Annotation>[] annotation() default {}; //容器中必需有该name的bean,当前类才会被加载到容器中 String[] name() default {}; //查询bean的模式,ALL表示先从子容器中查找,如果找不到,则递归查找父类 SearchStrategy search() default SearchStrategy.ALL; }
当看到了ConditionalOnBean的注释后,我相信大家对ConditionalOnBean的使用,没有太大问题了,即使没有太大问题,我们还是要一一的来测试,这样才能体现对知识理解的严谨性。下面我们来看一个最简单的例子。
- 创建普通bean
@Service public class ConditionalOnBeanAnnocation { }
- 创建被测试类
@ConditionalOnBean({ConditionalOnBeanAnnocation.class}) @Service public class ConditionalOnBeanUser { }
- 开始测试
@Autowired private ConditionalOnBeanUser conditionalOnBeanUser; @RequestMapping("conditionalOnBeanUserTest") public String conditionalOnBeanUserTest() { System.out.println(conditionalOnBeanUser); return "Sucess"; }
测试结果
从测试结果中我们可以看出,容器中己经存在了conditionalOnBeanUser的bean。我们修改一下。去掉ConditionalOnBeanAnnocation上的@Service注解。
代码启动报错。
下面我们再来测试,假如配置两个类,一个存在于容器中,另一个不会注册到容器中。编写测试代码如下
@RequestMapping("conditionalOnBeanUserTest") public String conditionalOnBeanUserTest() { System.out.println(conditionalOnBeanUser); ConditionalOnBeanAnnocation1 conditionalOnBeanAnnocation1 = SpringContextUtils.getBean(ConditionalOnBeanAnnocation1.class); System.out.println(conditionalOnBeanAnnocation1); return "Sucess"; }
测试结果:
从上述测试结果,我们得出结论,对于ConditionalOnBean注解中配置的参数,只要任意一个类存在于容器中,则会被ConditionalOnBean注解修饰的类都会被实例化到容器中。
接下来,我们来看另外一个实例,在@ConditionalOnBean(annotation = MyConditionalOnBeanTest.class)中配置注解,那配置注解是什么意思呢?也就是说,只要容器中有一个实例被MyConditionalOnBeanTest注解修饰,则被ConditionalOnBean修饰的bean才会注册到容器中。
- 创建MyConditionalOnBeanTest注解
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyConditionalOnBeanTest { }
- 创建bean 并配置MyConditionalOnBeanTest注解
@MyConditionalOnBeanTest @Service public class MyConditionalOnBeanTestImpl { }
- 测试
@ConditionalOnBean(annotation = MyConditionalOnBeanTest.class) @Service public class ConditionalOnBeanUser { }
测试结果
当注释掉MyConditionalOnBeanTestImpl类的MyConditionalOnBeanTest注解,显然conditionalOnBeanUser没有被实例化到容器中。
ConditionalOnSingleCandidate注解
接下来,我们继续看和ConditionalOnBean类似的注解ConditionalOnSingleCandidate,先来看看ConditionalOnSingleCandidate注解的内容。
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnBeanCondition.class) public @interface ConditionalOnSingleCandidate { Class<?> value() default Object.class; String type() default ""; SearchStrategy search() default SearchStrategy.ALL; }
ConditionalOnSingleCandidate和ConditionalOnBean相比,少了annotation和name属性,为什么会少这两个属性呢?目前我也不知道,到后面分析源码时,再来分析。
毋庸置疑,ConditionalOnSingleCandidate肯定是要求容器中有某个bean,被修饰的bean才会注册到容器中,ConditionalOnSingleCandidate的属性不存在的bean,这里就不测试了,被修饰的bean肯定不会被注册到容器中。如
@Service @ConditionalOnSingleCandidate(ConditionalOnSingleCandidateUserAnnocation0.class) public class ConditionalOnSingleCandidateUser { }
如果ConditionalOnSingleCandidateUserAnnocation0在容器中不存在,则ConditionalOnSingleCandidateUser肯定不会注册到容器中。
从ConditionalOnSingleCandidate的类名称,大致可以猜测,容器中必需有一个单例Bean,被修饰的bean才会注册到容器中,真的是如此吗?。根据猜测,我们来测试一下。
- 创建一个普通bean
@Service public class ConditionalOnSingleCandidateUserAnnocation0 { }
- 创建测试bean
@Service @ConditionalOnSingleCandidate(ConditionalOnSingleCandidateUserAnnocation0.class) public class ConditionalOnSingleCandidateUser { }
-
测试结果
-
我们将ConditionalOnSingleCandidateUserAnnocation0变成多例
显然,我们之前的猜测有问题,Single不是单例的意思,那么不是单例,会不会是单个呢?那我们来测试一下。 -
创建接口IConditionalOnSingleCandidateUserAnnocation
public interface IConditionalOnSingleCandidateUserAnnocation { }
- 创建测试类ConditionalOnSingleCandidateUserAnnocation实现IConditionalOnSingleCandidateUserAnnocation接口
@Service("conditionalOnSingleCandidateUserAnnocation") public class ConditionalOnSingleCandidateUserAnnocation implements IConditionalOnSingleCandidateUserAnnocation { }
- 创建测试类ConditionalOnSingleCandidateUserAnnocation1也实现IConditionalOnSingleCandidateUserAnnocation接口。
@Service("conditionalOnSingleCandidateUserAnnocation1") public class ConditionalOnSingleCandidateUserAnnocation1 implements IConditionalOnSingleCandidateUserAnnocation { }
- 修改测试类ConditionalOnSingleCandidateUser的注解ConditionalOnSingleCandidate属性。
@Service @ConditionalOnSingleCandidate(IConditionalOnSingleCandidateUserAnnocation.class) public class ConditionalOnSingleCandidateUser { }
- 开始测试
从测试结果中可以看出,ConditionalOnSingleCandidateUser注入失败。不过我们以前学过,假如配置@Primary注解,可以确定容器优先依赖注入哪个类,什么意思呢?假如ConditionalOnSingleCandidateUserAnnocation类上配置了注解@Primary,而ConditionalOnSingleCandidateUserAnnocation1类上没有配置@Primary注解,在TestController中,使用
@Autowired
private IConditionalOnSingleCandidateUserAnnocation iConditionalOnSingleCandidateUserAnnocation;
则TestController的iConditionalOnSingleCandidateUserAnnocation属性注入的是ConditionalOnSingleCandidateUserAnnocation的bean。基于这种情况,我们来测试一下。我们在ConditionalOnSingleCandidateUserAnnocation类上加上@Primary注解
@Service("conditionalOnSingleCandidateUserAnnocation") @Primary public class ConditionalOnSingleCandidateUserAnnocation implements IConditionalOnSingleCandidateUserAnnocation { }
显然ConditionalOnSingleCandidateUser注入成功。
如果ConditionalOnSingleCandidateUserAnnocation1类上也配置@Primary注解。
@Service("conditionalOnSingleCandidateUserAnnocation1") @Primary public class ConditionalOnSingleCandidateUserAnnocation1 implements IConditionalOnSingleCandidateUserAnnocation { }
测试结果
容器注册ConditionalOnSingleCandidateUser的bean失败。通过上面的例子,我们得出一个结论,在Spring中
只要@ConditionalOnSingleCandidate(IConditionalOnSingleCandidateUserAnnocation.class),ConditionalOnSingleCandidate的属性IConditionalOnSingleCandidateUserAnnocation在属性依赖注入时,
@Autowired
private IConditionalOnSingleCandidateUserAnnocation iConditionalOnSingleCandidateUserAnnocation;
无法唯一确定,则被ConditionalOnSingleCandidate修饰的bean将无法注入。从上例子中,
-
当ConditionalOnSingleCandidateUserAnnocation和ConditionalOnSingleCandidateUserAnnocation1都没有配置@Primary注解时,@Autowired注入属性iConditionalOnSingleCandidateUserAnnocation无法唯一确定。因此ConditionalOnSingleCandidateUser注入失败。
-
当ConditionalOnSingleCandidateUserAnnocation和ConditionalOnSingleCandidateUserAnnocation1有且只有一个配置了@Primary注解时,@Autowired能唯一确定被Primary修饰的bean将是属性依赖注入的bean,因此ConditionalOnSingleCandidateUser注册容器成功。
-
但ConditionalOnSingleCandidateUserAnnocation和ConditionalOnSingleCandidateUserAnnocation1同时都被@Primary修饰时,Spring容器又不知道哪个被Autowired注入了,此时ConditionalOnSingleCandidateUser注册到容器中失败。
我相信此时大家对ConditionalOnSingleCandidate注解的使用己经有了深刻的理解下,下面,我们再来看另外一个注解。ConditionalOnMissingBean的使用
ConditionalOnMissingBean注解
从ConditionalOnMissingBean的字面意思理解,只要是ConditionalOnMissingBean的属性bean在容器中不存在,则当前被ConditionalOnMissingBean注解修饰的bean将注册到容器中,真是这样吗?我们通过例子来证实我们的猜测。
从类结构来说,ConditionalOnMissingBean多了一个ignored和ignoredType属性。后面再来分析ignored属性的使用
- 创建普通bean
@Service public class ConditionalOnMissingBeanAnnocation { }
- 创建测试bean ConditionalOnMissingBeanUser
@Service @ConditionalOnMissingBean(ConditionalOnMissingBeanAnnocation.class) public class ConditionalOnMissingBeanUser { }
- 开始测试
显然ConditionalOnMissingBeanUser的bean并没有注入到容器中。 - 当我们将ConditionalOnBeanAnnocation1从容器中移除
显然ConditionalOnMissingBeanUser己经注册到容器中。从测试结果中可以看出,当ConditionalOnMissingBean的属性中的bean存在于容器中,ConditionalOnMissingBeanUser 的bean将不会注册到容器中,因此被ConditionalOnMissingBean修饰的bean和ConditionalOnMissingBean的属性值的bean是存在你死我活的关系。下面再来看,假如ConditionalOnMissingBean注解中是一个属性数组时,会怎样呢? - 再次创建一个普通类 ConditionalOnMissingBeanAnnocation1,没有注册到容器中。
@Service @ConditionalOnMissingBean({ConditionalOnMissingBeanAnnocation.class,ConditionalOnMissingBeanAnnocation1.class}) public class ConditionalOnMissingBeanUser { }
测试结果
从测试结果来看,当ConditionalOnMissingBeanAnnocation和ConditionalOnMissingBeanAnnocation1都不存在于容器中,ConditionalOnMissingBeanUser注册到容器成功。
- 假如ConditionalOnMissingBeanAnnocation和ConditionalOnMissingBeanAnnocation1有一个存在于容器中,会怎样呢?在ConditionalOnMissingBeanAnnocation类上添加@Service注解
从测试结果中,可以发现ConditionalOnMissingBeanUser注入失败。因此,只要ConditionalOnMissingBean中的属性任意一个存在容器中,则被ConditionalOnMissingBean修饰的bean将不会被注册到容器中。
接下来,我们来看看其另外一个属性ignored,这个属性有什么用呢?不就是忽略嘛,忽略掉ConditionalOnMissingBean中的value属性。那怎样测试呢?
@Service @ConditionalOnMissingBean(value={ConditionalOnMissingBeanAnnocation.class,ConditionalOnMissingBeanAnnocation1.class}, ignored = ConditionalOnMissingBeanAnnocation.class) public class ConditionalOnMissingBeanUser { }
ConditionalOnMissingBeanAnnocation不会被注册到容器中,但ConditionalOnMissingBeanAnnocation1会被注册到容器中,ignored中配置了ConditionalOnMissingBeanAnnocation属性。
测试结果
ConditionalOnMissingBeanUser注册到容器成功。
我们测试了那么多例子,那关于ConditionalOnSingleCandidate,ConditionalOnMissingBean,ConditionalOnBean的源码又是如何实现的呢?
根据之前ConditionalOnClass源码解析经验,最终判断是否注册到容器中,是由
注解ConditionalOnClass上的Conditional配置的类确定。因此,我们进入OnBeanCondition类。正如所料。
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ConditionMessage matchMessage = ConditionMessage.empty(); //如果bean配置了ConditionalOnBean注解 if (metadata.isAnnotated(ConditionalOnBean.class.getName())) { BeanSearchSpec spec = new BeanSearchSpec(context, metadata, ConditionalOnBean.class); //根据ConditionalOnBean配置的属性,从容器中获取beanNames List<String> matching = getMatchingBeans(context, spec); //如果容器中只要存在一个bean,则被注解修饰的bean就不会被注册到容器中 if (matching.isEmpty()) { return ConditionOutcome.noMatch( ConditionMessage.forCondition(ConditionalOnBean.class, spec) .didNotFind("any beans").atAll()); } matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec) .found("bean", "beans").items(Style.QUOTE, matching); } //如果bean配置了ConditionalOnSingleCandidate注解 if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) { BeanSearchSpec spec = new SingleCandidateBeanSearchSpec(context, metadata, ConditionalOnSingleCandidate.class); //根据ConditionalOnSingleCandidate配置的属性,从容器中获取beanNames List<String> matching = getMatchingBeans(context, spec); //如果容器中一个bean都不存在,则被ConditionalOnSingleCandidate注解修饰的bean将不会注册到容器中 if (matching.isEmpty()) { return ConditionOutcome.noMatch(ConditionMessage .forCondition(ConditionalOnSingleCandidate.class, spec) .didNotFind("any beans").atAll()); } //如果一个bean在容器中并不是唯一存在,同时也没有Primary注解修饰, //则被ConditionalOnSingleCandidate修饰的bean不注册到容器中 else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matching, spec.getStrategy() == SearchStrategy.ALL)) { return ConditionOutcome.noMatch(ConditionMessage .forCondition(ConditionalOnSingleCandidate.class, spec) .didNotFind("a primary bean from beans") .items(Style.QUOTE, matching)); } matchMessage = matchMessage .andCondition(ConditionalOnSingleCandidate.class, spec) .found("a primary bean from beans").items(Style.QUOTE, matching); } //如果bean配置了ConditionalOnMissingBean注解 if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) { BeanSearchSpec spec = new BeanSearchSpec(context, metadata, ConditionalOnMissingBean.class); //根据ConditionalOnMissingBean配置的属性,从容器中获取beanNames List<String> matching = getMatchingBeans(context, spec); //只要有bean存在,则被注解ConditionalOnMissingBean修饰的bean将不会注册到容器中 if (!matching.isEmpty()) { return ConditionOutcome.noMatch(ConditionMessage .forCondition(ConditionalOnMissingBean.class, spec) .found("bean", "beans").items(Style.QUOTE, matching)); } matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec) .didNotFind("any beans").atAll(); } return ConditionOutcome.match(matchMessage); }
我相信理解了ConditionalOnMissingBean,ConditionalOnSingleCandidate,及ConditionalOnBean的使用之后,再来看源码,我相信很容易理解了,但是上述代码中需要注意一些方法,先来看getMatchingBeans方法
private List<String> getMatchingBeans(ConditionContext context, BeanSearchSpec beans) { ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); //如果查找模式是parent或者祖先模式,则从父容器开始查起 if (beans.getStrategy() == SearchStrategy.PARENTS || beans.getStrategy() == SearchStrategy.ANCESTORS) { BeanFactory parent = beanFactory.getParentBeanFactory(); Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent, "Unable to use SearchStrategy.PARENTS"); beanFactory = (ConfigurableListableBeanFactory) parent; } //如果父容器为空,直接返回空集合 if (beanFactory == null) { return Collections.emptyList(); } List<String> beanNames = new ArrayList<String>(); //如果查找模式只是在当前容器中查找,则不会到父容器中去查找 boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT; //根据类型查找 for (String type : beans.getTypes()) { beanNames.addAll(getBeanNamesForType(beanFactory, type, context.getClassLoader(), considerHierarchy)); } //移除掉忽略的bean for (String ignoredType : beans.getIgnoredTypes()) { beanNames.removeAll(getBeanNamesForType(beanFactory, ignoredType, context.getClassLoader(), considerHierarchy)); } //注解上配置的注解是否在容器中的其他bean上配置了该注解,如果配置了,将bean名称加入到beanNames中 for (String annotation : beans.getAnnotations()) { beanNames.addAll(Arrays.asList(getBeanNamesForAnnotation(beanFactory, annotation, context.getClassLoader(), considerHierarchy))); } //根据名称从容器中查找 for (String beanName : beans.getNames()) { if (containsBean(beanFactory, beanName, considerHierarchy)) { beanNames.add(beanName); } } return beanNames; }
从上面示例中,我们知道ConditionalOnSingleCandidate注解修饰的类,如果在Spring容器中,不能唯一确定,被修饰的类不会注册到容器中,其实现源码如下。
private boolean hasSingleAutowireCandidate( ConfigurableListableBeanFactory beanFactory, List<String> beanNames, boolean considerHierarchy) { //如果只有一个bean或者存在多个bean,但是只有其中一个bean被@Primary注解修饰,则返回true return (beanNames.size() == 1 || getPrimaryBeans(beanFactory, beanNames, considerHierarchy) .size() == 1); } private List<String> getPrimaryBeans(ConfigurableListableBeanFactory beanFactory, List<String> beanNames, boolean considerHierarchy) { List<String> primaryBeans = new ArrayList<String>(); for (String beanName : beanNames) { BeanDefinition beanDefinition = findBeanDefinition(beanFactory, beanName, considerHierarchy); if (beanDefinition != null && beanDefinition.isPrimary()) { primaryBeans.add(beanName); } } return primaryBeans; }
上述情况,还有一段源码需要注意一下,就是属性的types,names等属性的封装这一块。
BeanSearchSpec(ConditionContext context, AnnotatedTypeMetadata metadata, Class<?> annotationType) { this.annotationType = annotationType; MultiValueMap<String, Object> attributes = metadata .getAllAnnotationAttributes(annotationType.getName(), true); collect(attributes, "name", this.names); //注解中value和type配置的属性都放到了types属性中 collect(attributes, "value", this.types); collect(attributes, "type", this.types); collect(attributes, "annotation", this.annotations); collect(attributes, "ignored", this.ignoredTypes); collect(attributes, "ignoredType", this.ignoredTypes); //获取从容器中查找策略 this.strategy = (SearchStrategy) metadata .getAnnotationAttributes(annotationType.getName()).get("search"); BeanTypeDeductionException deductionException = null; try { if (this.types.isEmpty() && this.names.isEmpty()) { //当配置的属性types和names为空时,通过addDeducedBeanType获取types addDeducedBeanType(context, metadata, this.types); } } catch (BeanTypeDeductionException ex) { deductionException = ex; } validate(deductionException); }
上述代码其他的还好理解,无非将注解配置的属性value数组,type数组等封装到相应的属性中,但是有一种情况难以理解,那就是当我们在注解中什么都不配置时,这个时候需要通过addDeducedBeanType方法来获取types参数。那我们来看看一个例子。
@Configuration public class ConditionalOnMissingBeanConfig { @Bean @ConditionalOnMissingBean public ConditionalOnMissingBeanDo conditionalOnMissingBeanDo(){ System.out.println("第一次实例化ConditionalOnMissingBeanDo"); return new ConditionalOnMissingBeanDo(); } class ConditionalOnMissingBeanDo{ } }
显然己经进来了,而创建ConditionalOnMissingBeanDo的bean上配置了ConditionalOnMissingBean注解。
测试结果
private void addDeducedBeanType(ConditionContext context, AnnotatedTypeMetadata metadata, final List<String> beanTypes) { if (metadata instanceof MethodMetadata && metadata.isAnnotated(Bean.class.getName())) { addDeducedBeanTypeForBeanMethod(context, (MethodMetadata) metadata, beanTypes); } } private void addDeducedBeanTypeForBeanMethod(ConditionContext context, MethodMetadata metadata, final List<String> beanTypes) { try { Class<?> returnType = ClassUtils.forName(metadata.getReturnTypeName(), context.getClassLoader()); beanTypes.add(returnType.getName()); } catch (Throwable ex) { throw new BeanTypeDeductionException(metadata.getDeclaringClassName(), metadata.getMethodName(), ex); } }
我相信看了例子以后,再来理解上述源码就简单得多了,当注解配置在方法上,并且没有设置注解属性时,则取方法的返回值类型作为注解属性的types的值。那这么设置的应用场景是什么呢?我们接着上面的例子继续扩展。
当两个方法都没有配置ConditionalOnMissingBean注解时,此时Spring容器实例化了两个ConditionalOnMissingBeanDo bean到容器中,当方法上配置了ConditionalOnMissingBean注解时。
容器只实例化了一次ConditionalOnMissingBeanDo bean对象。有人在想,这种有没有应用场景呢?我们来看一下mybatis中SqlSessionFactory对这个注解的使用。
对于ConditionalOnBean,ConditionalOnSingleCandidate,ConditionalOnMissingBean注解的使用及源码解析,下面,我们来看另外一个注解EnableConfigurationProperties的使用及源码解析。
EnableConfigurationProperties注解
关于EnableConfigurationProperties注解怎样使用呢?这个我也不知道,但是我们发现MybatisAutoConfiguration也这样使用,那么我们也学着这么用。
@org.springframework.context.annotation.Configuration @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }) @ConditionalOnBean(DataSource.class) @EnableConfigurationProperties(MybatisProperties.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class) public class MybatisAutoConfiguration { ... }
- 创建配置类
@ConfigurationProperties(prefix = "xxx.test") public class EnableConfigurationPropertiesTest { private String username; private String password; ... 省略 }
- 在配置文件中配置相关属性
- 创建测试类
@Service @EnableConfigurationProperties(EnableConfigurationPropertiesTest.class) public class EnableConfigurationPropertiesBean { }
- 开始测试
@Autowired private EnableConfigurationPropertiesTest enableConfigurationPropertiesTest; @RequestMapping("enableConfigurationPropertiesTest") public String enableConfigurationPropertiesTest() { System.out.println(JSON.toJSONString(enableConfigurationPropertiesTest)); return "Sucess"; }
-
测试结果
-
当我们注释掉EnableConfigurationPropertiesBean的EnableConfigurationProperties注解时
项目启动报错。这个注解的使用非常广泛,比如Spring Boot中的redis,rabbitmq,mysql等,都是通过这个注解的使用来对参数初始化的。
既然这么好用,那么实现原理是什么呢?先来看看EnableConfigurationProperties的源码。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(EnableConfigurationPropertiesImportSelector.class) public @interface EnableConfigurationProperties { Class<?>[] value() default {}; }
上述代码中有一个Import注解里配置了EnableConfigurationPropertiesImportSelector,这个属性很重要,后面再来看在源码中的使用。
先通过全局搜索,看EnableConfigurationProperties.class在哪里使用,果不其然,在EnableConfigurationPropertiesImportSelector中用到了
那么我们在EnableConfigurationPropertiesImportSelector的selectImports中打一个断点。看调用栈中,从哪里开始调用,下面的代码就是selectImports方法的入口代码。对于invokeBeanFactoryPostProcessors(beanFactory);方法调用,我们再熟悉不过了,就是激活各种BeanFactory处理器,那么在其内部如何实现呢?
我们中转到我们和EnableConfigurationProperties注解相关的代码。
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { // Recursively process any member (nested) classes first processMemberClasses(configClass, sourceClass); // Process any @PropertySource annotations for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { processPropertySource(propertySource); } else { logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } } // Process any @ComponentScan annotations Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { // The config class is annotated with @ComponentScan -> perform the scan immediately Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // Check the set of scanned definitions for any further config classes and parse recursively if needed for (BeanDefinitionHolder holder : scannedBeanDefinitions) { if (ConfigurationClassUtils.checkConfigurationClassCandidate( holder.getBeanDefinition(), this.metadataReaderFactory)) { parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName()); } } } } // Process any @Import annotations processImports(configClass, sourceClass, getImports(sourceClass), true); // Process any @ImportResource annotations if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) { AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); String[] resources = importResource.getStringArray("locations"); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } // Process individual @Bean methods Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // Process default methods on interfaces processInterfaces(configClass, sourceClass); // Process superclass, if any if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); // Superclass found, return its annotation metadata and recurse return sourceClass.getSuperClass(); } } // No superclass -> processing is complete return null; }
上面中每一行代码都很重要,但是我们只关心processImports方法,而processImports这一行方法中,有一个非常关键的方法调用。getImports(sourceClass)调用,那么这个方法调用的返回值是下一个方法运行不可缺少的参数。那我们看看这个方法的实现
private Set getImports(SourceClass sourceClass) throws IOException { Set imports = new LinkedHashSet(); Set visited = new LinkedHashSet(); collectImports(sourceClass, imports, visited); return imports; } private void collectImports(SourceClass sourceClass, Set imports, Set visited) throws IOException { if (visited.add(sourceClass)) { for (SourceClass annotation : sourceClass.getAnnotations()) { String annName = annotation.getMetadata().getClassName(); if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) { //递归调用collectImports方法 collectImports(annotation, imports, visited); } } imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value")); } }
getImports方法的意图是什么呢?我相信仔细看一下代码就明白,无非就是获取Bean上的注解的注解中是否配置了Import注解,如果配置了,则获取Import的value值加入到imports中。因此,EnableConfigurationProperties注解上配置了Import注解,并且属性值为EnableConfigurationPropertiesImportSelector。从getImport方法中,得知importCandidates的值是EnableConfigurationPropertiesImportSelector对象。
下面来看processImports方法到底做了哪些事情呢?
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, boolean checkForCircularImports) { if (importCandidates.isEmpty()) { return; } if (checkForCircularImports && isChainedImportOnStack(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } else { this.importStack.push(configClass); try { for (SourceClass candidate : importCandidates) { if (candidate.isAssignable(ImportSelector.class)) { // Candidate class is an ImportSelector -> delegate to it to determine imports Class<?> candidateClass = candidate.loadClass(); //实例化EnableConfigurationPropertiesImportSelector对象 ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class); ParserStrategyUtils.invokeAwareMethods( selector, this.environment, this.resourceLoader, this.registry); if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) { this.deferredImportSelectors.add( new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector)); } else { //调用EnableConfigurationPropertiesImportSelector的selectImports方法 String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames); processImports(configClass, currentSourceClass, importSourceClasses, false); } } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { // Candidate class is an ImportBeanDefinitionRegistrar -> // delegate to it to register additional bean definitions Class<?> candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class); ParserStrategyUtils.invokeAwareMethods( registrar, this.environment, this.resourceLoader, this.registry); configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata()); } else { // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> // process it as an @Configuration class this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); processConfigurationClass(candidate.asConfigClass(configClass)); } } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", ex); } finally { this.importStack.pop(); } } }
上述代码中,我们先来看EnableConfigurationPropertiesImportSelector的selectImports方法具体实现。
public String[] selectImports(AnnotationMetadata metadata) { MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes( EnableConfigurationProperties.class.getName(), false); Object[] type = attributes == null ? null : (Object[]) attributes.getFirst("value"); if (type == null || type.length == 0) { return new String[] { ConfigurationPropertiesBindingPostProcessorRegistrar.class .getName() }; } //如果bean的EnableConfigurationProperties注解上配置有值 return new String[] { ConfigurationPropertiesBeanRegistrar.class.getName(), ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() }; }
selectImports的方法实现很简单,如果bean的EnableConfigurationProperties注解上配置有值,则返回{ ConfigurationPropertiesBeanRegistrar.class.getName(),
ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };数组。
接下来,我们继续看,返回了ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar的类名称后,又做哪些事情呢?我们看到,当调用selectImports获得返回值后,将返回值名称实例化成bean集合,之后继续递归调用processImports方法,最终将调用addImportBeanDefinitionRegistrar方法将ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar的实例以key的形式存储到importBeanDefinitionRegistrars Map中。
public void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) { this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata); }
这个有什么用,我们也先记着。后面再来获取这个值。我们既然获取了ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar对象,那在这个对象中打一个断点看看。
从上图中,我们看到在Spring启动的某个方法中会调用以上方法,最终调用了我们的ConfigurationPropertiesBeanRegistrar的registerBeanDefinitions方法。
既然是遍历所有的bean,调用其loadBeanDefinitionsForConfigurationClass方法。我们进入loadBeanDefinitionsForConfigurationClass方法看看。
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) { if (trackedConditionEvaluator.shouldSkip(configClass)) { String beanName = configClass.getBeanName(); if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) { this.registry.removeBeanDefinition(beanName); } this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName()); return; } if (configClass.isImported()) { registerBeanDefinitionForImportedConfigurationClass(configClass); } for (BeanMethod beanMethod : configClass.getBeanMethods()) { loadBeanDefinitionsForBeanMethod(beanMethod); } loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); }
看到上面加粗的代码没有,上面加粗的代码中getImportBeanDefinitionRegistrars不就是我们之前千辛万苦分析出来的importBeanDefinitionRegistrars的值嘛。而这个值,我们之前分析是通过调用EnableConfigurationPropertiesImportSelector的selectImports方法返回的ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar类对象。我们继续跟进loadBeanDefinitionsFromRegistrars方法。
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) { for (Map.Entry<ImportBeanDefinitionRegistrar, AnnotationMetadata> entry : registrars.entrySet()) { entry.getKey().registerBeanDefinitions(entry.getValue(), this.registry); } }
loadBeanDefinitionsFromRegistrars的实现很简单,无非遍历map,调用key的registerBeanDefinitions方法,也就是调用ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar对象的registerBeanDefinitions方法。我们进入到ConfigurationPropertiesBeanRegistrar的registerBeanDefinitions方法中。
public static class ConfigurationPropertiesBeanRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { MultiValueMap<String, Object> attributes = metadata .getAllAnnotationAttributes( EnableConfigurationProperties.class.getName(), false); List<Class<?>> types = collectClasses(attributes.get("value")); for (Class<?> type : types) { String prefix = extractPrefix(type); String name = (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName()); //如果容器中不存在该beanDefinition if (!registry.containsBeanDefinition(name)) { registerBeanDefinition(registry, type, name); } } } private String extractPrefix(Class<?> type) { ConfigurationProperties annotation = AnnotationUtils.findAnnotation(type, ConfigurationProperties.class); if (annotation != null) { return annotation.prefix(); } return ""; } ... private void registerBeanDefinition(BeanDefinitionRegistry registry, Class<?> type, String name) { BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition(type); AbstractBeanDefinition beanDefinition = builder.getBeanDefinition(); registry.registerBeanDefinition(name, beanDefinition); ConfigurationProperties properties = AnnotationUtils.findAnnotation(type, ConfigurationProperties.class); Assert.notNull(properties, "No " + ConfigurationProperties.class.getSimpleName() + " annotation found on '" + type.getName() + "'."); } }
上述代码的实现逻辑也非常简单,获取到bean上配置的EnableConfigurationProperties注解的所有值,并注册beanDefinition。
Bean己经注册好了,那么Bean的值又是如何填充的呢?
在茫茫代码中去找到哪里注入,这也太难了,不知道读者有没有学到我的一招杀手锏,那就是结果索因法,比如警察找坏人,只需要到坏人家中等待,等坏人回来,直接抓住即可,但人的世界很复杂,在代码的世界,这招却是屡试不爽,因此对于这种无从查起的代码,用结果索因,无疑是最快,最好的选择。
我们在EnableConfigurationPropertiesTest的setUsername中打一个断点,立即看到了调用的堆栈信息。
从调用栈中找到,在postProcessBeforeInitialization方法中,有几行关键的代码。
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { ConfigurationProperties annotation = AnnotationUtils .findAnnotation(bean.getClass(), ConfigurationProperties.class); if (annotation != null) { postProcessBeforeInitialization(bean, beanName, annotation); } annotation = this.beans.findFactoryAnnotation(beanName, ConfigurationProperties.class); if (annotation != null) { postProcessBeforeInitialization(bean, beanName, annotation); } return bean; }
从上述方法中可以看到,只要bean在实例化时,会调用postProcessBeforeInitialization方法,当bean配置了ConfigurationProperties注解时,就会调用postProcessBeforeInitialization方法,而为什么bean的初始化过程会调用postProcessBeforeInitialization方法呢?我们来看一张图。
在bean的生命周期中,肯定会调用postProcessBeforeInitialization方法,这个就是Spring写死在代码中的,没有什么原因。在调用栈中,有一个重要的方法doBindPropertiesToTarget 绑定属性到目标对象中,接下来,我们来分析里面的关键代码。
private void doBindPropertiesToTarget() throws BindException { RelaxedDataBinder dataBinder = (this.targetName != null ? new RelaxedDataBinder(this.target, this.targetName) : new RelaxedDataBinder(this.target)); if (this.validator != null && this.validator.supports(dataBinder.getTarget().getClass())) { dataBinder.setValidator(this.validator); } if (this.conversionService != null) { dataBinder.setConversionService(this.conversionService); } dataBinder.setAutoGrowCollectionLimit(Integer.MAX_VALUE); dataBinder.setIgnoreNestedProperties(this.ignoreNestedProperties); dataBinder.setIgnoreInvalidFields(this.ignoreInvalidFields); dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields); customizeBinder(dataBinder); if (this.applicationContext != null) { ResourceEditorRegistrar resourceEditorRegistrar = new ResourceEditorRegistrar( this.applicationContext, this.applicationContext.getEnvironment()); resourceEditorRegistrar.registerCustomEditors(dataBinder); } Iterable<String> relaxedTargetNames = getRelaxedTargetNames(); Set<String> names = getNames(relaxedTargetNames); PropertyValues propertyValues = getPropertySourcesPropertyValues(names, relaxedTargetNames); dataBinder.bind(propertyValues); if (this.validator != null) { dataBinder.validate(); } checkForBindingErrors(dataBinder); }
通过getRelaxedTargetNames()方法返回了一系列的前缀,这个是什么意思呢?通过getPropertySourcesPropertyValues方法内部的代码可以得知,在application.yml中,只要配置了
xxx.test
xxx_test
xxxTest
xxxtest
XXX.TEST
XXX_TEST
XXXTEST
的任意一种,都可以属性绑定。
既然如何,我们还是来测试一把。
上述代码中有一个比较关键的方法,即是getPropertySourcesPropertyValues()方法,这个方法中获取的propertyValues,而数据绑定就是用propertyValues来绑定的。
private PropertyValues getPropertySourcesPropertyValues(Set<String> names, Iterable<String> relaxedTargetNames) { PropertyNamePatternsMatcher includes = getPropertyNamePatternsMatcher(names, relaxedTargetNames); return new PropertySourcesPropertyValues(this.propertySources, names, includes, this.resolvePlaceholders); } PropertySourcesPropertyValues(PropertySources propertySources, Collection<String> nonEnumerableFallbackNames, PropertyNamePatternsMatcher includes, boolean resolvePlaceholders) { Assert.notNull(propertySources, "PropertySources must not be null"); Assert.notNull(includes, "Includes must not be null"); this.propertySources = propertySources; this.nonEnumerableFallbackNames = nonEnumerableFallbackNames; this.includes = includes; this.resolvePlaceholders = resolvePlaceholders; PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver( propertySources); for (PropertySource<?> source : propertySources) { processPropertySource(source, resolver); } }
会遍历所有的propertySources,而propertySources的数据来源就是环境数据,命令行数据,以及我们配置的application.yml 或application-dev.yml数据,最终都会保存到propertySources中,目前在【Spring源码深度解析(郝佳)-学习-Spring Boot体系原理】这篇博客对于Spring Boot环境数据的注册做了详细分析,但是那篇博客还有一些内容依赖于这篇博客,所以,没有写完,不过后面会发出来的。既然不能看原理,那就看一下数据吧。
private void processPropertySource(PropertySource<?> source, PropertySourcesPropertyResolver resolver) { if (source instanceof CompositePropertySource) { processCompositePropertySource((CompositePropertySource) source, resolver); } else if (source instanceof EnumerablePropertySource) { processEnumerablePropertySource((EnumerablePropertySource<?>) source, resolver, this.includes); } else { processNonEnumerablePropertySource(source, resolver); } } private void processEnumerablePropertySource(EnumerablePropertySource<?> source, PropertySourcesPropertyResolver resolver, PropertyNamePatternsMatcher includes) { if (source.getPropertyNames().length > 0) { for (String propertyName : source.getPropertyNames()) { if (includes.matches(propertyName)) { Object value = getEnumerableProperty(source, resolver, propertyName); putIfAbsent(propertyName, value, source); } } } } private Object getEnumerableProperty(EnumerablePropertySource<?> source, PropertySourcesPropertyResolver resolver, String propertyName) { try { if (this.resolvePlaceholders) { return resolver.getProperty(propertyName, Object.class); } } catch (RuntimeException ex) { } return source.getProperty(propertyName); } @Override public <T> T getProperty(String key, Class<T> targetValueType) { return getProperty(key, targetValueType, true); } protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) { if (this.propertySources != null) { for (PropertySource<?> propertySource : this.propertySources) { if (logger.isTraceEnabled()) { logger.trace("Searching for key '" + key + "' in PropertySource '" + propertySource.getName() + "'"); } Object value = propertySource.getProperty(key); if (value != null) { if (resolveNestedPlaceholders && value instanceof String) { value = resolveNestedPlaceholders((String) value); } logKeyFound(key, propertySource, value); return convertValueIfNecessary(value, targetValueType); } } } if (logger.isDebugEnabled()) { logger.debug("Could not find key '" + key + "' in any property source"); } return null; }
从调用栈结果来看,最终调用了getProperty方法,返回的XXX_TEST.username的值。有了username和password的PropertyValues,最后无非是通过反射调用set方法填充属性值而已。
不过上述中有一点还是需要注意的是,关于实体中的变量名问题,变量应该怎样命名。我们将username改成USERNAME
当我们将username改成userName时
发现属性注入失败。
但是我们想要驼峰的命名规则,那该怎么办呢?我们新增一个属性homeLocation, 在配置文件中配置home-location: 湖南省,测试结果如下。显然,属性注入进去。
如果我们在配置文件中配置homeLocation属性,测试结果会怎样呢?
AutoConfigureAfter注解
接下来,我们来看看AutoConfigureAfter的使用,这个注解是什么意思呢?假如B bean中配置了@AutoConfigureAfter(A.class)注解,那么在实例化B 类之前先实例化A。既然如此,还是来测试一把,看看。
创建三个类AutoConfigureAfterA和AutoConfigureAfterB和AutoConfigureAfterC类,AutoConfigureAfterB类需要先等AutoConfigureAfterA和AutoConfigureAfterC实例化后再实例化。
@Service public class AutoConfigureAfterA { public AutoConfigureAfterA() { System.out.println("A实例化"); } } @Service public class AutoConfigureAfterC { public AutoConfigureAfterC() { System.out.println("C实例化"); } } @Service @AutoConfigureAfter({AutoConfigureAfterA.class, AutoConfigureAfterC.class}) @Import({AutoConfigureAfterA.class, AutoConfigureAfterC.class}) public class AutoConfigureAfterB { public AutoConfigureAfterB() { System.out.println("B实例化"); } }
启动项目,发现没有效果
将@Serivce注解改成@Configuration
//@Service @Configuration public class AutoConfigureAfterA { public AutoConfigureAfterA() { System.out.println("A实例化"); } } //@Service @Configuration public class AutoConfigureAfterC { public AutoConfigureAfterC() { System.out.println("C实例化"); } } //@Service @Configuration @AutoConfigureAfter({AutoConfigureAfterA.class, AutoConfigureAfterC.class}) @Import({AutoConfigureAfterA.class, AutoConfigureAfterC.class}) public class AutoConfigureAfterB { public AutoConfigureAfterB() { System.out.println("B实例化"); } }
依然没有效果
有classpath下添加META-INF,并在其下面添加spring.factories文件,文件内容为
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.springbootstudy.service.AutoConfigureAfterB
启动项目,发现神奇的效果出现了。
从测试结果来看AutoConfigureAfterA和AutoConfigureAfterC在AutoConfigureAfterB前面被先实例化。假如此时将AutoConfigureAfterA,AutoConfigureAfterC,AutoConfigureAfterB的@Configuration改成@Service注解会怎样呢?
从上面的测试中,我们得出结论,要想AutoConfigureAfterA,AutoConfigureAfterC在AutoConfigureAfterB前实例化必需满足两个条件。
- 被修饰的类必需配置@Configuration注解
- 被AutoConfigureAfter修饰的类必需在META-INF的spring.factories文件中配置org.springframework.boot.autoconfigure.EnableAutoConfiguration=被AutoConfigureAfter注解修饰的类
如果spring.factories中配置AutoConfigureAfter的属性类,如AutoConfigureAfterA和AutoConfigureAfterC,没有效果
AutoConfigureBefore注解
和AutoConfigureAfter相对应的注解是AutoConfigureBefore,那AutoConfigureBefore又该如何使用呢?我相信读者看到这里,肯定知道如何测试了,因为测试方法和AutoConfigureAfter一样。在测试之前,我们来猜测一下效果,配置了AutoConfigureBefore的类一定比AutoConfigureBefore的属性类要后实例化吗?我们的猜测是如此,那还是来测试一把。
- 创建AutoConfigure配置类,AutoConfigureBeforeA和AutoConfigureBeforeC
@Configuration public class AutoConfigureBeforeA { public AutoConfigureBeforeA() { System.out.println("BeforeA实例化"); } } @Configuration public class AutoConfigureBeforeC { public AutoConfigureBeforeC() { System.out.println("BeforeC实例化"); } }
- 创建测试类
@Configuration @AutoConfigureBefore({AutoConfigureBeforeA.class, AutoConfigureBeforeC.class}) @Import({AutoConfigureBeforeA.class, AutoConfigureBeforeC.class}) public class AutoConfigureBeforeB { public AutoConfigureBeforeB() { System.out.println("BeforeB实例化"); } }
-
META-INF目录下的配置文件spring.factories中添加
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.example.springbootstudy.service.AutoConfigureAfterB,
com.example.springbootstudy.service.AutoConfigureBeforeB -
开始测试
从测试结果中可以看出,AutoConfigureBeforeB比AutoConfigureBeforeA和AutoConfigureBeforeC后实例化。那么也就是说AutoConfigureBefore和AutoConfigureAfter的配置效果一样。我看网上有人说
@AutoConfigureBefore(AAAA.class)
public class CCCC {
}
说明 CCCC 将会在 AAAA 之前加载
从我的测试结果中来看,我觉得单纯这么说是不对的,从测试效果来看AutoConfigureBefore注解和AutoConfigureAfter注解配置效果目前是一样的。但是肯定有一定区别,那有什么区别呢?我们修改META-INF目录下的配置文件spring.factories,在文件中继续添加AutoConfigureBeforeA和AutoConfigureBeforeC类。
因此,要想AutoConfigureBeforeB在AutoConfigureBeforeA和AutoConfigureBeforeC之前实例化,必需在META-INF目录下的spring.factories配置文件中配置AutoConfigureBeforeB,AutoConfigureBeforeA和AutoConfigureBeforeC类。如果没有配置AutoConfigureBeforeA和AutoConfigureBeforeC类的话,AutoConfigureBefore注解的效果AutoConfigureAfter注解的效果一样。
那如果AutoConfigureBefore和AutoConfigureAfter结合起来使用,此时我想让AutoConfigureAfterB在AutoConfigureBeforeC后再实例化,此时在AutoConfigureAfterB的注解AutoConfigureAfter再添加属性AutoConfigureBeforeC。
@Configuration @AutoConfigureAfter({AutoConfigureAfterA.class, AutoConfigureAfterC.class,AutoConfigureBeforeC.class}) public class AutoConfigureAfterB { public AutoConfigureAfterB() { System.out.println("B实例化"); } }
测试结果
我们都测试了一遍,那么AutoConfigureBefore和AutoConfigureAfter注解源码是怎样实现的呢?对于上面奇怪的测试现像,今天花了一天的时间研究了一下,发现整个过程还是有点复杂的,我尽量写得通俗一点吧。在解读源码的过程中,有一种非常好用的方法,叫结果索因法,因为顺着找的话,可能性太多,无法通读整个Spring源码,那么我们在代码经过的地方,打个断点,根据调用栈,就能分析出源码来龙去脉了。在这里,我们依然用结果索因来分析。
首先,全局搜索AutoConfigureBefore.class在哪里用到过,发现只有AutoConfigurationSorter用到了,看getInPriorityOrder方法像是排序的方法,那我们就在这个方法中打一个断点。通过调用栈,我们发现,最终是在refreshContext()方法调用了refresh()方法。
而refresh()方法中的invokeBeanFactoryPostProcessors()方法调用了我们的getInPriorityOrder()方法。我们知道,只要Spring容器启动,则一定会调用refresh()方法。而invokeBeanFactoryPostProcessors()这个方法主要是调用容器中的各个处理器,好像上面的分析,和我们的主题没有什么关系,你先别急,AutoConfigureBefore和AutoConfigureAfter注解实现还是比较隐晦的,需要将前期工作做好,后面分析得到的结果才是水到渠成。
我们来看中途有一个方法selectImports是不是和我们之前的ConditionalOnBean注解的源码分析很像了,即使很像,但是好像还是和我们的AutoConfigureBefore,AutoConfigureAfter注解没有太大关系。
既然如此,我们回头看看AutoConfigurationSorter类。
细心的读者肯定发现了类里有面AutoConfigureBefore,AutoConfigureAfter,AutoConfigureOrder这些东西,只是无从下手而已,Configure的初始化先后顺序肯定和这个类有关系,这次好你通过结果索因,不太好找问题了,但是也让我们发现的蛛丝马迹。那好,反着不行,那我们顺着来。
我们先来到ConfigurationClassPostProcessor的processConfigBeanDefinitions方法,发现其parser.parse(candidates);的candidates参数值竟然是Spring boot 项目的启动类SpringBootStudyApplication。
因为现在问题无从查起,那我们就一步步来,先将我们不懂的东西弄明白,通过地毯式的搜索,最终那些隐藏的东西都会被挖出来。那我们一步步的弄明白那些不懂的东西,先不去找我们要找的东西。
言归正传,继续问十万个为什么?
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList<BeanDefinitionHolder>(); String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) || ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) { if (logger.isDebugEnabled()) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } if (configCandidates.isEmpty()) { return; } Collections.sort(configCandidates, new Comparator<BeanDefinitionHolder>() { @Override public int compare(BeanDefinitionHolder bd1, BeanDefinitionHolder bd2) { int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()); int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition()); return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0; } }); SingletonBeanRegistry sbr = null; if (registry instanceof SingletonBeanRegistry) { sbr = (SingletonBeanRegistry) registry; if (!this.localBeanNameGeneratorSet && sbr.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) { BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR); this.componentScanBeanNameGenerator = generator; this.importBeanNameGenerator = generator; } } ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates); Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size()); do { parser.parse(candidates); parser.validate(); Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } //对配置的xml中的bean 进行解析 this.reader.loadBeanDefinitions(configClasses); ... }while(...); }
上述过程中代码一大堆,看着都晕,但是从入口中可以看到,先获取所有的beanDefinitionNames,那么我们看看beanDefinitionNames是什么呢?
我们发现有beanDefinitionNames有8个,先弄明白这8个值是哪里注册的。
随便找一个org.springframework.context.annotation.internalConfigurationAnnotationProcessor,通过全局搜索,发现是AnnotationConfigUtils的一个常量CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME。
而这个常量在AnnotationConfigUtils类的registerAnnotationConfigProcessors方法用到过。我们在registerAnnotationConfigProcessors方法用到过的地方打一个断点。
发现竟然是在createApplicationContext方法中初始化时注册下面的beanDefinition的
org.springframework.context.annotation.internalConfigurationAnnotationProcessor->ConfigurationClassPostProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor->AutowiredAnnotationBeanPostProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor->RequiredAnnotationBeanPostProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor->CommonAnnotationBeanPostProcessor
org.springframework.context.event.internalEventListenerProcessor->EventListenerMethodProcessor
org.springframework.context.event.internalEventListenerFactory->DefaultEventListenerFactory。
我们进入createApplicationContext()方法看看。
protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { contextClass = Class.forName(this.webEnvironment ? "org.springframework." + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext" : "org.springframework.context." + "annotation.AnnotationConfigApplicationContext"); } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass", ex); } } return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass); }
实例化AnnotationConfigEmbeddedWebApplicationContext还是AnnotationConfigApplicationContext取决于webEnvironment,而webEnvironment取决于当前环境中是否有{ “javax.servlet.Servlet”,
“org.springframework.web.context.ConfigurableWebApplicationContext” },如果有,则webEnvironment为true,Spring Boot 项目,当然是有Servlet的,因此最终实例化AnnotationConfigEmbeddedWebApplicationContext时,注册了internalConfigurationAnnotationProcessor… 的beanDefinitions。
接下来,我们来看springBootStudyApplication的beanDefinition是什么时候注册的。
先来看项目启动代码。
@SpringBootApplication public class SpringBootStudyApplication { public static void main(String[] args) { SpringApplication springApplication = new SpringApplication(SpringBootStudyApplication.class); Map<String, Object> defaultProperties = new HashMap<>(); defaultProperties.put("pageNum", 1); defaultProperties.put("pageSize", 20); springApplication.setDefaultProperties(defaultProperties); springApplication.setBannerMode(Banner.Mode.CONSOLE); springApplication.run(args); //SpringApplication.run(SpringBootStudyApplication.class, args); } } public SpringApplication(Object... sources) { initialize(sources); } private void initialize(Object[] sources) { if (sources != null && sources.length > 0) { this.sources.addAll(Arrays.asList(sources)); } this.webEnvironment = deduceWebEnvironment(); setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
我们知道,此时SpringApplication的sources属性是SpringBootStudyApplication.class,先留在这里。
我们在AnnotatedBeanDefinitionReader的registerBean方法中打一个断点,发现了SpringBootStudyApplication来注册beanDefintion.
根据调用栈,我们找到了load方法,在Spring boot main方法启动时,调用了
prepareContext()方法,而在prepareContext()方法中,load了this。因此Spring boot 启动时,自身也被注册到了beanDefinitions中。
按照同样的方法,我们也能找到org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory的beanDefinition是在哪里注册,并且对应的beanClass是SharedMetadataReaderFactoryBean。
通过上述分析,我们找到了8个beanDefintion的注册来源,以及对应的BeanClass,如下
org.springframework.context.annotation.internalConfigurationAnnotationProcessor->ConfigurationClassPostProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor->AutowiredAnnotationBeanPostProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor->RequiredAnnotationBeanPostProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor->CommonAnnotationBeanPostProcessor
org.springframework.context.event.internalEventListenerProcessor->EventListenerMethodProcessor
org.springframework.context.event.internalEventListenerFactory->DefaultEventListenerFactory
SpringBootStudyApplication->SpringBootStudyApplication.class
org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory->SharedMetadataReaderFactoryBean.class
得到了上述结论以后,我们再回头看processConfigBeanDefinitions方法,其中有一个checkConfigurationClassCandidate过滤方法很重要,下面我们来看看checkConfigurationClassCandidate方法的实现。
public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) { String className = beanDef.getBeanClassName(); if (className == null || beanDef.getFactoryMethodName() != null) { return false; } AnnotationMetadata metadata; if (beanDef instanceof AnnotatedBeanDefinition && className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) { metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata(); } else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) { Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass(); metadata = new StandardAnnotationMetadata(beanClass, true); } else { try { MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className); metadata = metadataReader.getAnnotationMetadata(); } catch (IOException ex) { if (logger.isDebugEnabled()) { logger.debug("Could not find class file for introspecting configuration annotations: " + className, ex); } return false; } } //如果beanClass的注解元数据或祖先注解元数据配置了Configuration注解 if (isFullConfigurationCandidate(metadata)) { beanDef.setAttribute("org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass", full); } //如果beanClass的注解元数据或祖先注解元数据有Component,ComponentScan,Import,ImportResource,或者Bean注解 else if (isLiteConfigurationCandidate(metadata)) { beanDef.setAttribute("beanDef.setAttribute("org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass", "lite"); } else { return false; } Map<String, Object> orderAttributes = metadata.getAnnotationAttributes(Order.class.getName()); if (orderAttributes != null) { beanDef.setAttribute(ORDER_ATTRIBUTE, orderAttributes.get(AnnotationUtils.VALUE)); } return true; }
从上述方法中得知,除了SpringBootStudyApplication类上配置了SpringBootApplication注解,而SpringBootApplication上又配置了SpringBootConfiguration注解,SpringBootConfiguration注解上又配置了Configuration注解。
@SpringBootApplication public class SpringBootStudyApplication { } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { }
而其他的BeanDefinition对应的BeanClass上,边注解都没有一个,那肯定经过checkConfigurationClassCandidate方法后,candidates就只剩SpringBootStudyApplication了。
我们分析了那么多,现在我们进入parse方法内部看看,看内部做了哪些事情。
public void parse(Set<BeanDefinitionHolder> configCandidates) { this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>(); for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { //Spring boot 启动类肯定是实现了AnnotatedBeanDefinition接口 if (bd instanceof AnnotatedBeanDefinition) { parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); } } //处理延迟导入的bean,后面的源码再来分析 processDeferredImportSelectors(); }
因为SpringBootStudyApplication是通过
AnnotatedGenericBeanDefinition注册的beanDefinition,而AnnotatedGenericBeanDefinition的类结构如下。
因此上面代码肯定是走parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());这一行,我们进入parse方法。
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException { processConfigurationClass(new ConfigurationClass(metadata, beanName)); } protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { //主要对配置了Conditional注解的bean是否跳过加载的判断 if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } ConfigurationClass existingClass = this.configurationClasses.get(configClass); //假如己经存在了ConfigurationClass if (existingClass != null) { if (configClass.isImported()) { if (existingClass.isImported()) { existingClass.mergeImportedBy(configClass); } return; } else { //否则发现显式 bean 定义,可能替换导入。 让我们移除旧的并使用新的。 this.configurationClasses.remove(configClass); for (Iterator it = this.knownSuperclasses.values().iterator(); it.hasNext();) { if (configClass.equals(it.next())) { it.remove(); } } } } // 递归处理配置类及其超类层次结构。 SourceClass sourceClass = asSourceClass(configClass); do { sourceClass = doProcessConfigurationClass(configClass, sourceClass); } //如果@Configuration注解所在类有父类,则此时要处理父类中的注解信息 while (sourceClass != null); this.configurationClasses.put(configClass, configClass); }
上述过程中,可能看起来有点晕,不过一定要弄清楚,configClass是下面代码的ImportByB,而importedBy属性值才是ImportByC
@Configuration protected static class ImportByB { } @Configuration @Import(ImportByB.class) protected static class ImportByC { }
这里我们需要注意的一点是,configurationClasses是一个LinkedHashMap对象,而Spring初始化bean时,越早被加入到configurationClasses集合中的对象,越先被初始化,当上面existingClass.isImported()代码,existingClass被其他的bean Import后,此时,只能mergeImportedBy操作,而不能移除configClass或者调整configClass在configurationClasses位置。接下来,我们来看doProcessConfigurationClass方法,递归调用配置类及超类的层次结构 。当调用完配置类及超类的结构后,那就将当前类加入到configurationClasses集合中,也就是说父类及配置类配置的bean要先比当前类先实例化。
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { // 首先递归处理任何成员(嵌套)类 processMemberClasses(configClass, sourceClass); //PropertySource注解处理 for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { //PropertySource注解处理 processPropertySource(propertySource); } else { logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } } //ComponentScans注解处理 Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { //根据ComponentScan注解配置的路径扫描路径下的所有class文件 Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); for (BeanDefinitionHolder holder : scannedBeanDefinitions) { if (ConfigurationClassUtils.checkConfigurationClassCandidate( holder.getBeanDefinition(), this.metadataReaderFactory)) { parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName()); } } } } //对Import注解处理 processImports(configClass, sourceClass, getImports(sourceClass), true); //对ImportResource注解处理 if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) { AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); String[] resources = importResource.getStringArray("locations"); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); for (String resource : resources) { //对resouce 的路径中的${xxx}变量的处理 String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } //对方法中配置@Bean注解的处理 Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } //处理接口中的默认方法 processInterfaces(configClass, sourceClass); //处理父类 if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); //返回父类 return sourceClass.getSuperClass(); } } return null; }
首先对类成员信息处理,这个看上去是那样的亲切,那这一行代码的意思是什么呢?先看代码,再来看示例。
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { //如果成员类中有配置了Configuration或Component,或ComponentScan或ImportResource注解,并且不是自身 for (SourceClass memberClass : sourceClass.getMemberClasses()) { if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) && !memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) { if (this.importStack.contains(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } else { this.importStack.push(configClass); try { //递归处理配置类及其超类层次结构。 processConfigurationClass(memberClass.asConfigClass(configClass)); } finally { this.importStack.pop(); } } } } } public Collection<SourceClass> getMemberClasses() throws IOException { Object sourceToProcess = this.source; if (sourceToProcess instanceof Class) { Class<?> sourceClass = (Class<?>) sourceToProcess; try { //使用反射来获取类成员信息 Class<?>[] declaredClasses = sourceClass.getDeclaredClasses(); List<SourceClass> members = new ArrayList<SourceClass>(declaredClasses.length); for (Class<?> declaredClass : declaredClasses) { members.add(asSourceClass(declaredClass)); } return members; } catch (NoClassDefFoundError err) { //getDeclaredClasses() 由于不可解析的依赖关系而失败 -> 回退到下面的 ASM //如果反射失败,则使用ASM来解析 sourceToProcess = metadataReaderFactory.getMetadataReader(sourceClass.getName()); } } // 基于 ASM 的解析 - 对于不可解析的类也是安全的 MetadataReader sourceReader = (MetadataReader) sourceToProcess; String[] memberClassNames = sourceReader.getClassMetadata().getMemberClassNames(); List<SourceClass> members = new ArrayList<SourceClass>(memberClassNames.length); for (String memberClassName : memberClassNames) { try { members.add(asSourceClass(memberClassName)); } catch (IOException ex) { // 如果它不可解析,让我们跳过它 - 我们只是在寻找候选人 if (logger.isDebugEnabled()) { logger.debug("Failed to resolve member class [" + memberClassName + "] - not considering it as a configuration class candidate"); } } } return members; }
我们来看个例子。ImportByA有内部类ImportByB和ImportByC
@Configuration public class ImportByA { @Configuration protected static class ImportByB { @Bean public ImportByBB importByBB(){ return new ImportByBB(); } protected static class ImportByBB { } } @Configuration @Import(ImportByB.class) protected static class ImportByC { } }
测试结果
从上图中可以看出,类成员信息中,我们可以得到
com.example.springbootstudy.service.ImportByA$ImportByC
com.example.springbootstudy.service.ImportByA$ImportByB,再由isConfigurationCandidate方法,判断成员类是否有Configuration或Component,或ComponentScan或Import或ImportResource注解,如果有,则调用 processConfigurationClass 递归初始化成员类及配置类。
PropertySource注解
接下来,第二步,我们先看PropertySource注解的使用,再来看源码。
- 创建配置类
@Component @PropertySource({"classpath:config/bean1.properties","classpath:config/bean.properties"}) public class PropertySourceA { }
-
创建配置文件bean.properties和bean1.properties
bean.properties文件中配置bean.message=11111111111
bean1.properties配置文件中配置bean.message=2222222222222 -
开始测试
当bean.properties和bean1.properties配置相同的文件内容,bean1.properties会覆盖bean.properties文件内容。
为什么返回的是bean.properties中配置的内容呢?修改PropertySource注解上文件名的位置,将bean1.properties文件放到前面,bean.properties文件放到后面。
最后发现,bean.properties配置覆盖了bean1.properties配置内容,所以从上面的测试中得到,PropertySource注解中配置的数组属性文件,数组后面的文件内容会覆盖掉数组前面的内容。既然看了测试用例,接下来,我们来看看源码如何实现。
private void processPropertySource(AnnotationAttributes propertySource) throws IOException { String name = propertySource.getString("name"); if (!StringUtils.hasLength(name)) { name = null; } String encoding = propertySource.getString("encoding"); if (!StringUtils.hasLength(encoding)) { encoding = null; } String[] locations = propertySource.getStringArray("value"); Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required"); boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound"); Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory"); PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ? new DefaultPropertySourceFactory() : BeanUtils.instantiateClass(factoryClass)); for (String location : locations) { try { String resolvedLocation = this.environment.resolveRequiredPlaceholders(location); Resource resource = this.resourceLoader.getResource(resolvedLocation); addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding))); } catch (IllegalArgumentException ex) { if (ignoreResourceNotFound) { if (logger.isInfoEnabled()) { logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage()); } } else { throw ex; } } catch (IOException ex) { if (ignoreResourceNotFound && (ex instanceof FileNotFoundException || ex instanceof UnknownHostException)) { if (logger.isInfoEnabled()) { logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage()); } } else { throw ex; } } } }
对于上述方法,有一个就是ignoreResourceNotFound属性,如果为true,即使文件不存在,也不会抛出异常,否则抛出异常,上述方法原理很简单,就是遍历所有的locations文件,然后将其加入到环境变量中去,而加入环境变量位置就有讲究了,请看下面代码。
private void addPropertySource(PropertySource<?> propertySource) { String name = propertySource.getName(); MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources(); if (propertySources.contains(name) && this.propertySourceNames.contains(name)) { //如果propertySources存在名字相同的resouce,那就将其封装成CompositePropertySource对象,并将新的resource放在CompositePropertySource的propertySources属性的第一个位置 PropertySource<?> existing = propertySources.get(name); PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ? ((ResourcePropertySource) propertySource).withResourceName() : propertySource); if (existing instanceof CompositePropertySource) { ((CompositePropertySource) existing).addFirstPropertySource(newSource); } else { if (existing instanceof ResourcePropertySource) { existing = ((ResourcePropertySource) existing).withResourceName(); } CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource(newSource); composite.addPropertySource(existing); propertySources.replace(name, composite); } } else { if (this.propertySourceNames.isEmpty()) { propertySources.addLast(propertySource); } else { // 下面就是加入当前propertySource到propertySources的指定位置 String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1); propertySources.addBefore(firstProcessed, propertySource); } } this.propertySourceNames.add(name); }
上面的将当前propertySource加入到propertySources的指定位置有什么用呢?在之前的博客中,Spring boot 启动时设置环境变量也说到过。Spring 在getProperty方法时,先遍历propertySources中的每一个propertySource,再从propertySource去获取property的值,只要找到了就返回,因此如果同一个变量配置在不同的propertySource中,越在propertySources集合前面的propertySource,优先被找到。
- 创建测试类
@Component @PropertySource(value = {"classpath:config/bean2.properties","classpath:config/bean1.properties","classpath:config/bean.properties"},ignoreResourceNotFound =true ) public class PropertySourceA { }
【测试结果】
从上面来看配置在PropertySource注解中的value数组属性,越在数组后面的配置文件,在propertySources的位置越靠前,因此,相同的属性bean.message,最终取到的是bean.properties中配置的内容。
关于PropertySource注解的使用,及实现原理,我相信此时大家有了深刻的理解了,下面再来看ComponentScan注解的使用。
ComponentScan注解
关于ComponentScan注解我们经常使用,SpringBootApplication注解上也有ComponentScan注解。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { ... 省略 }
从SpringBootApplication注解上配置的ComponentScan注解来看,其中我们需要注意的是配置了excludeFilters属性里的AutoConfigurationExcludeFilter过虑器。最终解析得到componentScan的AnnotationAttributes的内容如下图所示。
我们讲了这么多,至始至终都没有讲到AutoConfigureBefore,和AutoConfigureAfter相关的内容,但是不急,我们继续看,相信会看到你想关心的内容。在ComponentScan注解处理,比较重要的是下面这一行代码,
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
根据componentScan配置的扫描路径,获取所有的beanDefinition,我们进入parse方法看看。
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) { Assert.state(this.environment != null, "Environment must not be null"); Assert.state(this.resourceLoader != null, "ResourceLoader must not be null"); ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader); Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator"); boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass); scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator : BeanUtils.instantiateClass(generatorClass)); ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy"); if (scopedProxyMode != ScopedProxyMode.DEFAULT) { scanner.setScopedProxyMode(scopedProxyMode); } else { Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver"); scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass)); } scanner.setResourcePattern(componentScan.getString("resourcePattern")); for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addIncludeFilter(typeFilter); } } for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addExcludeFilter(typeFilter); } } boolean lazyInit = componentScan.getBoolean("lazyInit"); if (lazyInit) { scanner.getBeanDefinitionDefaults().setLazyInit(true); } Set<String> basePackages = new LinkedHashSet<String>(); String[] basePackagesArray = componentScan.getStringArray("basePackages"); for (String pkg : basePackagesArray) { String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg), ",; \t\n"); basePackages.addAll(Arrays.asList(tokenized)); } for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) { basePackages.add(ClassUtils.getPackageName(clazz)); } if (basePackages.isEmpty()) { basePackages.add(ClassUtils.getPackageName(declaringClass)); } scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) { @Override protected boolean matchClassName(String className) { return declaringClass.equals(className); } }); return scanner.doScan(StringUtils.toStringArray(basePackages)); }
上述代码的实现也没有过多好说明的,就是从注解中取出属性,然后设置到scanner对象中,需要注意的时,此时将SpringBootApplication配置的ComponentScan注解上的excludeFilters属性TypeExcludeFilter和AutoConfigurationExcludeFilter加入到了scanner的excludeFilters属性中。这个结论先留在这里。我们继续看scanner的doScan()方法。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); // 创建一个集合,存入扫描到的Bean 定义的封装类 Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>(); //遍历扫描所给定的包 for (String basePackage : basePackages) { // 类路径的Bean定义扫描 ClassPathBeanDefinitionScanner 主要通过 findCandidateComponents() 方法调用其父类 ClassPathScanningCandidateComponentProvider // 来扫描获取给定包及其子包的类 Set<BeanDefinition> candidates = findCandidateComponents(basePackage); // 遍历扫描得到的Bean for (BeanDefinition candidate : candidates) { // 获取Bean定义类中的@Scope注解的值,即获取Bean的作用域 ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); // 为Bean设置注解配置的作用域 candidate.setScope(scopeMetadata.getScopeName()); // 设置我们的beanName,为Bean生成名称 String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); //设置Bean的自动依赖注入装配属性等 if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } //处理jsr250相关的组件,如果扫描到的Bean是Spring的注解的Bean,则处理其通用的注解 if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } //把我们解析出来的组件bean定义注册到Spring IoC容器中,根据Bean名称检查指定的Bean是否需要在容器注册,或者是否是容器中 // 有冲突。 if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); // 根据注解中的配置的作用域,为Bean的应用的代理模式 definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); //注册到Spring IoC容器中,向容器注册扫描到的Bean registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }
上述方法是通过注解注入bean的关键代码,非常重要,我们之前的博客也讲过,但是今天我们只关心findCandidateComponents方法,找到符合条件的BeDefinitions。
public Set<BeanDefinition> findCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>(); try { String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath); boolean traceEnabled = logger.isTraceEnabled(); boolean debugEnabled = logger.isDebugEnabled(); for (Resource resource : resources) { if (traceEnabled) { logger.trace("Scanning " + resource); } if (resource.isReadable()) { try { // 为指定资源获取元数据读取器,元数据读取器通过汇编(ASM) 读取资源的元信息 MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); // 如果扫描的类符合容器配置的过滤规则 if (isCandidateComponent(metadataReader)) { // 通过汇编(ASM) 读取资源字节码中的Bean定义的元信息 ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setResource(resource); sbd.setSource(resource); if (isCandidateComponent(sbd)) { if (debugEnabled) { logger.debug("Identified candidate component class: " + resource); } candidates.add(sbd); } else { if (debugEnabled) { logger.debug("Ignored because not a concrete top-level class: " + resource); } } } else { if (traceEnabled) { logger.trace("Ignored because not matching any filter: " + resource); } } } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to read candidate component class: " + resource, ex); } } else { if (traceEnabled) { logger.trace("Ignored because not readable: " + resource); } } } } catch (IOException ex) { throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); } return candidates; }
上述代码中,本身其他的代码也非常重要,但是今天的主角是isCandidateComponent方法,而这个方法主要判断当前注解是否需创建beanDefinition,接下来,我们来看isCandidateComponent方法的内部实现。
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { for (TypeFilter tf : this.excludeFilters) { //如果被TypeFilter匹配到,则不创建beanDefinition注册到容器中 if (tf.match(metadataReader, this.metadataReaderFactory)) { return false; } } for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return isConditionMatch(metadataReader); } } return false; }
上面这个方法非常简单,但需要注意的是,excludeFilters内容是什么?对于SpringBootApplication注解而言,不就是TypeExcludeFilter.class和AutoConfigurationExcludeFilter.class嘛。最终调用了Filter的match方法判断当前类是否创建BeanDefinition,因为我们在spring.factories中配置的内容如下,
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.example.springbootstudy.service.AutoConfigureAfterB,
com.example.springbootstudy.service.AutoConfigureBeforeB,
com.example.springbootstudy.service.AutoConfigureBeforeA,
com.example.springbootstudy.service.AutoConfigureBeforeC
因此从名字就猜测EnableAutoConfiguration注解和AutoConfigurationExcludeFilter过滤器有关系。我们进入这个过滤器
public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware { private ClassLoader beanClassLoader; private volatile List<String> autoConfigurations; @Override public void setBeanClassLoader(ClassLoader beanClassLoader) { this.beanClassLoader = beanClassLoader; } @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { // 配置了Configuration注解及是 //spring.factories文件中org.springframework.boot.autoconfigure.EnableAutoConfiguration的value属性值 return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader); } private boolean isConfiguration(MetadataReader metadataReader) { return metadataReader.getAnnotationMetadata() .isAnnotated(Configuration.class.getName()); } private boolean isAutoConfiguration(MetadataReader metadataReader) { return getAutoConfigurations() .contains(metadataReader.getClassMetadata().getClassName()); } protected List<String> getAutoConfigurations() { if (this.autoConfigurations == null) { //加载当前classpath下的所有META-INF/spring.factories配置文件,中的 //org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxx,配置的内容 this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames( EnableAutoConfiguration.class, this.beanClassLoader); } return this.autoConfigurations; } }
我们分析了那么多,其实就是围绕SpringBootApplication的ComponentScan注解中的excludeFilters的class来分析,如果被excludeFilters中配置的class的match方法匹配掉,则先不加入到beanDefinition中去,这也是为什么,我们之前测试AutoConfigureAfter和AutoConfigureBefore注解时,将bean上面配置@Service注解时,注解配置无效,而必需配置了Configuration注解才生效,凡是配置在META-INF/spring.factories下,且是org.springframework.boot.autoconfigure.EnableAutoConfiguration配置类,并且配置了@Configuration注解的bean,都不会被SpringBootApplication注解扫描进去,都会延后初始化。下面来看一个现象。
从图片来看,证实了我们的结论,事实上加入到configurationClasses集合中的实例的顺序决定了bean的创建顺序,因此,因为在META-INF/spring.factories中配置EnableAutoConfiguration对象,同时也配置了Configuration注解的bean,因为没有在Spring启动时扫描资源的时候,没有及时被加入到configurationClasses集合中,所以就被延后初始化化了,而在bean的加载创建过期,我们粗略的觉得,当所有的beanDefinition初始化完成,Spring会遍历configurationClasses,一个个的创建bean,但是这些都只是粗略的认为,事实是比这个复杂得多,假如B bean 依赖于A bean, 那么在B bean在实例化的时候,可能会先创建A bean。这个时候又涉及到循环依赖问题,循环依赖又包括构造器循环依赖和get set 属性循环依赖,等等,实际上整个过程很复杂,其实之前的博客也对这些情况做了分析,这里就不再赘述。接下来我们要分析是Import注解,看看Import注解
Import注解
我们在分析Import注解前,看来看一个实例。
加上Import注解。
从测试效果中可以看出,没有配置@Import({AutoConfigureBeforeA.class, AutoConfigureBeforeC.class})时,AutoConfigureBeforeB 比AutoConfigureBeforeA和AutoConfigureBeforeC先实例化,而配置了@Import({AutoConfigureBeforeA.class, AutoConfigureBeforeC.class})注解后,AutoConfigureBeforeA和AutoConfigureBeforeC比AutoConfigureBeforeB先实例化。通过上面的测试效果,我们来理解源码。
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException { Set<SourceClass> imports = new LinkedHashSet<SourceClass>(); Set<SourceClass> visited = new LinkedHashSet<SourceClass>(); collectImports(sourceClass, imports, visited); return imports; } private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited) throws IOException { //解析过的注解不再做解析 if (visited.add(sourceClass)) { for (SourceClass annotation : sourceClass.getAnnotations()) { String annName = annotation.getMetadata().getClassName(); if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) { collectImports(annotation, imports, visited); } } imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value")); } }
上面就是获取bean的Import注解的value值,或者递归获取bean上所有非Import注解的上配置了Import注解的value值,这个是什么意思呢?来看个例子
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import({AutoConfigureBeforeA.class, AutoConfigureBeforeC.class}) public @interface MyImport { } @Configuration @AutoConfigureBefore({AutoConfigureBeforeA.class, AutoConfigureBeforeC.class}) @MyImport public class AutoConfigureBeforeB { public AutoConfigureBeforeB() { System.out.println("BeforeB实例化"); } }
显然AutoConfigureBeforeB并没有配置@Import({AutoConfigureBeforeA.class, AutoConfigureBeforeC.class}),而是MyImport注解中配置了@Import({AutoConfigureBeforeA.class, AutoConfigureBeforeC.class}),测试结果
显然在AutoConfigureBeforeB并没有直接配置Import注解,而是MyImport配置了Import注解,从而实现了AutoConfigureBeforeA和AutoConfigureBeforeC比AutoConfigureBeforeB先实例化。换句话来说, bean先找当前Bean中是否配置了Import注解,如果没有配置,则看bean的注解的注解是否配置Import注解,如果还没有,则递归的找注解的注解的注解…是否配置了Import注解,如果有,则取出其值,并返回。
前面我们只看到了Import标签对实例化顺序的影响,接下来,我们来看Import标签的另外一个特性。
public class ImportA { } @Service @Import(ImportA.class) public class ImportB { }
ImportA 上并没有配置注解,但是在ImportB上有注解@Import(ImportA.class),此时ImportA会被注入到容器中吗?看测试结果
从测试结果中,我们看到了Import注解的另外一个特性,至少我们知道了另外一种方式将bean注入到容器中。
接下来我们来看看源码是如何实现。
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, boolean checkForCircularImports) { if (importCandidates.isEmpty()) { return; } if (checkForCircularImports && isChainedImportOnStack(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } else { this.importStack.push(configClass); try { for (SourceClass candidate : importCandidates) { if (candidate.isAssignable(ImportSelector.class)) { // Candidate class is an ImportSelector -> delegate to it to determine imports Class<?> candidateClass = candidate.loadClass(); ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class); ParserStrategyUtils.invokeAwareMethods( selector, this.environment, this.resourceLoader, this.registry); if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) { this.deferredImportSelectors.add( new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector)); } else { String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames); processImports(configClass, currentSourceClass, importSourceClasses, false); } } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { // Candidate class is an ImportBeanDefinitionRegistrar -> // delegate to it to register additional bean definitions Class<?> candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class); ParserStrategyUtils.invokeAwareMethods( registrar, this.environment, this.resourceLoader, this.registry); configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata()); } else { // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> // process it as an @Configuration class this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); //递归处理Configuration Class processConfigurationClass(candidate.asConfigClass(configClass)); } } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", ex); } finally { this.importStack.pop(); } } }
需要注意的一点是,此时candidate是@Import({AutoConfigureBeforeA.class, AutoConfigureBeforeC.class}) 配置的属性AutoConfigureBeforeA和AutoConfigureBeforeC,显然一般的类和ImportSelector及ImportBeanDefinitionRegistrar类无关,因此,还是走processConfigurationClass方法。而processConfigurationClass这个方法是递归调用方法,又会去看AutoConfigureBeforeA和AutoConfigureBeforeB类中是否有成员方法配置了Bean注解,或者配置了@ComponentScan等,如果没有,则直接将AutoConfigureBeforeA和AutoConfigureBeforeB加入到configurationClasses属性中,但可确定的一点是AutoConfigureBeforeA和AutoConfigureBeforeC一定会比AutoConfigureBeforeB先加入到configurationClasses属性中,也就出现了为什么配置了Import注解后,AutoConfigureBefore注解变得无效。接下来,我们来看ImportResource注解
ImportResource注解
注解和AutoConfigureAfter及AutoConfigureBefore注解没有太大关系,但是遇到了,我们还是来分析一下吧, 在分析源码之前,我们还是先来看一个例子。
- config目录下创建配置文件spring_importsource_test.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="importResourceTestBean" class="com.example.springbootstudy.service.ImportResourceTestBean"></bean> </beans>
- 创建普通类
public class ImportResourceTestBean { }
- 创建配置类
@ImportResource("classpath:config/spring_importsource_test.xml") @Service public class ImportResourceA { }
- 创建测试方法
@RequestMapping("importResourceTestBeanTest") public String importResourceTestBeanTest() { ImportResourceTestBean importResourceTestBean = SpringContextUtils.getBean(ImportResourceTestBean.class); System.out.println(importResourceTestBean); return "Sucess"; }
- 测试结果
打印出我们配置文件中配置的bean ImportResourceTestBean ,那源码如何实现的呢?也就是doProcessConfigurationClass方法中的下面一段代码实现
if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) { AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); String[] resources = importResource.getStringArray("locations"); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } }
public void addImportedResource(String importedResource, Class<? extends BeanDefinitionReader> readerClass) { this.importedResources.put(importedResource, readerClass); }
对于addImportedResource方法,内部并没有什么复杂的逻辑,只是将ImportResource注解中配置的locations值取出并存储到importedResources属性中,而这个属性什么时候用呢?我们找到
importedResources获取的地方
public Map<String, Class<? extends BeanDefinitionReader>> getImportedResources() { return this.importedResources; }
通过idea很容易找到getImportedResources方法有且只有loadBeanDefinitionsForConfigurationClass方法调用。接下来,我们来看loadBeanDefinitionsForConfigurationClass方法。
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) { if (trackedConditionEvaluator.shouldSkip(configClass)) { String beanName = configClass.getBeanName(); if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) { this.registry.removeBeanDefinition(beanName); } this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName()); return; } if (configClass.isImported()) { registerBeanDefinitionForImportedConfigurationClass(configClass); } for (BeanMethod beanMethod : configClass.getBeanMethods()) { loadBeanDefinitionsForBeanMethod(beanMethod); } //解析所有配置的xml文件,注册为beanDefinition loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); }
先不急着看xml解析的实现,带着疑问,我们找loadBeanDefinitionsForConfigurationClass这个方法是在哪里调用呢?发现最终来自于processConfigBeanDefinitions方法在parser.parse(candidates);之后调用了loadBeanDefinitions方法。
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) { TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator(); for (ConfigurationClass configClass : configurationModel) { loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator); } }
接下来,我们来看看loadBeanDefinitionsFromImportedResources方法的内部实现。
private void loadBeanDefinitionsFromImportedResources( Map<String, Class<? extends BeanDefinitionReader>> importedResources) { Map<Class<?>, BeanDefinitionReader> readerInstanceCache = new HashMap<Class<?>, BeanDefinitionReader>(); for (Map.Entry<String, Class<? extends BeanDefinitionReader>> entry : importedResources.entrySet()) { String resource = entry.getKey(); Class<? extends BeanDefinitionReader> readerClass = entry.getValue(); //获取resource的读取器 if (BeanDefinitionReader.class == readerClass) { if (StringUtils.endsWithIgnoreCase(resource, ".groovy")) { //如果文件后缀是groovy,使用GroovyBeanDefinitionReader解析器 readerClass = GroovyBeanDefinitionReader.class; } else { //使用xml文件解析器 readerClass = XmlBeanDefinitionReader.class; } } BeanDefinitionReader reader = readerInstanceCache.get(readerClass); //使用缓存提高性能 if (reader == null) { try { reader = readerClass.getConstructor(BeanDefinitionRegistry.class).newInstance(this.registry); if (reader instanceof AbstractBeanDefinitionReader) { AbstractBeanDefinitionReader abdr = ((AbstractBeanDefinitionReader) reader); abdr.setResourceLoader(this.resourceLoader); abdr.setEnvironment(this.environment); } readerInstanceCache.put(readerClass, reader); } catch (Throwable ex) { throw new IllegalStateException( "Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]"); } } //加载xml中配置的bean,解析成beanDefinition并注册到容器中 reader.loadBeanDefinitions(resource); } }
对于source的处理也是非常简单的,根据后缀名找到resource的解析器,然后直接读取,只是为了提高性能,可能使用了缓存。而loadBeanDefinitions这个是Spring容器的基础,bean从配置文件中加载,之前很多的博客都是围绕着这个代码来分析,这里也不再赘述。到这里,我们对ImportResource注解的使用及源码实现告一段落。接下来,我们来分析@Bean注解的使用及源码
Bean注解
关于Bean注解注解的使用,我相信基本上没有人不会使用吧,这里就不举例了。直接看源码
private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass) { AnnotationMetadata original = sourceClass.getMetadata(); Set<MethodMetadata> beanMethods = original.getAnnotatedMethods(Bean.class.getName()); if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) { try { // 尝试通过 ASM 读取类文件以获得确定性声明顺序... // 不幸的是,JVM 的标准反射以任意顺序 // 返回方法,即使在同一 JVM 上同一应用程序的不同运行之间也是如此。 AnnotationMetadata asm = this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata(); Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName()); if (asmMethods.size() >= beanMethods.size()) { Set<MethodMetadata> selectedMethods = new LinkedHashSet<MethodMetadata>(asmMethods.size()); for (MethodMetadata asmMethod : asmMethods) { for (MethodMetadata beanMethod : beanMethods) { if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) { selectedMethods.add(beanMethod); break; } } } if (selectedMethods.size() == beanMethods.size()) { // All reflection-detected methods found in ASM method set -> proceed beanMethods = selectedMethods; } } } catch (IOException ex) { logger.debug("Failed to read class file via ASM for determining @Bean method order", ex); // No worries, let's continue with the reflection metadata we started with... } } return beanMethods; }
关于StandardAnnotationMetadata的使用,也是非常简单
public static void main(String[] args) throws IOException { AnnotationMetadata reflectReader = new StandardAnnotationMetadata(BeanConfig.class); System.out.println(reflectReader.getAnnotationTypes()); }
网上有关StandardAnnotationMetadata的解释是
SimpleAnnotationMetadataReadingVisitor与StandardAnnotationMetadata的主要区别在于,SimpleAnnotationMetadataReadingVisitor是基于asm的实现,StandardAnnotationMetadata是基于反射的实现,那我们在使用时,应该要怎么选呢?
由于基于反射是要先加类加载到jvm中的,因此我的判断是,如果当前类没有加载到jvm中,就使用SimpleAnnotationMetadataReadingVisitor,如果类已经加载到jvm中了,两者皆可使用。
事实上,在spring包扫描阶段,读取类上的注解时,使用的都是SimpleAnnotationMetadataReadingVisitor,因为此时类并没有加载到jvm,如果使用StandardAnnotationMetadata读取,就会导致类提前加载。类提前加载有什么问题呢?java类是按需加载的,有的类可能在整个jvm生命周期内都没用到,如果全都加载了,就白白浪费内存了。
【总结】
本文介绍了 AnnotationMetadata两种实现方案,一种基于 Java 反射,另一种基于 ASM 框架。
两种实现方案适用于不同场景。StandardAnnotationMetadata 基于 Java 反射,需要加载类文件。而 AnnotationMetadataReadingVisitor基于 ASM 框架无需提前加载类,所以适用于 Spring 应用扫描指定范围内模式注解时使用。
因此Spring默认是用SimpleAnnotationMetadataReadingVisitor来获取bean的信息的。因此就有bean加载的无序性。那我们来看一个示例。
@Configuration public class BeanConfig { @Bean public BeanB beanB(){ return new BeanB(); } @Bean public BeanA beanA(){ return new BeanA(); } }
@Configuration public class BeanConfig { @Bean public BeanA beanA(){ return new BeanA(); } @Bean public BeanB beanB(){ return new BeanB(); } }
因此,有个时候,我们用Configuration配置bean时,Bean在代码中写的顺序影响到Bean实例化的顺序。目前我也没有找到好的办法解决这个问题。
接下来,我们继续看如何处理接口中定义的默认方法,并且方法配置了bean。在看源码之前 ,还是先来看一个示例。
public class ProcessInterfaceB { } public class ProcessInterfaceC { } public interface ProcessInterfaceA { @Bean default ProcessInterfaceB processInterfaceB() { return new ProcessInterfaceB(); } } @Configuration public interface ProcessInterfaceAA extends ProcessInterfaceA { @Bean default ProcessInterfaceC processInterfaceC() { return new ProcessInterfaceC(); } } @RequestMapping("processInterfaceCTest") public String processInterfaceCTest() { ProcessInterfaceB processInterfaceB = SpringContextUtils.getBean(ProcessInterfaceB.class); ProcessInterfaceC processInterfaceC = SpringContextUtils.getBean(ProcessInterfaceC.class); System.out.println(processInterfaceB); System.out.println(processInterfaceC); return "Sucess"; }
首先ProcessInterfaceB和ProcessInterfaceC是普通方法。ProcessInterfaceA和ProcessInterfaceAA是接口,分别在ProcessInterfaceA和ProcessInterfaceAA接口中创建了默认方法processInterfaceB和processInterfaceC,并且每个方法上都配置了Bean注解。同时在ProcessInterfaceAA类中配置Configuration注解。开始测试
容器中并没有注入ProcessInterfaceB和ProcessInterfaceC,是不是我们配置不对呢?我们修改一下,去掉ProcessInterfaceAA上的注解Configuration,添加新类ProcessInterfaceConfiguration并配置Configuration注解,并实现ProcessInterfaceAA接口。
@Configuration public class ProcessInterfaceConfiguration implements ProcessInterfaceAA { }
测试结果
我们发现一个神奇的现象,就是ProcessInterfaceA接口中的默认方法ProcessInterfaceB也被实例化。接口中的接口的方法bean方法也被实例化了,聪明的读者肯定会想,Spring又用了递归,真是如此吗?我们来看看源码。
private void processInterfaces(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { for (SourceClass ifc : sourceClass.getInterfaces()) { Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(ifc); for (MethodMetadata methodMetadata : beanMethods) { if (!methodMetadata.isAbstract()) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } } processInterfaces(configClass, ifc); } }
从源码上来看,是获取sourceClass类的所有接口,递归调用接口的接口中是否配置了@Bean注解的默认方法,如果有,则加入到BeanDefinition中。接下来看Configuration注解所配置类的父类,又是如何处理呢?
public class SuperClassConfigA { } public class SuperClassConfigParent { @Bean public SuperClassConfigA superClassConfigA(){ return new SuperClassConfigA(); } } @Configuration public class SuperClassConfig extends SuperClassConfigParent{ } @RequestMapping("superClassConfigTest") public String superClassConfigTest() { SuperClassConfigA superClassConfigA = SpringContextUtils.getBean(SuperClassConfigA.class); System.out.println(superClassConfigA); return "Sucess"; }
创建了普通类SuperClassConfigA,创建普通类SuperClassConfigParent,但是其内部创建了superClassConfigA()方法,注册SuperClassConfigA的bean,创建SuperClassConfig类继承SuperClassConfigParent,并配置了Configuration注解。源码很简单。
//如果配置的Configuration注解的类有父类 if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { //避免重复实例化 this.knownSuperclasses.put(superclass, configClass); //返回父类 return sourceClass.getSuperClass(); } } //如果返回的父类不为空,则再次递归调用父类的doProcessConfigurationClass方法 SourceClass sourceClass = asSourceClass(configClass); do { sourceClass = doProcessConfigurationClass(configClass, sourceClass); } while (sourceClass != null);
上述代码需要注意的一点是knownSuperclasses的使用,因为SuperClassConfigParent可能被其他的类也继承,但是为了保证SuperClassConfigA只被注册一次,因此需要knownSuperclasses来缓存己经被扫描的父类,还是看个例子吧。
@Configuration public class SuperClassConfig extends SuperClassConfigParent{ } @Configuration public class SuperClassConfig1 extends SuperClassConfigParent{ }
SuperClassConfig和SuperClassConfig1都继承SuperClassConfigParent,而SuperClassConfig只被实例化一次,打个断点看看。
我们分析了整个Configuration注解的扫描过程,但是到目前为止,只知道了AutoConfigureBefore和AutoConfigureAfter注解因为配置到META-INF/spring.factories的org.springframework.boot.autoconfigure.EnableAutoConfiguration属性中被延后实例化。AutoConfigureBefore和AutoConfigureAfter注解配置引起的实例化顺序问题,目前还没有看到在哪里实现。但是我们分析明白了一些问题,就是配置@Service,@Component注解,这些注解再配置AutoConfigureBefore无效,AutoConfigureBefore只对@Configuration注解有效。而且Configuration注解配置的bean还必需配置在META-INF/spring.factories的org.springframework.boot.autoconfigure.EnableAutoConfiguration属性中,否则也是无效,那么现在就基于bean配置了Configuration注解,同时bean也配置到org.springframework.boot.autoconfigure.EnableAutoConfiguration属性中来分析。不知道细心的读者有没有注意到processDeferredImportSelectors这个方法。Deferred单词是延迟的意思,是不是在这个方法中呢?我们进入这个方法。
private void processDeferredImportSelectors() { List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors; this.deferredImportSelectors = null; Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR); for (DeferredImportSelectorHolder deferredImport : deferredImports) { ConfigurationClass configClass = deferredImport.getConfigurationClass(); try { String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata()); processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", ex); } } }
从测试结果来看,我们知道了deferredImport.getImportSelector()就是EnableAutoConfigurationImportSelector,并且来源于deferredImportSelectors属性。那EnableAutoConfigurationImportSelector又是在何时注入的呢?为了寻找答案,我们不妨再回头看SpringBootApplication注解。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { }
在SpringBootApplication注解中,我们发现了另外一个注解EnableAutoConfiguration,这个注解和我们的目标EnableAutoConfigurationImportSelector很像,那再来看看EnableAutoConfiguration注解。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(EnableAutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { }
在这个注解中,我们看到了EnableAutoConfigurationImportSelector类被Import了,根据之前Import注解的特性,EnableAutoConfigurationImportSelector肯定会被注册到容器中。那何时加入到deferredImportSelectors属性中的呢?我们再回到processImports的源码来看,其中candidate.isAssignable(ImportSelector.class)后面几行代码就是处理ImportSelector及其子类的。如下图所示
既然我们知道了deferredImport.getImportSelector()返回的是EnableAutoConfigurationImportSelector对象,那么我们来看看selectImports内部是如何实现
public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } try { AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata); //加载org.springframework.boot.autoconfigure.EnableAutoConfiguration内容 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); //去重 configurations = removeDuplicates(configurations); //排序 configurations = sort(configurations, autoConfigurationMetadata); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); //过虑掉配置在spring.factories文件中的类,但是当前编译环境中并不存在的类 configurations = filter(configurations, autoConfigurationMetadata); //发送配置类导入的事件消息 fireAutoConfigurationImportEvents(configurations, exclusions); return configurations.toArray(new String[configurations.size()]); } catch (IOException ex) { throw new IllegalStateException(ex); } } protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { //加载META-INF/spring.factories下的 //org.springframework.boot.autoconfigure.EnableAutoConfiguration内容 List<String> configurations = SpringFactoriesLoader.loadFactoryNames( getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; }
我们千辛万苦终于看到了sort方法,这也是AutoConfigureBefore和AutoConfigureAfter注解排序的关键代码。我们进入这个方法看看。
private List<String> sort(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) throws IOException { configurations = new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata).getInPriorityOrder(configurations); return configurations; } AutoConfigurationSorter(MetadataReaderFactory metadataReaderFactory, AutoConfigurationMetadata autoConfigurationMetadata) { Assert.notNull(metadataReaderFactory, "MetadataReaderFactory must not be null"); this.metadataReaderFactory = metadataReaderFactory; this.autoConfigurationMetadata = autoConfigurationMetadata; }
上面代码只是对AutoConfigurationSorter做初始化,但最终是调用getInPriorityOrder方法来获取configuration的排序的。
public List<String> getInPriorityOrder(Collection<String> classNames) { final AutoConfigurationClasses classes = new AutoConfigurationClasses( this.metadataReaderFactory, this.autoConfigurationMetadata, classNames); List<String> orderedClassNames = new ArrayList<String>(classNames); //按字母顺序排序 Collections.sort(orderedClassNames); //按AutoConfigureOrder注解排序 Collections.sort(orderedClassNames, new Comparator<String>() { @Override public int compare(String o1, String o2) { //先根据AutoConfigureOrder排序 int i1 = classes.get(o1).getOrder(); int i2 = classes.get(o2).getOrder(); return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0; } }); //然后尊重@AutoConfigureBefore @AutoConfigureAfter orderedClassNames = sortByAnnotation(classes, orderedClassNames); return orderedClassNames; }
上面有一个重要的类AutoConfigurationClasses,下面我们来看看这个类的结构
private static class AutoConfigurationClasses { private final Map<String, AutoConfigurationClass> classes = new HashMap<String, AutoConfigurationClass>(); AutoConfigurationClasses(MetadataReaderFactory metadataReaderFactory, AutoConfigurationMetadata autoConfigurationMetadata, Collection<String> classNames) { for (String className : classNames) { this.classes.put(className, new AutoConfigurationClass(className, metadataReaderFactory, autoConfigurationMetadata)); } } public AutoConfigurationClass get(String className) { return this.classes.get(className); } }
最终将configurations转化为了AutoConfigurationClass 集合中,并存储到classes集合中,而AutoConfigurationClass这个类源码又是怎样的呢?因为比较重要,所以,我将整个类的源码都贴出来了。
private static class AutoConfigurationClass { private final String className; private final MetadataReaderFactory metadataReaderFactory; private final AutoConfigurationMetadata autoConfigurationMetadata; private AnnotationMetadata annotationMetadata; private final Set<String> before; private final Set<String> after; AutoConfigurationClass(String className, MetadataReaderFactory metadataReaderFactory, AutoConfigurationMetadata autoConfigurationMetadata) { this.className = className; this.metadataReaderFactory = metadataReaderFactory; this.autoConfigurationMetadata = autoConfigurationMetadata; //读取AutoConfigureBefore注解 this.before = readBefore(); //读取AutoConfigureAfter注解 this.after = readAfter(); } public Set<String> getBefore() { return this.before; } public Set<String> getAfter() { return this.after; } private int getOrder() { if (this.autoConfigurationMetadata.wasProcessed(this.className)) { return this.autoConfigurationMetadata.getInteger(this.className, "AutoConfigureOrder", Integer.MAX_VALUE ); } Map<String, Object> attributes = getAnnotationMetadata() .getAnnotationAttributes(AutoConfigureOrder.class.getName()); return (attributes == null ?Integer.MAX_VALUE : (Integer) attributes.get("value")); } private Set<String> readBefore() { if (this.autoConfigurationMetadata.wasProcessed(this.className)) { return this.autoConfigurationMetadata.getSet(this.className, "AutoConfigureBefore", Collections.<String>emptySet()); } return getAnnotationValue(AutoConfigureBefore.class); } private Set<String> readAfter() { if (this.autoConfigurationMetadata.wasProcessed(this.className)) { return this.autoConfigurationMetadata.getSet(this.className, "AutoConfigureAfter", Collections.<String>emptySet()); } return getAnnotationValue(AutoConfigureAfter.class); } private Set<String> getAnnotationValue(Class<?> annotation) { Map<String, Object> attributes = getAnnotationMetadata() .getAnnotationAttributes(annotation.getName(), true); if (attributes == null) { return Collections.emptySet(); } Set<String> value = new LinkedHashSet<String>(); Collections.addAll(value, (String[]) attributes.get("value")); Collections.addAll(value, (String[]) attributes.get("name")); return value; } private AnnotationMetadata getAnnotationMetadata() { if (this.annotationMetadata == null) { try { MetadataReader metadataReader = this.metadataReaderFactory .getMetadataReader(this.className); this.annotationMetadata = metadataReader.getAnnotationMetadata(); } catch (IOException ex) { throw new IllegalStateException( "Unable to read meta-data for class " + this.className, ex); } } return this.annotationMetadata; } }
AutoConfigurationClass这个类非常重要,但是内部代码其实不理解,就是读取AutoConfigureOrder,AutoConfigureBefore和AutoConfigureAfter注解,并保存到AutoConfigurationClass的属性中。
接下来,我们来看是如何通过注解来排序的
private List<String> sortByAnnotation(AutoConfigurationClasses classes, List<String> classNames) { //侍排序类集合 List<String> toSort = new ArrayList<String>(classNames); //己经排好序集合 Set<String> sorted = new LinkedHashSet<String>(); //正在排序的类集合 Set<String> processing = new LinkedHashSet<String>(); //只要待排序的类还存在,则循环不会结束 while (!toSort.isEmpty()) { //开始排序 doSortByAfterAnnotation(classes, toSort, sorted, processing, null); } return new ArrayList<String>(sorted); } private void doSortByAfterAnnotation(AutoConfigurationClasses classes, List<String> toSort, Set<String> sorted, Set<String> processing, String current) { if (current == null) { //如果current为空,则从侍排序的队列中取出第0个元素 current = toSort.remove(0); } processing.add(current); for (String after : classes.getClassesRequestedAfter(current)) { Assert.state(!processing.contains(after), "AutoConfigure cycle detected between " + current + " and " + after); if (!sorted.contains(after) && toSort.contains(after)) { doSortByAfterAnnotation(classes, toSort, sorted, processing, after); } } processing.remove(current); sorted.add(current); } public Set<String> getClassesRequestedAfter(String className) { Set<String> rtn = new LinkedHashSet<String>(); rtn.addAll(get(className).getAfter()); for (Map.Entry<String, AutoConfigurationClass> entry : this.classes .entrySet()) { if (entry.getValue().getBefore().contains(className)) { rtn.add(entry.getKey()); } } return rtn; }
其实AutoConfigureBefore和AutoConfigureAfter注解的排序就在上面的几个方法里,但是很多人可能第一眼就看晕了,虽然我们知道getAfter()方法就是获取当前类的AutoConfigureAfter注解配置的类,getBefore()方法就是AutoConfigureBefore注解配置的类,我们来看个例子说明一下。
@Configuration @AutoConfigureAfter({AutoConfigureAfterA.class, AutoConfigureAfterC.class}) public class AutoConfigureAfterB { public AutoConfigureAfterB() { System.out.println("AutoConfigureAfterB实例化"); } } @Configuration @AutoConfigureBefore({AutoConfigureBeforeA.class, AutoConfigureBeforeC.class,AutoConfigureAfterB.class}) public class AutoConfigureBeforeB { public AutoConfigureBeforeB() { System.out.println("AutoConfigureBeforeB实例化"); } }
类的配置如上所示,AutoConfigureBeforeA,AutoConfigureBeforeC,AutoConfigureAfterA,AutoConfigureAfterC都是普通配置@Configuration的类,在调用AutoConfigureAfterB的 getClassesRequestedAfter方法时,调用getAfter()方法,获得AutoConfigureAfterA,AutoConfigureAfterC类,再循环遍历所有的classes中的所有类,看其他类的AutoConfigureBefore注解中是否配置了AutoConfigureAfterB类,显然,我们的AutoConfigureBeforeB类的AutoConfigureBefore注解中配置了AutoConfigureAfterB类,因此获得AutoConfigureAfterB必需在AutoConfigureAfterA,AutoConfigureAfterC,AutoConfigureBeforeB类之后实例化。其实上面的配置下面的配置效果等同
@Configuration @AutoConfigureAfter({AutoConfigureAfterA.class, AutoConfigureAfterC.class,AutoConfigureBeforeB.class}) public class AutoConfigureAfterB { public AutoConfigureAfterB() { System.out.println("AutoConfigureAfterB实例化"); } } @Configuration @AutoConfigureBefore({AutoConfigureBeforeA.class, AutoConfigureBeforeC.class}) public class AutoConfigureBeforeB { public AutoConfigureBeforeB() { System.out.println("AutoConfigureBeforeB实例化"); } }
上面测试代码的区别,在于将AutoConfigureAfterB配置在AutoConfigureBeforeB的AutoConfigureBefore注解中,还是将AutoConfigureBeforeB配置到AutoConfigureAfterB的AutoConfigureAfter注解中,其实两者的效果一样。再来回顾一下,AutoConfigureAfterB上配置的AutoConfigureAfter类一定比AutoConfigureAfterB先实例化,而AutoConfigureBeforeB配置的AutoConfigureBefore注解类一定比AutoConfigureBeforeB要后实例化。AutoConfigureAfter和AutoConfigureBefore这两个注解的功能非常容易弄混了。大家一定要小心。
因为越先创建beanDefinition加入到容器中,就越先被实例化,因此当获取比当前类AutoConfigureAfterB先实例化的类
AutoConfigureAfterA和AutoConfigureAfterC后,再去看有没有比AutoConfigureAfterA和AutoConfigureAfterC是否配置了AutoConfigureAfter注解或被其他类配置在AutoConfigureBefore注解,有,则获取比AutoConfigureAfterA要先实例化的类,以此类推,递归调用,从而实现了AutoConfigureBefore和AutoConfigureAfter注解功能。
可能有人会想,什么时候会出现循环依赖呢?测试用例很简单,我们来看一个例子。
@Configuration @AutoConfigureAfter(AutoConfigureCycleC.class) public class AutoConfigureCycleA { } @Configuration @AutoConfigureAfter(AutoConfigureCycleA.class) public class AutoConfigureCycleB { } @Configuration @AutoConfigureAfter(AutoConfigureCycleB.class) public class AutoConfigureCycleC { }
AutoConfigureCycleA说AutoConfigureCycleC在我前面实例化,AutoConfigureCycleB说AutoConfigureCycleA在我前面实例化,AutoConfigureCycleC又说AutoConfigureCycleB在我前面实例化,明显是一个鸡生蛋,和蛋生鸡的问题,因此,Spring不知道你要什么效果,直接抛出异常好了。
总结
AutoConfigureBefore,AutoConfigureAfter注解主要是维护配置类的beanDefinition在容器中保存的顺序,保存在集合前面的BeanDefinition在bean实例化时,就越先被实例化,这也是一个粗略的认为,因为当有Import注解或有依赖时,实例化的顺序就会改变。所以AutoConfigureAfter,AutoConfigureBefore的作用并不是绝对的。
其他的注解还好,只是AutoConfigureBefore和AutoConfigureAfter注解的源码解析这一块,中途可能穿插了很多的其他的内容,如Spring boot的启动,像PropertySource,ComponentScan,ImportResource注解,扫描到接口,父类时的处理,因为不说明整个过程,那我们得到的知识点也是零碎的,不完整的,所以,就一路分析下来,也可能有些问题,我写得不够深刻,或者有误,如蒙不弃,大家在我的博客下方留言,有问题,我一定会去修正,因为Spring 代码太博大精深了,对于里面的注解,也不可能页面具到,我希望读者通过这篇博客能学习到注解相关的知识,更重要的是,学会分析Spring源码的方法,如果此博客对你有帮助,或有没有帮助,给我一个回馈也好的。我在写另外一篇关于Spring Boot博客,目前还没有写完,可能这篇博客或多或少有Spring Boot 启动,配置相关的影子,但是不急, 有兴趣,等我下一篇博客写好了,再来看看。
参考文章
Spring探秘之组合注解的处理
https://www.jianshu.com/p/0097572f34e8
spring-core:元数据之AnnotationMetadata.md
本文相关源码github地址
https://github.com/quyixiao/spring-boot-study