springboot自动配置的原理

水深无声 2023-01-02 03:24 268阅读 0赞

SpringBoot的核心就是自动配置,自动配置又是基于条件判断来配置Bean。关于自动配置的源码在spring-boot-autoconfigure-2.0.0.RELEASE.jar

在讲springBoot自动配置之前我们应该思考,为什么要有自动配置,有什么好处呢?

为什么要有springboot自动配置?

直接文字说明可能不直观,理解起来也吃力,我们用例子来证明体现。

Spring搭建一个项目

如果我们用传统的spring来搭建一个SSM框架:

核心配置类:

spring-web.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
  4. xmlns:mvc="http://www.springframework.org/schema/mvc"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/context
  8. http://www.springframework.org/schema/context/spring-context.xsd
  9. http://www.springframework.org/schema/mvc
  10. http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
  11. <!-- 配置SpringMVC -->
  12. <!-- 1.开启SpringMVC注解模式 -->
  13. <!-- 简化配置: (1)自动注册DefaultAnootationHandlerMapping,AnotationMethodHandlerAdapter
  14. (2)提供一些列:数据绑定,数字和日期的format @NumberFormat, @DateTimeFormat, xml,json默认读写支持 -->
  15. <mvc:annotation-driven />
  16. <!-- 2.静态资源默认servlet配置 (1)加入对静态资源的处理:js,gif,png (2)允许使用"/"做整体映射 -->
  17. <mvc:resources mapping="/resources/**" location="/resources/" />
  18. <mvc:default-servlet-handler />
  19. <!-- 3.定义视图解析器 -->
  20. <bean id="viewResolver"
  21. class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  22. <property name="prefix" value="/WEB-INF/html/"></property>
  23. <property name="suffix" value=".html"></property>
  24. </bean>
  25. <!-- 在spring-mvc.xml文件中加入这段配置后,spring返回给页面的都是utf-8编码了 -->
  26. <bean
  27. class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
  28. <property name="messageConverters">
  29. <list>
  30. <bean
  31. class="org.springframework.http.converter.StringHttpMessageConverter">
  32. <property name="supportedMediaTypes">
  33. <list>
  34. <value>text/html;charset=UTF-8</value>
  35. </list>
  36. </property>
  37. </bean>
  38. </list>
  39. </property>
  40. </bean>
  41. <!-- 4.扫描web相关的bean -->
  42. <context:component-scan base-package="com.xiateng.web" />
  43. </beans>

web.xml

  1. <servlet>
  2. <servlet-name>spring-dispatcher</servlet-name>
  3. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  4. <init-param>
  5. <param-name>contextConfigLocation</param-name>
  6. <param-value>classpath:spring/spring-*.xml</param-value>
  7. </init-param>
  8. </servlet>
  9. <servlet-mapping>
  10. <servlet-name>spring-dispatcher</servlet-name>
  11. <!-- 默认匹配所有请求 -->
  12. <url-pattern>/</url-pattern>
  13. </servlet-mapping>

可以看到,这里用到两个配置文件,如果要集成更多第三方技术配置会更复杂、繁重。

SpringBoot搭建一个项目pring

相对于Spring,新建一个spingBoot项目无需任何配置就可启动一个项目。

具体新建流程就不说了,有兴趣可以看看这篇:https://blog.csdn.net/qq_43037478/article/details/90031380

我们会看到,创建好的项目的pom文件里自动引入了几个jar包,我们先看下面这个父依赖

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>2.0.0.RELEASE</version>
  5. </parent>

点进去会发现里面会有个dependencies,里面其实就是很多jar包的版本号(由于文件比较长,这里只截图部分。)

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzMDM3NDc4_size_16_color_FFFFFF_t_70

这里我们可以得到一个结论,就是我们引入依赖的时候不需要知道版本,因为SpringBoot的父依赖里已经帮我们维护了一套版本

父依赖里继续往下翻,会看到里面帮我们写好了资源库,不用我们自己去配置

  1. <resources>
  2. <resource>
  3. <filtering>true</filtering>
  4. <directory>${basedir}/src/main/resources</directory>
  5. <includes>
  6. <include>**/application*.yml</include>
  7. <include>**/application*.yaml</include>
  8. <include>**/application*.properties</include>
  9. </includes>
  10. </resource>
  11. <resource>
  12. <directory>${basedir}/src/main/resources</directory>
  13. <excludes>
  14. <exclude>**/application*.yml</exclude>
  15. <exclude>**/application*.yaml</exclude>
  16. <exclude>**/application*.properties</exclude>
  17. </excludes>
  18. </resource>
  19. </resources>

启动器,我这里用的是下面这个跟mybatis整合的

  1. <dependency>
  2. <groupId>org.mybatis.spring.boot</groupId>
  3. <artifactId>mybatis-spring-boot-starter</artifactId>
  4. <version>1.3.1</version>
  5. </dependency>

引入这个依赖的同时会自动引入jdbc,autoconfigure,starter等相关依赖,这里就不细说了

