SpringBoot 源码解读系列(3)[email protected]注解的源码处理分析
一般我们创建一个SpringBoot项目,其启动类加@SpringBootApplication注解就能直接启动。
@SpringBootApplication public class SpringbootdemoApplication { public static void main(String[] args) { SpringApplication.run(SpringbootdemoApplication.class, args); } }
上面就是一个启动demo。SpringApplication.run的入参就是表示Class(SpringbootdemoApplication),但这里其是主要就是需要扫描到其上面添加的@SpringBootApplication注解,下面我们来分析下@SpringBootApplication。
1、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 { ........... }
可以看到其上面的Spring自定义注解有@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。这里的@ComponentScan就是用来扫描这个SpringbootdemoApplication 所在的包及其下面的子包,这个就不过多赘叙了。
1、@SpringBootConfiguration
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { }
可以看到其上面就是加了一个@Configuration,用于给ConfigurationClassPostProcessor来扫描处理。
2、@EnableAutoConfiguration
这个@EnableAutoConfiguration就是@SpringBootApplication的主要引入内容了,用来加载在spring-boot-autoconfigure模块中定义的那些自动配置的类了。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {
可以看到其通过@Import引入了一个ImportSelector(AutoConfigurationImportSelector),这个就是整个引入的关键。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { ............. @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return StringUtils.toStringArray(configurations); }
在分析这个AutoConfigurationImportSelector之前我们首先需要了解两个文件。
1、两个properties文件
protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties"; public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
首先我们通过这两个文件的内容来看下它们之间的联系,我们通过在前一篇文章注入JdbcTemplate的类JdbcTemplateAutoConfiguration 来分析分析。
@Configuration @ConditionalOnClass({DataSource.class, JdbcTemplate.class}) @ConditionalOnSingleCandidate(DataSource.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class) @EnableConfigurationProperties(JdbcProperties.class) public class JdbcTemplateAutoConfiguration {
我们知道其有注解@ConditionalOnClass,所以需要有DataSource.class, JdbcTemplate.class才会去处理这个类。
1、spring.factories
............... org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ ................ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\ ...........
我们看到其其实通过key-value[]的形式设置的。这个可以key就是我们在前面提到的@EnableAutoConfiguration注解,value[]就是我们用来注入JdbcTemplate、或者DataSource所需要的的JdbcTemplateAutoConfiguration、DataSourceAutoConfiguration。
2、spring-autoconfigure-metadata.properties
........... org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.jdbc.core.JdbcTemplate .........
可以看到这个key是"JdbcTemplateAutoConfiguration"+".ConditionalOnClass",而value就是@ConditionalOnClass所需要的类。
2、AutoConfigurationImportSelector
下面我们再来分析AutoConfigurationImportSelector的selectImports方法。
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } .............. AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return StringUtils.toStringArray(configurations); }
1、isEnabled方法
protected boolean isEnabled(AnnotationMetadata metadata) { if (getClass() == AutoConfigurationImportSelector.class) { return getEnvironment().getProperty( EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true); } return true; } String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
这个入参就是描叙对应的Class信息的,例如有哪些注解,首先是从环境中看需不需要默认注入配置。
2、AutoConfigurationMetadataLoader .loadMetadata
protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties"; ........ public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) { return loadMetadata(classLoader, PATH); } static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) { try { Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path) : ClassLoader.getSystemResources(path); Properties properties = new Properties(); while (urls.hasMoreElements()) { properties.putAll(PropertiesLoaderUtils .loadProperties(new UrlResource(urls.nextElement()))); } return loadMetadata(properties); ........ }
可以看到这里就是加载spring-autoconfigure-metadata.properties文件中的内容。
3、getAttributes
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) { String name = getAnnotationClass().getName(); AnnotationAttributes attributes = AnnotationAttributes .fromMap(metadata.getAnnotationAttributes(name, true)); Assert.notNull(attributes, () -> "No auto-configuration attributes found......); return attributes; } protected Class<?> getAnnotationClass() { return EnableAutoConfiguration.class; }
这个就是用来获取该Class上面@EnableAutoConfiguration注解里面的属性配置的
4、getCandidateConfigurations
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames( getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); ......... return configurations; } protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; }
这个SpringFactoriesLoader.loadFactoryNames就是用来加载spring.factories的内容的。这里传入的getSpringFactoriesLoaderFactoryClass(),就是EnableAutoConfiguration,所以这里就会获取spring.factories中key为EnableAutoConfiguration(会带包名)的,例如JdbcTemplateAutoConfiguration、DataSourceAutoConfiguration。
5、filter
上面的那些removeAll、exclusions就不再分析了,就是删除一些需要排除的。
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) { String[] candidates = StringUtils.toStringArray(configurations); boolean[] skip = new boolean[candidates.length]; boolean skipped = false; for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) { invokeAwareMethods(filter); boolean[] match = filter.match(candidates, autoConfigurationMetadata); for (int i = 0; i < match.length; i++) { if (!match[i]) { skip[i] = true; skipped = true; } } } ........ List<String> result = new ArrayList<>(candidates.length); for (int i = 0; i < candidates.length; i++) { if (!skip[i]) { result.add(candidates[i]); } } ........... return new ArrayList<>(result); }
这里就偶是用来通过AutoConfigurationImportFilter去match,然后通过
if (!skip[i]) { result.add(candidates[i]); }
如果匹配,就添加到result中,result的内容就是spring.factories获取的XXXConfiguration最终需要注入到Bean容器的内容。那这个filter.match是主要匹配的呢?
我们首先需要看下通过getAutoConfigurationImportFilters方法有获取哪些AutoConfigurationImportFilter
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() { return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader); }
可以看到这里会去spring.ffactories文件中获取key为AutoConfigurationImportFilter的value
# Auto Configuration Import Filters org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.condition.OnClassCondition
然后获取到的就是OnClassCondition。这个OnClassCondition在上一篇有提到,其实一个Condition。
@Override public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) { ConditionEvaluationReport report = getConditionEvaluationReport(); ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata); boolean[] match = new boolean[outcomes.length]; .......... return match; }
这里首先需要注意的一个点就是autoConfigurationClasses中放到是spring.factories文件获取的value[],autoConfigurationMetadata中获取的是从spring-autoconfigure-metadata.properties文件获取的内容。
我们直接来到其主要的逻辑处理
private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) { ConditionOutcome[] outcomes = new ConditionOutcome[end - start]; for (int i = start; i < end; i++) { String autoConfigurationClass = autoConfigurationClasses[i]; Set<String> candidates = autoConfigurationMetadata .getSet(autoConfigurationClass, "ConditionalOnClass"); if (candidates != null) { outcomes[i - start] = getOutcome(candidates); } } return outcomes; }
这里就是以autoConfigurationClasses[i]为key再拼接"ConditionalOnClass",例如我们前面在将这两个文件的时候:
例如"autoConfigurationClass "为"org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration",这里拼接的就是"org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.ConditionalOnClass",然后以这个为key从spring-autoconfigure-metadata.properties文件中获取:
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.jdbc.core.JdbcTemplate
这里就获取到了JdbcTemplate、DataSource。然后再调用getOutcome方法(前面调用的是getOutcomes)
private ConditionOutcome getOutcome(Set<String> candidates) { try { List<String> missing = getMatches(candidates, MatchType.MISSING, this.beanClassLoader); if (!missing.isEmpty()) { return ConditionOutcome.noMatch( ......... } } catch (Exception ex) { // We'll get another chance later } return null; } MISSING { @Override public boolean matches(String className, ClassLoader classLoader) { return !isPresent(className, classLoader); } }; private static boolean isPresent(String className, ClassLoader classLoader) { if (classLoader == null) { classLoader = ClassUtils.getDefaultClassLoader(); } try { forName(className, classLoader); return true; } catch (Throwable ex) { return false; } }
所以这里其主要是一个提取校验并且记录校验的结果,看需要注入的例如JdbcTemplateAutoConfiguration,满不满足,@ConditionalOnClass所需的。如果校验通过就返回通过,然后就会将其添加到result中。之后再注入到Bean容器中。
以上就是关于@SpringBootApplication注解的大体分析。