目前Spring Boot有多火,相信大家都能感受到,SpringBoot仿佛现在成为一个java开发中必备的技能之一。另外一方面,其实真正只有使用过的人才知道Spring Boot的爽快,那是一种享受。但是想做一个合格的、有优秀的java开发者,Spring Boot其背后的相关原理也是不得不掌握的。所以这一篇中我们来说Spring Boot的配置。 依赖配置的处理在使用SpringMVC的时候,咱们会涉及到大量的配置、大量的依赖。但是Spring Boot的依赖是怎么样的呢?麻烦吗? 人总是懒惰的,各种各样的工具出现都是为人们的懒惰而出现的。 有了洗衣机就不用手洗衣服了。 有了车就不用走路了。 有了电话就不用写信了。 …..
看看Spring Boot的pom文件,我们可以看到 pom 文件中的 parent,点击 spring-boot-starter-parent: <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath /> </parent>
点击spring-boot-starter-parent进去之后发现。 熟悉的配置文件yml、yaml、properties。另外看到有这一段parent <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.1.6.RELEASE</version> <relativePath>../../spring-boot-dependencies</relativePath> </parent>
spring-boot-dependencies顾名思义,Spring Boot 的相关引用。点击spring-boot-dependencies进去 发现parent有<properties>值 这里就是为我们配置好了常用 jar 的版本。所以我们不用显性的配置版本号。(这里太多了只截图了部分,有兴趣按照我这步骤自行查看)。 如何实现自动配置回到我们代码中的启动类里。 @SpringBootApplication public class BlogApplication { public static void main(String[] args) { SpringApplication.run(BlogApplication.class, args); } }
这里面有个神奇的main函数,还有一个注解 @SpringBootApplication ,大家都知道注解的作用就是用来标记的,表示在某一时刻做某件事。我们点进去看下。 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented//可以记录在javac中 @Inherited//可以被子类击沉改注解 @SpringBootConfiguration//标明改类为配置类 @EnableAutoConfiguration//启动自动装配功能 //扫描(前面文中中有说过) @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication {
/** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */ @AliasFor(annotation = EnableAutoConfiguration.class) Class<?>[] exclude() default {};
可以看出注解 @SpringBootApplication 其实使用三个核心注解组合而成的。 @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan
`@SpringBootConfiguration`第一个注解@SpringBootConfiguration 就是一个标记类为配置类.可以被组件扫描器扫描到,和 @Configuration 注解功能相同。 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { }
@EnableAutoConfiguration第二个注解@EnableAutoConfiguration ,该 注解是启动自动配置功能。 这个注解是 Spring Boot 框架最重要的注解,也是实现自动化配置的注解。我们看下源码: @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 和 @Import。 @AutoConfigurationPackage注解@AutoConfigurationPackage 。这个注解的作用就是自动配置包。 看下源码: @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited //spring 框架的底层注解,它的作用就是给容器导入某个组件类 //这里就是将Registrar组件类导入到容器中 @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { }
Registrar再看看Registrar 类的源码,该类是一个静态内部类, org.springframework.boot.autoconfigure.AutoConfigurationPackages 中的静态内部类
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { //获取到的是项目主程序启动类所在的目录 // @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //默认将会扫描@SpringBootApplication注解标注的主配置类所在的包 //以及其子包下所有组件 register(registry, new PackageImport(metadata).getPackageName()); } @Override public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new PackageImport(metadata)); }
}
这个类 registerBeanDefinitions 方法会将主程序类所在你的包以及子包下的组件都扫描到 spring 容器中。所以我们在项目进行包目录制定的时候,需要有个规范,这样才能被扫描到。 @Import这个注解,其实我们上面看到了,就是将资源引入到容器中。这里是 @Import(AutoConfigurationImportSelector.class)
也就是说将 AutoConfigurationImportSelector 加载到组件中,我们看下这个类中的这个方法,这个方法就是将Spring Boot需要的组件都导入进来。 @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { //判断enableautoconfiguration注解是否已经开启。 //默认开启 if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } //加载配置文件META_INF/spring-autoconfigure-metadata.properties AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }
主要就是加载组件,哪会加载什么组件呢?就在这个 loadMetadata 这个方法中。这个方法中传递了当前的类的构造器,看看这个方法。 final class AutoConfigurationMetadataLoader {
protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";
private AutoConfigurationMetadataLoader() { }
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); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex); } }
可以发现,这个方法就是加载 spring-autoconfigure-metadata.properties
这个文件,获取需要自动加载的类路径进行自动装载。可以看下这个配置文件。
我这里版本是2.1.6RELEASE。继续看看该配置文件的内容有些什么; 这个配置文件中都是需要装载的类的路径。所以这里就说明了 loadMetadata 这个方法就是加载这些配置信息,但是加载完做什么呢?所以就是下一步 getAutoConfigurationEntry 方法了,这个方法就是获得 AutoConfigurationEntry 对象。 继续看下一个方法 protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { //判断是否开启注解,如果开启则返回空对象 if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } //获得注解的属性 AnnotationAttributes attributes = getAttributes(annotationMetadata); //这个方法是用来获取默认支持的自动配置类名列表 //spring Boot在启动的时候,使用内部工具类SpringFactoriesLoader,查找classpath下所有jar包中 //的META_INF/spring.factories,然后将其值作为自动配置类导入到容器中,自动装配类这样就会生效了。 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); //去除重复的配置类,如我们自己写的starter可能存在重复 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); }
这个方法就是获取 AutoConfigurationEntry 对象,先回去到 configurations。我们进入这个 getCandidateConfigurations protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { 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; }
可以看到这里实际加载的就是 META-INF/spring.factories 文件,所以想要实现自动加载,需要在这个文件中进行配置。
总结一下: Spring Boot 底层实现自动配置的流程: Spring Boot 应用启动 @SpringBootApplication 注解执行 @EnableAutoConfifiguration AutoConfifigurationImportSelector.class 执行,它通过 selectImports,查找 classpath 上所有 jar 包中的 META-INF/spring.factories 进行加载,实现将配置类信息交给 springFactory 加载器进行一系列的容器创建过程。
其实我们还有一个注解没讲,@ComponentScan 这个注解就是包扫描器,用来指定扫描器要从哪个包开始扫描。 可以参考:SpringBoot如何使用注解装配Bean 上面说了一堆,都是本文的重点,但是大家可以了解下,知道为什么 Spring Boot 不用我们在写大量的配置了,是因为 Spring Boot 在启动的时候把我们都加载好了,但是要实现这样的效果,就需要保证制定的 starter 满足一定的规范,比如必须在 starter 中的 META-INF/spring.factories 进行配置。 另外如何自定义一个starter,请参考前面的文章: Spring Boot 如何手写stater
|