写点什么

【原创】Spring Boot 一口气说自动装配与案例

用户头像
田维常
关注
发布于: 2020 年 11 月 04 日

关注公众号Java 后端技术全栈”**


回复“面试”获取全套大厂面试资料


今天我们来说说 Spring Boot 的核心——自动装配原理。


大家都记得我们 SpringBoot 项目有个启动类XXApplication.java类。下面就是启动类:


import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class DemoApplication {    public static void main(String[] args) {        SpringApplication.run(DemoApplication.class, args);    }}
复制代码


其中这里有核心注解 @SpringBootApplication


注解 @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 {//先省略}
复制代码


它主要加载了 @SpringBootApplication 注解主配置类,这个 @SpringBootApplication 注解主配置类里边最主要的功能就是 SpringBoot 开启了一个 @EnableAutoConfiguration 注解的自动配置功能。


这里面的重要关注的三个注解:


@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan


注解 @EnableAutoConfiguration:


翻译过来就是开启自动配置,源码如下


@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration {//省略}
复制代码


它主要利用了一个 EnableAutoConfigurationImportSelector 选择器给 Spring 容器中来导入一些组件。其中的关键功能由 @Import 提供,其导入的 AutoConfigurationImportSelector 的 selectImports()方法通过 SpringFactoriesLoader.loadFactoryNames()扫描所有具有 META-INF/spring.factories 的 jar 包。spring-boot-autoconfigure-x.x.x.x.jar 里就有一个这样的 spring.factories 文件。


这个 spring.factories 文件也是一组一组的 key=value 的形式,其中一个 key 是 EnableAutoConfiguration 类的全类名,而它的 value 是一个 xxxxAutoConfiguration 的类名的列表,这些类名以逗号分隔,如下图所示:



再来看看 spring.factories 的内容



每一个 xxxAutoConfiguration 类都是容器中的一个组件,并都加入到容器中。加入到容器中之后的作用就是用它们来做自动配置,这就是 Springboot 自动配置之源,也就是自动配置的开始,只有这些自动配置类进入到容器中以后,接下来这个自动配置类才开始进行启动。


这个 @EnableAutoConfiguration 注解通过 @SpringBootApplication 被间接的标记在了 Spring Boot 的启动类上。在 SpringApplication.run(…)的内部就会执行 selectImports()方法,找到所有 JavaConfig 自动配置类的全限定名对应的 class,然后将所有自动配置类加载到 Spring 容器中。


先看 EnableAutoConfigurationImportSelector 的类图



重要方法


protected Collection<String> loadFactoryNames(Class<?> source) {        return SpringFactoriesLoader.loadFactoryNames(source,                getClass().getClassLoader());}
复制代码


public final class SpringFactoriesLoader {    //读取配置文件META-INF下的spring.factories    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {        String factoryClassName = factoryClass.getName();        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, 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 {            Enumeration<URL> urls = (classLoader != null ?                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));            result = new LinkedMultiValueMap<>();            while (urls.hasMoreElements()) {                URL url = urls.nextElement();                UrlResource resource = new UrlResource(url);                Properties properties = PropertiesLoaderUtils.loadProperties(resource);                for (Map.Entry<?, ?> entry : properties.entrySet()) {                    String factoryClassName = ((String) entry.getKey()).trim();                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {                        result.add(factoryClassName, factoryName.trim());                    }                }            }            cache.put(classLoader, result);            return result;        }        catch (IOException ex) {            throw new IllegalArgumentException("Unable to load factories from location [" +                    FACTORIES_RESOURCE_LOCATION + "]", ex);        }    }}
复制代码


读取出来后如何使其生效呢?


自动配置生效


每一个 XxxxAutoConfiguration 自动配置类都是在某些条件之下才会生效的,这些条件的限制在 Spring Boot 中以注解的形式体现,常见的条件注解有如下几项:


@ConditionalOnBean:当容器里有指定的 bean 的条件下。

@ConditionalOnMissingBean:当容器里不存在指定 bean 的条件下。

@ConditionalOnClass:当类路径下有指定类的条件下。

@ConditionalOnMissingClass:当类路径下不存在指定类的条件下。

@ConditionalOnProperty:指定的属性是否有指定的值,比如 @ConditionalOnProperties(prefix=”xxx.xxx”, value=”enable”, matchIfMissing=true),代表当 xxx.xxx 为 enable 时条件的布尔值为 true,如果没有设置的情况下也为 true。


以 ServletWebServerFactoryAutoConfiguration 配置类为例,解释一下全局配置文件中的属性如何生效,比如:server.port=8081,是如何生效的(当然不配置也会有默认值,这个默认值来自于 org.apache.catalina.startup.Tomcat)。


@Configuration@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)@ConditionalOnClass(ServletRequest.class)@ConditionalOnWebApplication(type = Type.SERVLET)@EnableConfigurationProperties(ServerProperties.class)@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,        ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,        ServletWebServerFactoryConfiguration.EmbeddedJetty.class,        ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })public class ServletWebServerFactoryAutoConfiguration {    @Bean    public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(            ServerProperties serverProperties) {        return new ServletWebServerFactoryCustomizer(serverProperties);    }    @Bean    @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")    public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(            ServerProperties serverProperties) {        return new TomcatServletWebServerFactoryCustomizer(serverProperties);    }//省略}
复制代码


这里看到一个 tomcat servlet 相关的类了。


public class TomcatServletWebServerFactoryCustomizer        implements WebServerFactoryCustomizer<TomcatServletWebServerFactory>, Ordered {    private final ServerProperties serverProperties;    public TomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {        this.serverProperties = serverProperties;    }//省略}
复制代码


再进 serverProperties 中,终于发现一个熟悉的注解 @ConfigurationProperties 了,读取配置文件中 server 为前缀的配置项。


ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)public class ServerProperties {    private Integer port;    private InetAddress address;    //省略}
复制代码


@EnableConfigurationProperties 负责导入这个已经绑定了属性的 bean 到 spring 容器中(见上面截图)。那么所有其他的和这个类相关的属性都可以在全局配置文件中定义,也就是说,真正“限制”我们可以在全局配置文件中配置哪些属性的类就是这些 XxxxProperties 类,它与配置文件中定义的 prefix 关键字开头的一组属性是唯一对应的。


至此,我们大致可以了解。在全局配置的属性如:server.port 等,通过 @ConfigurationProperties 注解,绑定到对应的 XxxxProperties 配置实体类上封装为一个 bean,然后再通过 @EnableConfigurationProperties 注解导入到 Spring 容器中。


而诸多的 XxxxAutoConfiguration 自动配置类,就是 Spring 容器的 JavaConfig 形式,作用就是为 Spring 容器导入 bean,而所有导入的 bean 所需要的属性都通过 xxxxProperties 的 bean 来获得。


借用网上一张图来回顾整个流程:



解锁面试被问


面试官可能看到你简历上写着掌握 SpringBoot,通常都会让你说说 Spring Boot 自动装配原理。如果你能把上面过程中的源码讲一遍给他听,那是很 NB 了。但是通常只要如下这样回答就可以了。


Spring Boot 启动的时候会通过 @EnableAutoConfiguration 注解找到 META-INF/spring.factories 配置文件中的所有自动配置类,并对其进行加载,而这些自动配置类都是以 AutoConfiguration 结尾来命名的,它实际上就是一个 JavaConfig 形式的 Spring 容器配置类,它能通过以 Properties 结尾命名的类中取得在全局配置文件中配置的属性如:server.port,而 XxxxProperties 类是通过 @ConfigurationProperties 注解与全局配置文件中对应的属性进行绑定的。


ok,今天就聊到这里。


码字不易,点个在看+分享朋友圈呗!!!


推荐阅读


如何优雅的导出 Excel


终于明白为什么要加 final 关键字了!



发布于: 2020 年 11 月 04 日阅读数: 41
用户头像

田维常

关注

关注公众号:Java后端技术全栈,领500G资料 2020.10.24 加入

关注公众号:Java后端技术全栈,领500G资料

评论

发布
暂无评论
【原创】Spring Boot一口气说自动装配与案例