我们在看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数组,肯定在编译环境中存在该类。如果配置字符串数组的话,字符串构成的类名,在编译环境中不一定存在,基于以上的可能性,我们来测试一把。

  1. 创建普通类
public class ConditionalOnClassAnnocation {}
  1. 创建ConditionalOnClassUser测试类
@ConditionalOnClass(ConditionalOnClassAnnocation.class)
@Service("conditionalOnClassUser")
public class ConditionalOnClassUser {public void a() {System.out.println("a");}
}
  1. 创建测试方法
@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功能刚好相反,是否真的相反,看看例子再说。

  1. 创建普通类ConditionalOnMissingClassAnnocation和ConditionalOnMissingClassAnnocation1
public class ConditionalOnMissingClassAnnocation {
}
public class ConditionalOnMissingClassAnnocation1 {
}
  1. 创建测试类ConditionalOnMissingClassUser,上面配置了ConditionalOnMissingClass注解
@Service("conditionalOnMissingClassUser")
@ConditionalOnMissingClass("com.example.springbootstudy.service.ConditionalOnMissingClassAnnocation")
public class ConditionalOnMissingClassUser {}

  我们知道ConditionalOnMissingClassAnnocation在编译环境中定义肯定存在。

  1. 测试1

  容器中并不存在conditionalOnMissingClassUser类,因此,初步得出结论,ConditionalOnMissingClassUser类和其注解ConditionalOnMissingClass中配置的类是你死我活的关系,只要编译环境中存在ConditionalOnMissingClass注解配置的类,则Spring容器中就不会注册ConditionalOnMissingClass类。真是这样吧,要多举几个例子看看。修改ConditionalOnMissingClass属性信息,配置一个不存在的类

@Service("conditionalOnMissingClassUser")
@ConditionalOnMissingClass("com.example.springbootstudy.service.ConditionalOnMissingClassAnnocation2")
public class ConditionalOnMissingClassUser {}

  我们知道ConditionalOnMissingClassAnnocation2类肯定在编译环境中不存在。开始测试

  1. 测试2

  显然当ConditionalOnMissingClass注解中配置的类在当前编译环境不存在时,则当前类会被注入到Spring容器中。

  1. 测试3 ,当ConditionalOnMissingClass注解中配置两个类,一个类存在,另一个类不存在时。
@Service("conditionalOnMissingClassUser")
@ConditionalOnMissingClass({"com.example.springbootstudy.service.ConditionalOnMissingClassAnnocation","com.example.springbootstudy.service.ConditionalOnMissingClassAnnocation2"})
public class ConditionalOnMissingClassUser {
}


  当ConditionalOnMissingClass注解中配置一个类存在,另一个类不存在时,仍然不会注册到Spring容器中。

  1. 测试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不允许,我们来看一个例子。

  1. 创建MyService注解,添加Inherited注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Service
@Inherited
public @interface MyService {String value() default "";
}
  1. 创建ServiceTestImpl类,实现ServiceTestI接口,在ServiceTestI接口中配置@MyService注解
@MyService
public interface ServiceTestI {
}public class ServiceTestImpl  implements ServiceTestI{}
  1. 测试

  项目报错。ServiceTestImpl并没有注入。显然不能继承父接口的注解。即使注解中MyService配置了Inherited注解

  接下来,我们来看Transactional注解,看一个关于事务的小例子。

  1. 创建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>
  1. 配置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);}@Overridepublic 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()方法中,当执行插入操作以后,并且抛出异常。下面就看测试结果,看数据有没有回滚。

  1. 编写测试类
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);}
}
  1. 开始测试

