简介
说明
本文介绍SpringBoot是如何实现自动配置的。
问题的引出
SpringBoot有如下功能:
- 创建好的SpringBoot项目(假如启动类是DemoApplication),可以直接运行
- 可以在配置文件自定义配置
- 在启动类上加个注解就可以使用某个功能
那么,SpringBoot是如何实现这些功能的呢?
入口
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
@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 { ... }
- @SpringBootConfiguration // 对@Configuration 注解的封装,与@Configuration 作用相同。
- @Configuration // 通过javaConfig的方式来添加组件到 IoC 容器中
- @EnableAutoConfiguration
- @AutoConfigurationPackage // 扫描@ComponentScan指定的路径下的类,添加到IOC
- @Import(AutoConfigurationImportSelector.class) // 将META-INF/spring.factories中定义的bean 添加到 IoC 容器中
- @ComponentScan // 包扫描。
- 默认扫描的包的根路径是 Spring Boot 主程序启动类所在包的位置。
- 在扫描过程中由前面介绍的 @AutoConfigurationPackage 注解进行解析,从而得到 Springboot 项目主程序启动类所在包的具体位置。
@SpringBootConfiguration
简介
对@Configuration 注解的封装,标注当前类是配置类,并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中。
源码
package org.springframework.boot; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.AliasFor; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { @AliasFor( annotation = Configuration.class ) boolean proxyBeanMethods() default true; }
@EnableAutoConfiguration
总结
Springboot 底层实现自动装配的步骤是:
- Springboot 应用启动;
- @SpringBootApplication 起作用;
- @EnableAutoConfiguration;
- @AutoConfigurationPackage: 这个组合注解主要是 @Import(AutoConfigurationPackages.Registrar.class), 它通过将 Registrar 类导入到容器中,而 Registrar 类作用是扫描主配置类同级目录及其子包,并将相应的组件导入到 Springboot创建管理容器中
- @Import(AutoConfigurationImportSelector.class): 它通过将 AutoConfigurationImportSelector 类导入到容器中,AutoConfigurationImportSelector 类作用是通过selectImports方法中,使用内部工具类SpringFactoriesLoader查找 classpath 上所用 jar 包中的 MATE-INF/spring.factories 进行加载, 实现将配置类信息交给 SpringFactory 加载器进行一系列的容器创建过程。
简介
@EnableAutoConfiguration 注解表示开启自动配置功能。
@Import注解用于导入配置类,本处导入AutoConfigurationImportSelector。容器刷新时,会调用AutoConfigurationImportSelector类的selectImports方法,扫描META-INF/spring.factories文件,将其中org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的配置项通过反射实例化为对应的标注了@Configuration 的JavaConfig形式的配置类,并加载到 IoC 容器中。
@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 {}; String[] excludeName() default {}; }
@AutoConfigurationPackage
自动配置包
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited // 导入 Registrar 组件类 @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { }
public abstract class AutoConfigurationPackages { static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 将主程序类所在包及其子包下的组件扫描到 Spring 容器中 register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0])); } @Override public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new PackageImports(metadata)); } } // 其他代码 }
@Import(AutoConfigurationImportSelector.class)
将 AutoConfigurationImportSelector 这个类导入到 Spring 容器中。AutoConfigurationImportSelector 可以帮助 SpringBoot 应用将所有符合条件的配置都加载到当前 SpringBoot 创建并使用的 IoC 容器(ApplicationContext) 中。
这个类中是通过 selectImports() 这个方法告诉 SpringBoot 都需要导入哪些组件
selectImports //给容器中导入组件
简述
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { // 这个方法告诉springboot都需要导入那些组件 @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { //判断 enableautoconfiguration注解有没有开启,默认开启(是否进行自动装配) if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } //1. 加载配置文件META-INF/spring-autoconfigure-metadata.properties,从中获取所有支持自动配置类的条件 //作用:SpringBoot使用一个Annotation的处理器来收集一些自动装配的条件,那么这些条件可以在META-INF/spring-autoconfigure-metadata.properties进行配置。 // SpringBoot会将收集好的@Configuration进行一次过滤进而剔除不满足条件的配置类 // 自动配置的类全名.条件=值 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } //其他方法 }
详解
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { // 判断 enableautoconfiguration注解有没有开启,默认开启(是否进行自动装配) if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } // 获取自动配置项 AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); // 获取所有默认支持的自动配置类名列表 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // 去除重复的配置类,自己写的starter 可能存在重复的 configurations = removeDuplicates(configurations); // 若不希望某些自动配置类自动配置,可通过EnableAutoConfiguration的exclude或excludeName属性进行配置, // 也可在配置文件里通过配置项“spring.autoconfigure.exclude”进行配置。 Set<String> exclusions = getExclusions(annotationMetadata, attributes); // 校验排除类(exclusions指定的类必须是自动配置类,否则抛出异常) checkExcludedClasses(configurations, exclusions); // 从 configurations 中,移除所有不希望自动配置的配置类 configurations.removeAll(exclusions); // 筛选候选的自动配置类,根据项目pom.xml中加入的依赖筛选出最终符合当前项目运行环境对应的自动配置类 // 判断是否要加载某个类的两种方式: // 根据spring-autoconfigure-metadata.properties进行判断。 // 判断@Conditional是否满足 configurations = getConfigurationClassFilter().filter(configurations); 将自动配置导入事件通知监听器 // 过滤完成后会自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类, // 并触发fireAutoConfigurationImportEvents事件。 fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); } protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // getSpringFactoriesLoaderFactoryClass 获取默认的 EnableAutoConfiguration.class 类名,传入 loadFactoryNames 方法 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; } // 默认的 EnableAutoConfiguration.class 类名 protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; } }
public final class SpringFactoriesLoader { public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { // 如果类加载器不为 null,则加载"META-INF/spring.factories",将其中设置的配置类的全路径信息封装为 Enumeration Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); // 循环 Enumeration 类对象,根据相应的节点信息生成 Properties 对象, // 通过传入的键获取值,在将值切割为一个个小的字符串转化为 Array,方法result集合中 while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } } }
- 首先注意到 selectImports 方法,其实从方法名就能看出,这个方法用于给容器中导入组件,然后跳到 getAutoConfigurationEntry 方法就是用于获取自动配置项的。
- getCandidateConfigurations 方法就是 获取一个自动配置 List ,这个 List 就包含了所有的自动配置的类名 。
- SpringFactoriesLoader#loadFactoryNames :跳转到 loadSpringFactories 方法发现 ClassLoader 类加载器指定了一个 FACTORIES_RESOURCE_LOCATION 常量。
- 然后利用PropertiesLoaderUtils 把 ClassLoader 扫描到的这些文件的内容包装成 properties 对象,从 properties 中获取到 EnableAutoConfiguration.class 类(类名)对应的值,然后把他们添加在容器中。
默认配置
简介
说明
springboot里边已经有了很多默认的配置,对应的依赖为:spring-boot-starter-xxx,对于这些依赖,大多数情况下,导入它们就可以直接使用,不需要加@EnableXxx。
springboot默认支持的spring-boot-starter-xxx对应的自动配置类见:自动配置类(英文官网)
实例分析:AOP自动配置
配置类
在上边“简介”中已经说过如何找配置类,本处spring-boot-starter-aop的配置类为:
spring-boot-autoconfigure-2.3.0.RELEASE.jar包里的org/springframework/boot/autoconfigure/aop/AopAutoConfiguration.class
package org.springframework.boot.autoconfigure.aop; import org.aspectj.weaver.Advice; import org.springframework.aop.config.AopConfigUtils; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration * Auto-configuration} for Spring's AOP support. Equivalent to enabling * {@link EnableAspectJAutoProxy @EnableAspectJAutoProxy} in your configuration. * <p> * The configuration will not be activated if {@literal spring.aop.auto=false}. The * {@literal proxyTargetClass} attribute will be {@literal true}, by default, but can be * overridden by specifying {@literal spring.aop.proxy-target-class=false}. * * @author Dave Syer * @author Josh Long * @since 1.0.0 * @see EnableAspectJAutoProxy */ @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true) public class AopAutoConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnClass(Advice.class) static class AspectJAutoProxyingConfiguration { @Configuration(proxyBeanMethods = false) @EnableAspectJAutoProxy(proxyTargetClass = false) @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false) static class JdkDynamicAutoProxyConfiguration { } @Configuration(proxyBeanMethods = false) @EnableAspectJAutoProxy(proxyTargetClass = true) @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true) static class CglibAutoProxyConfiguration { } } @Configuration(proxyBeanMethods = false) @ConditionalOnMissingClass("org.aspectj.weaver.Advice") @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true) static class ClassProxyingConfiguration { ClassProxyingConfiguration(BeanFactory beanFactory) { if (beanFactory instanceof BeanDefinitionRegistry) { BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); } } } }
分析
从上边代码可以看到如下两点:
aop的开启与关闭
类上有注解:@ConditionalOnProperty(prefix = “spring.aop”, name = “auto”, havingValue = “true”, matchIfMissing = true)
也就是说,默认情况下,会启用aop。(可通过spring.aop.auto=false关闭)
AspectJ的开启
@Configuration(proxyBeanMethods = false) @ConditionalOnClass(Advice.class) static class AspectJAutoProxyingConfiguration{ }
添加spring-boot-starter-aop依赖后,会自动引入aspectjweaver-1.9.4.jar,而aspectjweaver-1.9.4.jar里边有Advice.class。所以,@ConditionalOnClass这个注解需要的条件已经满足了。
CGLIB的开启
@Configuration(proxyBeanMethods = false) @EnableAspectJAutoProxy(proxyTargetClass = true) @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true) static class CglibAutoProxyConfiguration { }
也就是说,如果不配置apring.aop.proxy-target-class,会使用CGLIB,而且,@EnableAspectJAutoProxy(proxyTargetClass = true)会生效。
也在另一处得到验证:在spring-boot-autoconfigure-2.3.0.RELEASE.jar的META-INFO/spring-configuration-metadata.json中找到如下配置信息:
请先
!