一般我们创建一个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注解的大体分析。