程序抛出异常,同时数据没有插入到数据库

  我偿注释掉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 {@Overridepublic boolean matches(String className, ClassLoader classLoader) {return isPresent(className, classLoader);}},MISSING {@Overridepublic 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为falseif (!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为truereturn 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的使用,没有太大问题了,即使没有太大问题,我们还是要一一的来测试,这样才能体现对知识理解的严谨性。下面我们来看一个最简单的例子。

  1. 创建普通bean
@Service
public class ConditionalOnBeanAnnocation {}
  1. 创建被测试类
@ConditionalOnBean({ConditionalOnBeanAnnocation.class})
@Service
public class ConditionalOnBeanUser {
}
  1. 开始测试
@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才会注册到容器中。

  1. 创建MyConditionalOnBeanTest注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyConditionalOnBeanTest {}
  1. 创建bean 并配置MyConditionalOnBeanTest注解
@MyConditionalOnBeanTest
@Service
public class MyConditionalOnBeanTestImpl {
}
  1. 测试
@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才会注册到容器中,真的是如此吗?。根据猜测,我们来测试一下。

  1. 创建一个普通bean
@Service
public class ConditionalOnSingleCandidateUserAnnocation0 {}
  1. 创建测试bean
@Service
@ConditionalOnSingleCandidate(ConditionalOnSingleCandidateUserAnnocation0.class)
public class ConditionalOnSingleCandidateUser {
}
  1. 测试结果

  2. 我们将ConditionalOnSingleCandidateUserAnnocation0变成多例

      显然,我们之前的猜测有问题,Single不是单例的意思,那么不是单例,会不会是单个呢?那我们来测试一下。

  3. 创建接口IConditionalOnSingleCandidateUserAnnocation

public interface IConditionalOnSingleCandidateUserAnnocation {}
  1. 创建测试类ConditionalOnSingleCandidateUserAnnocation实现IConditionalOnSingleCandidateUserAnnocation接口
@Service("conditionalOnSingleCandidateUserAnnocation")
public class ConditionalOnSingleCandidateUserAnnocation implements IConditionalOnSingleCandidateUserAnnocation {
}
  1. 创建测试类ConditionalOnSingleCandidateUserAnnocation1也实现IConditionalOnSingleCandidateUserAnnocation接口。
@Service("conditionalOnSingleCandidateUserAnnocation1")
public class ConditionalOnSingleCandidateUserAnnocation1 implements IConditionalOnSingleCandidateUserAnnocation {}
  1. 修改测试类ConditionalOnSingleCandidateUser的注解ConditionalOnSingleCandidate属性。
@Service
@ConditionalOnSingleCandidate(IConditionalOnSingleCandidateUserAnnocation.class)
public class ConditionalOnSingleCandidateUser {}
  1. 开始测试

      从测试结果中可以看出,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属性的使用

  1. 创建普通bean
@Service
public class ConditionalOnMissingBeanAnnocation {
}
  1. 创建测试bean ConditionalOnMissingBeanUser
@Service
@ConditionalOnMissingBean(ConditionalOnMissingBeanAnnocation.class)
public class ConditionalOnMissingBeanUser {
}
  1. 开始测试

      显然ConditionalOnMissingBeanUser的bean并没有注入到容器中。
  2. 当我们将ConditionalOnBeanAnnocation1从容器中移除

      显然ConditionalOnMissingBeanUser己经注册到容器中。从测试结果中可以看出,当ConditionalOnMissingBean的属性中的bean存在于容器中,ConditionalOnMissingBeanUser 的bean将不会注册到容器中,因此被ConditionalOnMissingBean修饰的bean和ConditionalOnMissingBean的属性值的bean是存在你死我活的关系。下面再来看,假如ConditionalOnMissingBean注解中是一个属性数组时,会怎样呢?
  3. 再次创建一个普通类 ConditionalOnMissingBeanAnnocation1,没有注册到容器中。
@Service
@ConditionalOnMissingBean({ConditionalOnMissingBeanAnnocation.class,ConditionalOnMissingBeanAnnocation1.class})
public class ConditionalOnMissingBeanUser {}

  测试结果

  从测试结果来看,当ConditionalOnMissingBeanAnnocation和ConditionalOnMissingBeanAnnocation1都不存在于容器中,ConditionalOnMissingBeanUser注册到容器成功。

  1. 假如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配置的属性,从容器中获取beanNamesList<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配置的属性,从容器中获取beanNamesList<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配置的属性,从容器中获取beanNamesList<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));}//移除掉忽略的beanfor (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注解修饰,则返回truereturn (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获取typesaddDeducedBeanType(context, metadata, this.types);}}catch (BeanTypeDeductionException ex) {deductionException = ex;}validate(deductionException);
}

  上述代码其他的还好理解,无非将注解配置的属性value数组,type数组等封装到相应的属性中,但是有一种情况难以理解,那就是当我们在注解中什么都不配置时,这个时候需要通过addDeducedBeanType方法来获取types参数。那我们来看看一个例子。