不难看出SpringBoot相比于Spring创建项目的优势:

  • 遵循”约定优于配置”的原则,使用springboot的时候只需要少量配置即可,大部分时候我们可以使用默认配置。
  • 项目搭建速度非常快,没有繁重的配置,而且不容易出错。
  • 无需配置就可整合第三方框架
  • 内嵌Servlet如:Tomcat日期,应用可以直接jar包运行

下面我们着重讲下SpringBoot的自动配置

首先我们找到入口:启动类(该启动类的核心就是@SpringBootApplication注解)

  1. // exclude = {DataSourceAutoConfiguration.class} 排除主动注入数据源
  2. @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
  3. public class Application extends WebMvcConfigurerAdapter{
  4. public static void main(String[] args) {
  5. SpringApplication.run(Application.class,args);
  6. }
  7. }

@SpringBootApplication其实是个组合注解,我们只需要关心其中的@SpringBootConfiguration注解和@EnableAutoConfiguration注解

  1. @SpringBootConfiguration // 核心
  2. @EnableAutoConfiguration // 核心
  3. @ComponentScan(excludeFilters =
  4. {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}
  5. ), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class}
  6. )})
  7. public @interface SpringBootApplication {
  8. }

@SpringBootConfiguration注解内容

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Configuration // 核心
  5. public @interface SpringBootConfiguration {
  6. }

可以看到,SpringBootConfiguration其实就携带了一个@Configuration注解,这个注解详细大家都很熟悉了,他代表自己是一个spring的配置类。

@EnableAutoConfiguration注解内容

  1. @AutoConfigurationPackage
  2. @Import({AutoConfigurationImportSelector.class}) // 核心
  3. public @interface EnableAutoConfiguration {
  4. String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
  5. Class<?>[] exclude() default {};
  6. String[] excludeName() default {};
  7. }

可以看到,在@EnableAutoConfiguration注解内使用到了@import注解来完成导入配置的功能,而EnableAutoConfigurationImportSelector内部则是使用了SpringFactoriesLoader.loadFactoryNames方法进行扫描具有META-INF/spring.factories文件的jar包。下面是2.0.0.RELEASE实现源码:

  1. public String[] selectImports(AnnotationMetadata annotationMetadata) {
  2. if (!this.isEnabled(annotationMetadata)) {
  3. return NO_IMPORTS;
  4. } else {
  5. try {
  6. AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
  7. AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
  8. //扫描具有META-INF/spring.factories文件的jar包
  9. List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
  10. //去重
  11. configurations = this.removeDuplicates(configurations);
  12. // 排序
  13. configurations = this.sort(configurations, autoConfigurationMetadata);
  14. // 删除需要排除的类,如:启动类上的@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
  15. Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
  16. this.checkExcludedClasses(configurations, exclusions);
  17. configurations.removeAll(exclusions);
  18. configurations = this.filter(configurations, autoConfigurationMetadata);
  19. this.fireAutoConfigurationImportEvents(configurations, exclusions);
  20. return StringUtils.toStringArray(configurations);
  21. } catch (IOException var6) {
  22. throw new IllegalStateException(var6);
  23. }
  24. }
  25. }

加载spring.factories文件的类

所有的配置都存放在configurations中, 而这些配置都从getCandidateConfiguration中获取, 这个方法是用来获取候选的配置。

  1. protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  2. List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
  3. 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.");
  4. return configurations;
  5. }

实际上它返回了一个List,这个List是由loadFactoryNames()方法返回的,其中传入了一个getSpringFactoriesLoaderFactoryClass(),我们可以看看这个方法的内容。

  1. protected Class<?> getSpringFactoriesLoaderFactoryClass() {
  2. return EnableAutoConfiguration.class;
  3. }

发现没有?它实际上就是返回所有标注了这个类的所有包,而标注了这个类的包不就是@SpringBootApplication吗

从而我们可以得出结论:饶了一圈其实就是为了将启动类所需的所有资源导入。

spring.factories内容:一个key对应一个集合类(注:自动配置只会读取key为EnableAutoConfiguration下的集合类)

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzMDM3NDc4_size_16_color_FFFFFF_t_70 1

总结

SpringBoot所有自动配置类都是在启动的时候进行扫描并加载,通过spring.factories可以找到自动配置类的路径,但是不是所有存在于spring,factories中的配置都进行加载,而是通过@ConditionalOnClass注解进行判断条件是否成立(只要导入相应的stater,条件就能成立),如果条件成立则加载配置类,否则不加载该配置类。

发表评论

表情:
评论列表 (有 0 条评论,268人围观)

还没有评论,来说两句吧...

相关阅读

    相关 SpringBoot自动配置原理

    SpringBoot可以简化开发的一个主要原因就是采用了默认配置,所谓约定大于配置就是这个意思。在没有自己指定配置的时候使用默认配置的原理大致如下。如有错误,还请指正。 ==

    相关 SpringBoot自动配置原理

            配置文件到底能写什么?怎么写?自动配置原理; [配置文件能配置的属性参照][Link 1] 1、自动配置原理: 1)、SpringBoot启动的时候加