导语
说到Spring Boot的特性是什么,大家都可能都会说Spring Boot的特性有自动化装配,当然我也看到网上有一些文章讲这方面的,但是貌似都是Copy来Copy去的,很多也没什么新意,基本上就是简单说说几个注解的含义等等。因此笔者想着是不是该写写这方面的文章,其实笔者也是现学现卖的。既然说的是现学现卖,那么肯定写的不是特别严谨,因此如果下面的文章中,有什么不足之处也请大家多多的之处。
对于Spring Boot的自动装配,那是避免不了Spring Boot的启动类注解@SpringBootApplication,其实对于Spring Boot或者Spring而言,他们所要装配的是Bean,在Spring的官方文档中,提到了激活自动化装配的注解@SpringBootConfiguration和@EnableAutoConfiguration,并将连两者标注在@Configuration上,但是对于使用过Spring的朋友来说,都知道有如下几种配置方法:XML元素(<context:componet-scan>)、@Import和@ComponetScan,但是这几种方法是需要Spring应用上下文来进行引导的。在这里我们来先说说关于@SpringBootApplication注解的语义。
@SpringBootApplication注解语义
在Spring Boot官网文档中,关于@SpringBootApplication注解是有说明的,它说@SpringBootApplication是被用于激活@SpringBootConfiguration、 @EnableAutoConfiguration和@ComponentScan的,而@EnableAutoConfiguration就是负责Spring Boot自动装配机制的,@ComponentScan是用于激活@Componet的扫描,在这里@SpringBootConfiguration注解是等同于@Configuration的,那也就是说@SpringBootConfiguration的作用是用于声明被标注的配置类的,这里有关于Spring注解派生性方面的知识点,后面会提到。说到这里我们还是看看@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 {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
/**
* @since 1.3.0
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
/**
*
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
/**
*
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
/**
*
* @since 2.2
* @return whether to proxy {@code @Bean} methods
*/
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
从上面的代码中可以看出,@SpringBootApplication是有等同于@SpringBootConfiguration、 @EnableAutoConfiguration和@ComponentScan这三个注解的意思的,如果你在引导类中同时标注上这三个注解,你的项目也是可以启动的。
对于@SpringBootApplication上标注的注解@Inherited,是说明被标注这个注解的注解是会被子类自动继承的,这就说明@SpringBootApplication 注解被标注在启动引导类上的时候,这个时候引导类就具有自动继承@SpringBootApplication 的含义。其实@Inherited 也是元注解,对于元注解的含义就是指一个声明在其他注解上的注解。
在上面我们还分别提到了@SpringBootConfiguration、 @EnableAutoConfiguration和@ComponentScan 这三个注解,对于这三个注解,我们来简单说说其作用。
@ComponentScan的作用
从@SpringBootApplication 注解上可以看到,@ComponentScan 注解并没有使用默认值,而是加上了排除和包含的实现,这个对于使用过Spring的同学应该都是了解的,不了解的建议去看看了解一下。其实在Spring中@ComponentScan 的作用就是用于完成组件扫描,不过这里需要注意的是,在这里其@ComponentScan 仅仅用于配置组件扫描指令,并没有真正的扫描,更没有装配其中的类,真正扫描其实是由@EnableAutoConfiguration 注解去完成的,这个后面会说到。还有在这里可以看到@ComponentScan 所添加的分别是:TypeExcludeFilter和AutoConfigurationExcludeFilter,TypeExcludeFilter类的作用是用于负责查找BeanFactory中已经注册了的TypeExcludeFilter,并作为代理执行对象,而AutoConfigurationExcludeFilter用于同时排除标注了@Configuration 和@EnableAutoConfiguration 的类的。我们可以看看他们类的基本实现,如下:
TypeExcludeFilter
public class TypeExcludeFilter implements TypeFilter, BeanFactoryAware {
private BeanFactory beanFactory;
private Collection<TypeExcludeFilter> delegates;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
if (this.beanFactory instanceof ListableBeanFactory && getClass() == TypeExcludeFilter.class) {
for (TypeExcludeFilter delegate : getDelegates()) {
if (delegate.match(metadataReader, metadataReaderFactory)) {
return true;
}
}
}
return false;
}
private Collection<TypeExcludeFilter> getDelegates() {
Collection<TypeExcludeFilter> delegates = this.delegates;
if (delegates == null) {
delegates = ((ListableBeanFactory) this.beanFactory).getBeansOfType(TypeExcludeFilter.class).values();
this.delegates = delegates;
}
return delegates;
}
@Override
public boolean equals(Object obj) {
throw new IllegalStateException("TypeExcludeFilter " + getClass() + " has not implemented equals");
}
@Override
public int hashCode() {
throw new IllegalStateException("TypeExcludeFilter " + getClass() + " has not implemented hashCode");
}
}
AutoConfigurationExcludeFilter
public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware {
private ClassLoader beanClassLoader;
private volatile List<String> autoConfigurations;
@Override
public void setBeanClassLoader(ClassLoader beanClassLoader) {
this.beanClassLoader = beanClassLoader;
}
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
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) {
this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,
this.beanClassLoader);
}
return this.autoConfigurations;
}
如果再深入点的话,可以发现在@ComponentScan 注解类上,是有标注@Repeatable(ComponentScans.class) 的,对于@Repeatable 注解,如果知道的同学便会说它不就是表示被它锁注解的注解是可以在同一个类上重复使用的嘛!其实这个功能还是在Spring 4.0版本的时候才实现的,当然这个特性也是在Java 8中可以看见的。
关于@SpringBootConfiguration
前面我们有说到@SpringBootConfiguration 注解是等同于@Configuration 的,其实这里有着这样的一个派生关系,也就是说@SpringBootConfiguration 是一个具有多层次派生性注解的注解,如果你进入到@Configuration 类你会发现这个类上是有标注@Component 注解的,对于这个注解的意义就是,当被这个注解所标注的类都会被创建为bean(可查看Spring In Action 这本书),这也就要说到在说到@ComponentScan 注解的时候所忽略的一点,在@ComponentScan 类上有这样一据注释:“ <p>Either {@link #basePackageClasses} or {@link #basePackages} (or its alias {@link #value}) may be specified to define specific packages to scan. If specific packages are not defined, scanning will occur from the package of the class that declares this annotation.”意思就是说标注这个注解的类会从当前类开始扫描,专业就要提到前面提到的@Inherited 注解,这也就是为什么我们Spring Boot启动引导类必须处于虽有类的顶层的原因。
我们还是来说说派生性这样一个问题,刚才说到@SpringBootConfiguration 属于@Configuration 的派生注解,而@Configuration 又属于@Component 的派生注解,而@Component 所注解的类是为了可以被@ComponentScan 所识别,这个时候就可以把前面几个注解都串起来了。
@SpringBootApplication注解的属性别名
对于属性别名注解,在这里只是简单提一下,并不会做深入阐述,后面可能会新鞋一篇文章来进行说明,说到这里有些人可能要说设么是属性别名,其实属性别名就是@AliasFor 注解,这个注解是从Spring Framework 4.2版本才开始支持的。
@AliasFor 的含义就是,这个注解能够将一个或多个注解的属性别名注解到某个注解上。
@EnableAutoConfiguration激活自动装配
在上面,我们其实有提到,对于自动装配其实是靠@EnableAutoConfiguration 这个注解的,其实在Spring中标注了@EnableXxx 注解的,一般是用于开启某一项功能的,也就是是为了简化代码的导入,即使用了该类注解的类,就会自动导入某些类。这也说明了该注解是一个组合注解,一般都会组合一个@Import 注解,来用 于导入指定的多个类,而被导入的类一般分为三种:配置类、选择器、注册器。那就看看@EnableAutoConfiguration 注解类的代码,如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
/**
* @since 1.3.0
*/
String[] excludeName() default {};
}
从上面代码中,已经看到了@Import 注解,这里可以简单看下@Import 注解上所标注的@AutoConfigurationPackage 的注解类的代码,如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}
从代码中可以看出,@Import 注解上还标注了AutoConfigurationPackages这个类,并同时调用了Registrar这个类,那我们来看看这里它做了什么,如下:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
从@AutoConfigurationPackage 这个注解类开始的逻辑是,先将Registrar这个组件类导入到容器中,然后调用Registrar静态内部类中的registerBeanDefinitions方法来将注解标注的元信息传入,并获取到相应的包名。由此得知@AutoConfigurationPackage 注解类其实就是是将启动引导类所在包及所有子包下的组件到扫描到spring容器中。
那么我们继续回来看看@Import(AutoConfigurationImportSelector.class) ,这里的作用就是将AutoConfigurationImportSelector这个类导入到spring的容器中,然后通过AutoConfigurationImportSelector 这个类可以帮助Spring Boot应用将所有符合@Configuration注解条件的配置类都加载到当前Spring Boot 的IoC容器(ApplicationContext)中来创建并使用。那我们来看下AutoConfigurationImportSelector 这个类中的selectImports 方法:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 获得自动配置元信息,且传入beanClassLoader这个类加载器
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
对于selectImports 这个方法的调用,要看ConfigurationClassParser 这个配置信息类解析器类中的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 {
// 1.遍历ClassSource
for (SourceClass candidate : importCandidates) {
// 2.判断candidate是否包含ImportSelector类,符合则进入
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
// 5.判断selector 是否包含DeferredImportSelector
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
// 6.如果5不符合,这进入这里,这里便是selectImports方法的入口
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
// 3.判断candidate中是否包含ImportBeanDefinitionRegistrar类
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
// 4.以上都不符合则进入这里
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
}
}
}
上面代码的逻辑颇为复杂,但是从5这里可以看出当selector 选择器不是DeferredImportSelector的时候,便会执行selectImports方法。
我们接着看看selectImports#loadMetadata 这个方法,代码如下:
final class AutoConfigurationMetadataLoader {
protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties"; //文件中需要加载的配置类类路径
private AutoConfigurationMetadataLoader() {
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
//读取spring-boot-autoconfigure-XXX.RELEASE.jar包下的spring-autoconfigure-metadata.properties配置文件中的信息,来生成urls枚举对象
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
: ClassLoader.getSystemResources(path);
Properties properties = new Properties();
while (urls.hasMoreElements()) { //解析urls枚举对象中的信息。并封装成properties对象后再加载
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
}
// 用所封装好的properties,来生成AutoConfigurationMetadata对象返回
return loadMetadata(properties);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
}
static AutoConfigurationMetadata loadMetadata(Properties properties) {
}
在看完selectImports#loadMetadata后,继续来看看selectImports#getAutoConfigurationEntry 方法,如下:
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 将注解元信息封装成注解属性对象
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 new AutoConfigurationEntry(configurations, exclusions);
}
继续看看getCandidateConfigurations和loadFactoryNames方法:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
/**
* 这个方法需要传入两个参数:getSpringFactoriesLoaderFactoryClass()和getBeanClassLoader(),
* 前者getSpringFactoriesLoaderFactoryClass()这个方法返回的是EnableAutoConfiguration.class
* 而getBeanClassLoader()这个方法返回的是beanClassLoader(类加载器)
*/
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;
}
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
// 如果类加载器不为null,则加载类路径下spring.factories文件,将其中设置的配置类的全路径信息封装为Enumeration类对象
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
// 然后循环Enumeration类对象,并根据相应的节点信息生成Properties对象,通过传入的键获取值,在将值切割为一个个小的字符串转化为Array数组,然后放到result集合中
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
总结
到这里,本文也将要结束了,最后可能有点跑偏了,不过大体的思路还在,总而言之,@SpringBootApplication是一个组合注解,它下面的@EnableAutoConfiguration才是具有真正实现自动装配功能的注解,而这个一系列的大体过程如下:
- Spring Boot启动引导类;
- 然后便是@SpringBootApplication起作用;
- 再者@EnableAutoConfiguration注解开始实现真正的自动装配;
- 而@EnableAutoConfiguration注解注解是由@AutoConfigurationPackage这个组合注解来实现的,主要是@Import(AutoConfigurationPackages.Registrar.class)和Import(AutoConfigurationImportSelector.class)
,前者的是通过将Registrar类导入到容器中,而Registrar类作用是扫描主配置类同级目录以及子包,并将相应的组件导入到Spring Boot所创建并管理的容器中; - 后者@Import(AutoConfigurationImportSelector.class)是通过将AutoConfigurationImportSelector这个类来导入到容器中,AutoConfigurationImportSelector类作用是通过selectImports方法实将配置类信息交给SpringFactory加载器,来进行一系列的容器创建过程,具体实现可从面源码中得知。