@Configuration
public class ConditionalOnMissingBeanConfig {@Bean@ConditionalOnMissingBeanpublic 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 {...
}
  1. 创建配置类
@ConfigurationProperties(prefix = "xxx.test")
public class EnableConfigurationPropertiesTest {private String username;private String password;... 省略
}
  1. 在配置文件中配置相关属性

  1. 创建测试类
@Service
@EnableConfigurationProperties(EnableConfigurationPropertiesTest.class)
public class EnableConfigurationPropertiesBean {}
  1. 开始测试
@Autowired
private EnableConfigurationPropertiesTest enableConfigurationPropertiesTest;@RequestMapping("enableConfigurationPropertiesTest")
public String enableConfigurationPropertiesTest() {System.out.println(JSON.toJSONString(enableConfigurationPropertiesTest));return "Sucess";
}
  1. 测试结果

  2. 当我们注释掉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 firstprocessMemberClasses(configClass, sourceClass);// Process any @PropertySource annotationsfor (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 annotationsSet<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 immediatelySet<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());// Check the set of scanned definitions for any further config classes and parse recursively if neededfor (BeanDefinitionHolder holder : scannedBeanDefinitions) {if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) {parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());}}}}// Process any @Import annotationsprocessImports(configClass, sourceClass, getImports(sourceClass), true);// Process any @ImportResource annotationsif (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 methodsSet<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);for (MethodMetadata methodMetadata : beanMethods) {configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}// Process default methods on interfacesprocessInterfaces(configClass, sourceClass);// Process superclass, if anyif (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 recursereturn sourceClass.getSuperClass();}}// No superclass -> processing is completereturn 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 importsClass<?> 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 definitionsClass<?> 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 classthis.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 ConfigurationPropertiesBeanRegistrarimplements ImportBeanDefinitionRegistrar {@Overridepublic 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());//如果容器中不存在该beanDefinitionif (!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的属性类要后实例化吗?我们的猜测是如此,那还是来测试一把。

  1. 创建AutoConfigure配置类,AutoConfigureBeforeA和AutoConfigureBeforeC
@Configuration
public class AutoConfigureBeforeA {public AutoConfigureBeforeA() {System.out.println("BeforeA实例化");}
}@Configuration
public class AutoConfigureBeforeC {public AutoConfigureBeforeC() {System.out.println("BeforeC实例化");}
}
  1. 创建测试类
@Configuration
@AutoConfigureBefore({AutoConfigureBeforeA.class, AutoConfigureBeforeC.class})
@Import({AutoConfigureBeforeA.class, AutoConfigureBeforeC.class})
public class AutoConfigureBeforeB {public AutoConfigureBeforeB() {System.out.println("BeforeB实例化");}
}
  1. META-INF目录下的配置文件spring.factories中添加
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=
    com.example.springbootstudy.service.AutoConfigureAfterB,
    com.example.springbootstudy.service.AutoConfigureBeforeB

  2. 开始测试

      从测试结果中可以看出,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>() {@Overridepublic 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 contentif (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);//假如己经存在了ConfigurationClassif (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 {@Configurationprotected static class ImportByB {@Beanpublic 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注解的使用,再来看源码。

  1. 创建配置类
@Component
@PropertySource({"classpath:config/bean1.properties","classpath:config/bean.properties"})
public class PropertySourceA {}
  1. 创建配置文件bean.properties和bean1.properties
    bean.properties文件中配置bean.message=11111111111
    bean1.properties配置文件中配置bean.message=2222222222222

  2. 开始测试


  当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,优先被找到。

  1. 创建测试类
@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) {@Overrideprotected 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);// 遍历扫描得到的Beanfor (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容器中,向容器注册扫描到的BeanregisterBeanDefinition(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;@Overridepublic void setBeanClassLoader(ClassLoader beanClassLoader) {this.beanClassLoader = beanClassLoader;}@Overridepublic 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 importsClass<?> 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 definitionsClass<?> 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 classthis.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注解没有太大关系,但是遇到了,我们还是来分析一下吧, 在分析源码之前,我们还是先来看一个例子。

  1. 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>
  1. 创建普通类
public class ImportResourceTestBean {
}
  1. 创建配置类
@ImportResource("classpath:config/spring_importsource_test.xml")
@Service
public class ImportResourceA {
}
  1. 创建测试方法
@RequestMapping("importResourceTestBeanTest")
public String importResourceTestBeanTest() {ImportResourceTestBean importResourceTestBean = SpringContextUtils.getBean(ImportResourceTestBean.class);System.out.println(importResourceTestBean);return "Sucess";
}
  1. 测试结果

  打印出我们配置文件中配置的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文件,注册为beanDefinitionloadBeanDefinitionsFromImportedResources(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 -> proceedbeanMethods = 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 {@Beanpublic BeanB beanB(){return new BeanB();}@Beanpublic BeanA beanA(){return new BeanA();}}

@Configuration
public class BeanConfig {@Beanpublic BeanA beanA(){return new BeanA();}@Beanpublic BeanB beanB(){return new BeanB();}
}


  因此,有个时候,我们用Configuration配置bean时,Bean在代码中写的顺序影响到Bean实例化的顺序。目前我也没有找到好的办法解决这个问题。
  接下来,我们继续看如何处理接口中定义的默认方法,并且方法配置了bean。在看源码之前 ,还是先来看一个示例。

public class ProcessInterfaceB {
}public class ProcessInterfaceC {
}public interface ProcessInterfaceA {@Beandefault ProcessInterfaceB processInterfaceB() {return new ProcessInterfaceB();}
}@Configuration
public interface ProcessInterfaceAA extends ProcessInterfaceA {@Beandefault 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 {@Beanpublic 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>() {@Overridepublic 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 @AutoConfigureAfterorderedClassNames = 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

https://github.com/alex2chen/spring-boot-cloud-note/blob/master/spring-core%EF%BC%9A%E5%85%83%E6%95%B0%E6%8D%AE%E4%B9%8BAnnotationMetadata.md

本文相关源码github地址
https://github.com/quyixiao/spring-boot-study

Spring源码深度解析(郝佳)-Spring 常用注解使用及源码解析相关推荐

  1. 《Spring源码深度解析 郝佳 第2版》XML标签的解析

    目录 往期博客<Spring源码深度解析 郝佳 第2版>容器的基本实现与XML文件的加载 分析了xml文件的加载,接下来就是标签的解析,入口函数有两个 默认标签的解析 自定义标签的解析 一 ...

  2. 《Spring源码深度解析 郝佳 第2版》AOP

    往期博客 <Spring源码深度解析 郝佳 第2版>容器的基本实现与XML文件的加载 <Spring源码深度解析 郝佳 第2版>XML标签的解析 <Spring源码深度解 ...

  3. 《Spring源码深度解析 郝佳 第2版》ApplicationContext

    往期博客: <Spring源码深度解析 郝佳 第2版>容器的基本实现与XML文件的加载 <Spring源码深度解析 郝佳 第2版>XML标签的解析 <Spring源码深度 ...

  4. 《Spring源码深度解析 郝佳 第2版》SpringBoot体系分析、Starter的原理

    往期博客 <Spring源码深度解析 郝佳 第2版>容器的基本实现与XML文件的加载 <Spring源码深度解析 郝佳 第2版>XML标签的解析 <Spring源码深度解 ...

  5. 《Spring源码深度解析 郝佳 第2版》事务

    往期博客 <Spring源码深度解析 郝佳 第2版>容器的基本实现与XML文件的加载 <Spring源码深度解析 郝佳 第2版>XML标签的解析 <Spring源码深度解 ...

  6. 《Spring源码深度解析 郝佳 第2版》JDBC、MyBatis原理

    往期博客 <Spring源码深度解析 郝佳 第2版>容器的基本实现与XML文件的加载 <Spring源码深度解析 郝佳 第2版>XML标签的解析 <Spring源码深度解 ...

  7. 《Spring源码深度解析 郝佳 第2版》bean的加载、循环依赖的解决

    往期博客: <Spring源码深度解析 郝佳 第2版>容器的基本实现与XML文件的加载 <Spring源码深度解析 郝佳 第2版>XML标签的解析 往期博客完成了xml文件加载 ...

  8. Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean定义(一)

    我们在之前的博客 Spring源码深度解析(郝佳)-学习-ASM 类字节码解析 简单的对字节码结构进行了分析,今天我们站在前面的基础上对Spring中类注解的读取,并创建BeanDefinition做 ...

  9. Spring源码深度解析(郝佳)-学习-源码解析-创建AOP静态代理实现(八)

    继上一篇博客,我们继续来分析下面示例的 Spring 静态代理源码实现. 静态 AOP使用示例 加载时织入(Load -Time WEaving,LTW) 指的是在虚拟机载入字节码时动态织入 Aspe ...

最新文章

  1. 程序员必备,新手也可以直接拿来用的jQuery万能代码段
  2. Heartbeat V2.x双机热备安装
  3. 【Java后台】Java执行Python代码的3类5种情况测试【Java源码+Python源码举例】
  4. char强制类型转换为int_C语言学习第5篇---类型转换概念理解
  5. 双击jar包 运行SpringBoot项目
  6. windowsR2(流媒体服务器)
  7. 51单片机 日历 c语言 数码管,51单片机做的数码管电子日历
  8. 【软件与系统安全】栈溢出利用的分析
  9. FFmpeg 软编码h.264与H.265(从简到深)
  10. 力扣(LeetCode)刷题,简单+中等题(第36期)
  11. Edison重新上手
  12. android模拟器字体,真正免root的ifont字体软件详细使用教程
  13. Nacos+Gateway503错误,No servers available for service
  14. 平面设计师怎么找素材?
  15. Arale Base源码分析(含Attribute)
  16. AUC和ROC曲线的前世今生
  17. 面向对象程序设计实践(C++)——二维向量
  18. win7最适合的杀毒软件(电脑适合用什么杀毒软件)
  19. Python 决策树预测 分类算法
  20. 万拓云存储系统解决方案

热门文章

  1. 【CVPR2022】【小样本分类和分割】Integrative Few-Shot Learning for Classification and Segmentation
  2. quartz-job实现实时或定时发送短信任务
  3. 神级程序猿用HTML5代码画出恐龙求欢图,想象力太丰富!
  4. JavaScript学习笔记(第二部分)总共四部分
  5. 修电脑大全,学会不求人
  6. Orical 汉字排序问题
  7. 机器学习19:反卷积算法
  8. 从统计看机器学习(一) 一元线性回归
  9. Mac安装office
  10. 可见即可爬:快速上手 Selenium