史上最烂 spring boot 原理分析

盗引·禁篇·spring boot

spring boot 启动过程、内嵌 tomcat 容器、条件装配、自动配置等。


版本

  • jdk:8
  • spring boot:2.7.0

1 spring boot 启动过程

  spring boot 是 spring 什么什么诸如此类等等吧啦吧啦一大堆的框架。

  spring boot 启动过程主要分为两大块,即是 SpringApplication 实例构造和 SpringApplication.run() 方法的执行。

1.1 SpringApplication 实例构造
  • 1、根据引导类(启动类)记录 bean definition 源。如 注解、xml 等。
  • 2、根据类路径推断 application type,如 servlet。reactive、none。
  • 3、添加 application 初始化器。
  • 4、添加 application 事件监听器。
  • 5、推断主类。
1.2 SpringApplication.run() 方法执行
  • 1、获取 application 事件发布器。

    • 发布 application 启动事件(application starting)。
  • 2、封装启动参数 args。

  • 3、准备环境。

    • a、创建 application 环境,并添加命令行参数源。

      即准备 ApplicationEnvironment 对象,继承自 spring 的 StandardEnvironment。其作用是整合应用程序中的所有配置,如系统变量、本地配置、命令行参数、properties、yaml 等,且可以通过 addFirst、addLast、addBedore、addAfter 等方法设置参数生效的优先级。其创建时默认会添加两种变量,即 systemProperties 和 systemEnvironment,分别代表系统配置和本地 java 配置。然后添加命令行参数,即 commandLineArgs。

    • b、添加配置属性解析源。

      即添加 ConfigurationProperties,其作用为不标准的键命名提供值获取方式,如根据键的别名、键的前缀等方式获取值。

      • 发布环境已准备事件(application environmentPrePared)。
    • c、环境已准备事件触发环境后置处理器监听器,会以后置处理器的方式补充配置源(如添加 properties、yaml 等)。

      spring boot 定义了环境后置处理器 EnvironmentPostProcessor,并提供了很多子实现,如 ConfigDataEnvironmentPostProcessor、RandomValuePropertySourceEnvironmentPostProcessor 等。在环境后置处理器事件监听器 EnvironmentPostProcessorApplicationListener 监听到事件发布器 EventPublishingRunListener 发布了 environmentPrePared 事件(即环境已准备事件)后,就会去执行环境后置处理器 EnvironmentPostProcessor 的所有子实现类,以此来达到添加环境配置的目的。

    • d、绑定 spring.main 配置到 SpringApplication 对象。

      这里涉及到了绑定器 Binder,其作用是将配置文件中的键值对绑定到 java 对象上,其有以下特性:

      • 可以实现不同命名格式的 key 的绑定,如将 first-name、first_name、firstName 绑定到 firstName 属性上。
      • 可以实现属性嵌套绑定,如将前缀为 user.location 的 key 的值绑定到 User 类的 Location 类类型中的属性上。
      • 可以为已存在的对象绑定属性值。
      • 可以为已知类型的类的属性绑定值。
  • 4、打印 banner。

    即使用 SpringApplicationBannerPrinter 来打印 banner,默认使用 SpringBootBanner 实现,当用户提供自定义标语文件时(banner.txt)则会打印自定义标语。

  • 5、根据 application type 创建对应的 spring ioc 容器。

  • 6、准备容器。

    • 发布容器已准备事件(application conextPrepared)。
    • a、加载 bean definition。
    • 发布容器已加载事件(application contextLoaded)。
  • 7、刷新容器。

    • 发布容器已刷新且 application 已启动事件(application started)。
  • 8、调用 runner 接口。

    • 发布 runner 已调用事件(application ready)。
  • 启动过程若出现异常,则发布 application 异常事件(application failed)。

2 内嵌 tomcat 容器

  tomcat 是 web 容器。

2.1 tomcat 目录结构

  tomcat 基本结构:

Server
└───Service
    ├───Connector   (连接器(协议、端口号) 代表请求是以什么协议、那个端口连接到 tomcat 服务器上)
    └───Engine
        └───Host   (虚拟主机 localhost)
            ├───Context1   (应用一: 设置虚拟路径,即 url 起始路径; 设置项目磁盘路径,即 docBase)
            │   │   index.html   (静态资源 (如 html、css))
            │   └───WEB-INF   (动态资源)
            │       │   web.xml   (配置 tomcat 能识别的三大组件: servlet、filter、listener 用来调用 controller、service 等 注: servlet3.0 中则不需要配置 web.xml 它可以通过编程的方式添加 servlet 等)
            │       ├───classes   (实际代码: servlet、controller、service...)
            │       ├───jsp   (jsp 目录 非必需)
            │       └───lib   (第三方 jar 包)
            └───Context2
                │   index.html
                └───WEB-INF
                    web.xml
2.2 tomcat 使用方式

  tomcat 使用方法:

  • a、创建 tomcat 实例。

    即创建 Tomcat 对象,并给其设置基本目录(tomcat 启动过程中会产生临时文件,将存放在该目录下)。

  • b、创建 tomcat 应用。

    即通过 Tomcat#addContext() 方法添加 tomcat 应用。添加此应用时需要一个应用目录,即项目的磁盘路径(docBase)。同时需要设置一个虚拟路径,即 url 其实路径,如 ‘/’。

  • c、添加 servlet。

    即通过 Context#addServletContainerInitializer() 方法添加 Servlet。即将项目中定义的 Servlet 注册到 tomcat 应用 Context 的实例中。针对每个 Servlet 需要设置一个映射路径。

  • d、启动 tomcat。

    即通过 Tomcat#start() 方法启动 tomcat。

  • e、创建连接器,设置监听端口。

    即创建 Connector。创建时需要指定使用的协议(如 Http11Nio2Protocol 则代表 http 协议)。同时可设置端口,最后将连接器设置到 tomcat 实例中。

  tomcat 使用方式示例:

public class HelloServlet extends HttpServlet {

    private static final long serialVersionUID = 5410576673136422878L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        resp.getWriter().write("<h3>hello servlet</>");
    }
}
public class TomcatTest {

    public static void main(String[] args) throws Throwable {

        // 1、创建 tomcat 实例
        Tomcat tomcat = new Tomcat();
        tomcat.setBaseDir("tomcat");

        // 2、创建项目目录
        File file = Files.createTempDirectory("tomcat-test").toFile();
        file.deleteOnExit();

        // 3、在项目目录下创建 tomcat 应用
        Context context = tomcat.addContext("", file.getAbsolutePath());

        // 4、编程添加 servlet
        context.addServletContainerInitializer((c, ctx) -> {
            // 给应用添加一个 servlet 并设置映射路径
            ctx.addServlet("hello", new HelloServlet()).addMapping("/hello");
        }, Collections.emptySet());

        // 5、启动 tomcat
        tomcat.start();

        // 6、创建连接器 设置监听端口
        Connector connector = new Connector(new Http11Nio2Protocol());
        connector.setPort(8081);
        tomcat.setConnector(connector);
    }
}
2.3 内嵌 tomcat

  spring web 的入口为 DispatcherServlet,DispatcherServlet 间接实现了 HttpServlet 接口,所以其本质上是个 servlet。而 tomcat 服务器使用时需要将 servlet 注册到 tomcat 的应用 Context 中,接收到请求后会根据映射将请求分发给 servlet 去处理。所以,DsipatcherServlet 可以直接注册到 tomcat 中。这是 spring web 与 tomcat web 服务器的联系。

  spring boot 内嵌 tomcat 的含义是 spring boot 框架为我们自动集成了 tomcat web 服务器。spring boot 在启动执行 SpringApplication.run() 方法的过程中会创建并刷新 spring ioc 容器(1.2.6 和 1.2.7),其实际上创建的是 AnnotationConfigServletWebServerApplicationContext(spring boot 提供的基于 java 配置的 servlet web 环境的容器实现,且该容器继承自 ServletWebServerApplicationContext)容器。

  在刷新容器的过程中(即调用 refresh() 方法时),会执行自定义刷新操作(即 onRefresh() 方法),这个自定义刷新操作是由子类实现的,这里的子类即 ServletWebServerApplicationContext,其在实现的 onRefresh() 中创建了 Tomcat 实例(Tomcat 实例由 ServletWebServerFactory 创建),并注册了 DispatcherServlet(由 DispatcherServletRegistryBean 注册)。

  在刷新容器的过程中(即调用 refresh() 方法时),会执行完成刷新操作(即 finishRefresh() 方法),这里也是调用的子类的实现,在子类实现中会启动 tomcat。

  注意,spring boot 启动时默认不会初始化 DispatcherServlet,而是在第一次请求时初始化。这个是由 spring.mvc.servlet.load-on-startup 配置控制的,其默认值为 -1,表示不会在 tomcat 启动时初始化 DispatcherServlet,当其大于 0 时表示在启动时初始化,且值越小,初始化优先级越高(spring web 设计允许同时存在多个 web 应用)。

3 条件装配

3.1 条件装配原理

  spring 中的条件装配是指当满足某些条件时才执行装配动作。如当 spring ioc 容器中存在某个 bean 时、类路径中存在某个类时等等这样的条件成立时才执行装配动作。spring 中的条件装配是由 @Conditional 注解和 Condition 接口实现的。其中 Condition 接口定义了匹配动作,即校验 spring ioc 容器中是否存在某个 bean、类路径中是否存在某个类等。@Conditional 注解会应用 Condition 接口的实现类,并会给其传入必要参数,如要检查的 bean name、要检查的类名称等。

3.2 spring boot 中的条件装配实现

  spring boot 为了自动配置的灵活性,提供了很多条件装配注解:

  • @ConditionalOnBean

    该注解的作用是仅当 spring 容器中存在指定的 bean 时才装配。可通过 bean 类型和 bean 名称进行指定,即 value 和 type 参数。

  • @ConditionalOnClass

    该注解的作用是仅当存在指定的类时才装配。可通过类路径(classpath)和类名称进行指定。即 value 和 name 的参数。

  • @ConditionalOnCloudPlatform

    该注解的作用是仅当指定的云平台处于活动状态时才装配。可通过 value 参数进行指定云平台。

  • @ConditionalOnExpression

    该注解的作用仅当指定的 spel 表达式返回 true 时才进行装配。可通过 value 参数进行指定 spel 表达式。

  • @ConditionalOnJava

    该注解的作用是仅当应用程序运行在指定的 jvm 版本上或运行在指定的 jvm 版本范围内时才进行装配。可通过 value 和 range 参数指定 jvm 版本或版本范围。

  • @ConditionalOnJndi

    该注解的作用是仅当指定位置的 Jndi 存在时或 InitialContext 存在时才进行装配。可通过 value 参数指定 Jndi 位置。

  • @ConditionalOnMissingBean

    该注解的作用是仅当 spring 容器没有指定的 bean 时才进行装配。可通过 bean 类型和 bean 名称进行指定,即 value / type(多个)和 name 参数。

  • @ConditionalOnMissingClass

    该注解的作用是仅当指定名称的类存在时才进行装配。可通过 value 参数指定类名称。

  • @ConditionalOnNotWebApplication

    该注解的作用是仅当当前应用上下文不是 web 应用上下文时才进行装配。

  • @ConditionalOnProperty

    该注解的作用是仅当指定的属性拥有指定值时才进行装配。默认情况下该属性必须存在于环境中。且不等于 false。还可以使用 havingValue 参数来指定属性值;当属性不存在于环境中时,可通过 matchIfMissing 参数指定重新匹配的属性名。另外,可通过 value、name 和 prefix 等参数来指定要匹配的属性名或前缀等。

  • @ConditionalOnResource

    该注解的作用是仅当指定的资源存在于 classpath 时才进行装配。可通过 resources 参数来指定资源。

  • @ConditionalOnSingleCandidate

    该注解的作用是仅当指定类的 bean 已经存在于 bean factory 中,且确定其是单例(如果在 bean factory 匹配到了多个 bean,但是有一个 primary(主要的)修饰的 bean 也适用)候选者时才进行装配。可通过 value 和 type 参数指定要检查的 bean 的类类型或类型名称,单二者不可同时使用。

  • @ConditionalOnWarDeployment

    该注解的作用是仅当当前应用是以传统的 war 包方式部署时才进行装配。但当该应用是嵌入式服务时则不适用。

  • @ConditionalOnWebApplication

    该注解的作用是仅当当前应用是 web 应用时才进行装配。默认情况下适用于所有 web 应用,可以通过 type 参数来指定 web 应用的范围,如所有 web 应用、基于 servlet 的 web 应用、响应式的 web 的应用等。

3.3 自定义条件注解

  简单自定义个条件注解,感受下其原理。该注解的作用是当存在或不存在某个类时才执行装配操作。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

  	// 要校验的类全名
    String className();

  	// 存在时装配还是不存在时装配
    boolean exists();
}
public class OnClassCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

      	// 获取 自定义注解 @ConditionalOnClass 的参数
        Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnClass.class.getName());
        if (CollectionUtils.isEmpty(attributes)) {
            throw new RuntimeException("The values of class-name and exists cannot be empty");
        }

        String className = (String) attributes.get("className");
        Boolean exists = (Boolean) attributes.get("exists");

      	// 判断是否存在指定的类
        boolean present = ClassUtils.isPresent(className, OnClassCondition.class.getClassLoader());

        return exists == present;
    }
}
// 使用
public class ConditionTest {
  	// 当类路径下存在 指定 Test 类时 注册 Zed bean
    @Configuration
    @ConditionalOnClass(className = "org.xgllhz.spring.boot.Test", exists = true)
    static class AutoConfigurationOne {

        @Bean
        public Zed zed() {
            return new Zed();
        }
    }

    // 当类路径下不存在 指定 Test 类时 注册 Fizz bean
    @Configuration
    @ConditionalOnClass(className = "org.xgllhz.spring.boot.Test", exists = false)
    static class AutoConfigurationTwo {

        @Bean
        public Fizz fizz() {
            return new Fizz();
        }
    }
}

4 自动配置

4.1 自动配置原理

  spring boot 项目相对于普通的 spring 项目来说简化了配置,便于开发。其宗旨是约定大于配置,看似降低了其灵活度,但却大大简化了开发过程。而其简化配置实际上是改变了配置,且由开发者手动配置转换为自动配置,同时支持自定义配置。而其核心则在于 @EnableAutoConfiguration 注解。

  @EnableAutoConfiguration 注解作用于 @SpringBootApplication 注解(@SpringBootApplication 注解由 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 三个注解组成)。@EnableAutoConfiguration 注解主要作用是开启自动配置。而其实现自动配置主要是由 @Import 注解和其注解值 AutoConfigurationImportSelector 类完成的。同时,该注解上还标注了 @AutoConfigurationPackage 注解,@AutoConfigurationPackage 注解通过 @Import 注解注入了 BasePackagesBeanDefinition bean,该 bean 维护了一个 basePackages 集合,该集合收录了当前项目中所有模块中启动类所在的包路径,spring ioc 创建单例 bean 时就去这些包路径下查找配置 bean。所以 @AutoConfigurationPackage 注解的作用可以理解为将当前模块的启动类所在的包路径存储起来,以便后续创建单例 bean 使用,这也是为什么项目中定义的配置 bean 要放在启动类所在包下。

  @Import 注解和 ImportSelector 接口可以实现自动导入的功能。ImportSelector 接口的 selectImports() 方法会返回一个需要导入的配置类的全类名数组,然后将其交给 spring ioc 以完成自动配置。

  spring boot 对 ImportSelector 的实现是 AutoConfigurationImportSelector 类。该类会去 jar 包下找 META-INF/spring.factories 文件,该文件以键值对的形式维护了需要导入的配置类。其 key 为 @EnableAutoConfiguration 注解的全类名,value 为待导入的配置类的全类名数组。selectImports() 会以 @EnableAutoConfiguration 注解的全类名为 key 去加载 META-INF/spring.factories 文件中的 vlaue 值,将匹配到的待导入的配置类的全名类返回。需要注意的是,spring boot 从 2.7.0 开始新增了一种更便捷的配置文件,即 META-INF/spring/ 目录下的 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,且以该配置文件为主(同时支持老版本的 spring.factories 配置文件)。

# META-INF/spring.factories 文件大概长这样
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.xgllhz.demo.auto.DemoAutoConfiguration,\
  org.xgllhz.demo.auto.TestAutoConfiguration
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件大概长这样
org.xgllhz.demo.auto.DemoAutoConfiguration
org.xgllhz.demo.auto.TestAutoConfiguration

  自动配置可以解决什么问题呢?当我们自定义了一个 jar 或需要将第三方 jar 引入当前项目,同时这些第三方 jar 中定义了一些 bean 需要被 spring ioc 管理,若没有自动配置,那岂不是要开发者将这些 bean 一个个注册到 spring ioc 容器中。但是当大家将配置 bean 都统一放在一个位置,spring boot 启动时将去约定的位置自动加载并注册这些 bean,这就是简化配置。

  至此,我们了解到 @EnableAutoConfiguration 注解共有两个作用,一是通过 @AutoConfigurationPackage 注解来加载当前项目中的配置,而是通过 @Import(AutoConfigurationImportSelector.class) 注解来加载第三方 jar 包中的配置。这就是自动配置。

4.2 使用自动配置

  通过上述分析可以知道,自动配置的本质是个配置类,即该配置类结合 @Import 注解和 ImportSelector 接口的实现类就可以实现自动配置。因此,有两种方式可以使用自动配置,一是使用 spring boot 已经实现的这一套,二是我们自定义实现一套。

4.2.1 使用 spring boot 自动配置

  其原理就是将配置类的类全名维护到 META-INF/spring.factories 文件或 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中即可。

  假如我们自定义的 jar 包中有以下配置类:

// 配置类一
package org.xgllhz.test.config;

@Configuration
public class ZedAutoConfiguration {
  	@Bean
  	public Zed zed() {
      	return new Zed();
    }
}

// 配置类二
package org.xgllhz.test.config;

@Configuration
public class FizzAutoConfiguration {
  	@Bean
  	public Fizz fizz() {
      	return new Fizz();
    }
}

  则需要在 META-INF/spring.factories 文件中如此配置即可:

# 注意此时的 key 一定得是 @EnableAutoConfiguration 的全类名
# 即 org.springframework.boot.autoconfigure.EnableAutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.xgllhz.test.config.ZedAutoConfiguration,\
  org.xgllhz.test.config.FizzAutoConfiguration

  对于以上 jar 包而言,当其被引入后,其所定义的配置 bean 将会自动注册到 spring ioc 容器中。其实 spring boot starter 的原理就是这样的,对于 spring boot starter 可戳 人世间子 (spring boot 自定义 starter)

4.2.2 自定义自动配置

  自定义自动配置的核心就是需要自定义实现 ImportSelector 接口。同时需要在当前项目中定义其对应的配置类。

  假设我们自定义的 jar 包中有以下配置类:

// 配置类一
package org.xgllhz.test.config;

@Configuration
public class ZedAutoConfiguration {
  	@Bean
  	public Zed zed() {
      	return new Zed();
    }
}

// 配置类二
package org.xgllhz.test.config;

@Configuration
public class FizzAutoConfiguration {
  	@Bean
  	public Fizz fizz() {
      	return new Fizz();
    }
}

  则当引入了该 jar 包后,想让以上两个配置类生效我们需要在当前项目中做三件事:

  • 在 META-INF 目录下创建 spring.factories 文件:

    org.xgllhz.momo.config.MyImportSelector=\
      org.xgllhz.test.config.ZedAutoConfiguration,\
      org.xgllhz.test.config.FizzAutoConfiguration
    
  • 实现 ImportSelector 接口:

    package org.xgllhz.momo.config;
    
    public class MyImportSelector implements DeferredImportSelector {
      	// 该方法会以 MyImportSelector 类的全类名为 key 去 spring.factories 文件中找到对应的 value 并返回
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return SpringFactoriesLoader
                .loadFactoryNames(MyImportSelector.class, null).toArray(new String[0]);
        }
    }
    
  • 添加配置类,使 MyImportSelector 生效:

    package org.xgllhz.momo.config;
    
    @Configuration
    @Import(MyImportSelector.class)
    public class MyConfig {}
    

  以上配置完成后,当前项目在启动时就会加载解析 MyConfig 配置类,然后通过 @Import(MyImportSelector.class) 来使第三方 jar 包中的 ZedAutoConfiguration 和 FizzAutoConfiguration 生效。这样就完成了自动配置。

4.3 spring boot 中的自动配置

  spring boot 中的自动配置主要目的是将各个层面的技术或实现中最常用或在 spring 圈子中认可度最高的实现配置为默认实现,同时允许用户选择其它实现。这样做减少了集成某个技术时的繁琐配置,提高了搬砖效率。其所提出的 “约定大于配置” 或可这样解读。

4.3.1 AopAutoConfiguration

  AopAutoConfiguration 是 spring boot 提供的关于 spring aop 的自动配置类。其核心是通过 spring boot 实现的多种条件注解 @Conditional 来使用 spring aop 注解 @EnableAspectJAutoProxy。

  • @EnableAspectJAutoProxy

    即 spring 提供的开启 spring 注解版 aop 功能的注解。spring 中代理默认是关闭的,可使用此注解开始。其原理是通过 @Import 注解结合 AspectJAutoProxyRegistrar 类来向 spring ioc 容器中注入了自动代理创建器 AnnotationAwareAspectJAutoProxyCreator。该创建器会在 bean 的初始化阶段为其创建代理。

    其提供了两个属性分别是:

    • proxyTargetClass:代理方式。默认为 false,表示使用基于接口的 jdk 动态代理。若为 true,则表示使用基于继承的 cglib 动态代理。
    • exposeProxy:代理暴露方式。默认为 false,表示不暴露代理。若为 true,则表示将代理暴露到线程本地内存(ThradLocal)。解决了类内部方法之间调用不能使用代理的问题,同时,其有效解决了 spring 事务失效的方法自调用问题。

    AspectJAutoProxyRegistrar:自动代理注册器,其被 @Import 注解作用于 @EnableAspectJAutoProxy 注解上。其作用是向 spring ioc 容器中注册基于注解的自动代理创建器 AnnotationAwareAspectJAutoProxyCreator bean。同时将 proxyTargetClass 和 exposeProxy 属性的值设置给该 bean,以便其在创建代理时选择合适的代理实现方式和代理暴露方式。

    AnnotationAwareAspectJAutoProxyCreator:即基于注解的自动代理创建器,其本质是 bean 的后置处理器(BPP),其可以找到容器中的所有切面,然后根据匹配到的切点为目标对象创建代理对象,其创建代理对象一般在 bean 的初始化阶段完成。

  • AopAutoConfiguration

    其被注解 @ConditionalOnProperty(prefix = “spring.aop”, name = “auto”, havingValue = “true”, matchIfMissing = true) 标注,即其表示当环境配置中存在前缀为 spring.aop,名称为 auto,值为 true 的配置时 AopAutoConfiguration 才生效;后面又将 matchIfMissing 设置为 true,则表示 若配置中没有 spring.aop.auto = true 的配置 AopAutoConfiguration 也生效。

    即其作用在 AopAutoConfiguration 类上的则比表示,无论环境中是否存在 spring.aop.auto = true 的配置该自动配置类都生效。spring.aop.auto 是 spring boot 提供的是否开启 aop 自动配置的配置,默认为 true 即表示开启。

  • AspectJAutoProxyingConfiguration

    其被注解 @ConditionalOnClas(Advice.class) 标注,即其表示当程序中存在指定的 Advice 类时其作用的类中的配置才生效。又因为 Advice 是 AspectJ aop 的核心类,且 spring boot 默认开启了 aop 自动配置,所以程序中一定存在 Advice 类。所以默认情况下 AspectJAutoProxyingConfiguration 配置一定生效。

  • JdkDynamicAutoProxyConfiguration

    其被注解 @ConditionalOnProperty(prefix = “spring.aop”, name = “proxy-target-class”, havingValue = “false”) 标注,表示当配置中存在 spring.aop.proxy-target-class=false 配置时,将使用基于接口的 jdk 动态代理。

  • CglibAutoProxyConfiguration

    其被注解 @ConditionalOnProperty(prefix = “spring.aop”, name = “proxy-target-class”, havingValue = “true”,matchIfMissing = true) 标注,表示不管有没有添加 spring.aop.proxy-target-class=true 配置,都将使用基于继承的 cglib 动态代理。

  综上所述,在默认情况下,spring boot 开启了 aop 的自动代理,其原理是向 srping ioc 容器中注册了自动代理创建器,该创建器会在目标对象的初始化阶段为其创建代理,且默认使用基于继承的 cglib 动态代理实现。

4.3.2 DataSourceAutoConfiguration

  DataSourceAutoConfiguration 是 spring boot 提供的关于数据源的自动配置类。其核心是通过数据源基本配置结合条件注解来选择对应的数据源并创建数据源链接。数据源基本配置会封装到 DataSourceProperties 配置类中。且 spring boot 支持两大类型的数据源,即 EmbeddedDatabase 内嵌数据源和 PooledDataSource 池化数据源。

  • DataSourceProperties

    即数据源属性配置类,程序中配置的数据源的 url、driver、username、password 等属性会被封装到该对象中,且该对象会被注册到 spring ioc 中,以便在后续创建数据源连接时使用。

  • EmbeddedDatabaseConfiguration

    即关于嵌入式数据源的配置。spring boot 默认支持 H2、HSQL、DERBY 等嵌入式数据源。

    • @Conditional(EmbeddedDatabaseCondition.class):该条件用来检查何时使用内嵌式数据源。即当程序中存在池化数据源且可用时,则优先池化数据源;否则则使用嵌入式数据源。
    • @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }):即当容器中不存在 DataSource 和 XADataSource 这两种类型的 bean 时该配置才生效。
    • @Import(EmbeddedDataSourceConfiguration.class):即若以上两种条件成立,则通过引入 EmbeddedDataSourceConfiguration 配置类的方式配置嵌入式数据源。
  • PooledataSourceConfiguration

    即关于池化数据源的配置。spring boot 默认支持 Hikari、Tomcat、Dbcp2、OracleUcp 等池化数据源。

    • @Conditional(PooledDataSourceCondition.class):该条件用来检查程序中是否已存在池化数据源,若存在则检查其类型是否支持,若支持则检查其是否可用。
    • @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }):即当容器中不存在 DataSource 和 XADataSource 这两种类型的 bean 时该配置才生效。
    • @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class, DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class }):若以上两种条件成立,则通过引入 DataSourceConfiguration 配置类的方式配置池化数据源。
    • DataSourceConfiguration:该配置类中通过条件注解配置了 spring boot 默认支持的几种池化数据源。默认情况化会配置 Hikari 数据源。
4.3.3 MyBatisAutoConfiguration

  MybatisAutoConfiguration 是 spring boot 提供的关于 mybatis 的自动配置类,准确来说是 mybtais-spring 的自动配置类。其作用是通过一系列条件注解自动配置 SqlSessionFactory 和 SqlSessionTemplate,同时配置了默认的 mapper 扫描方法。

  • 配置生效条件

    • a、@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }):程序中必须存在 SqlSessionFactory 和 SqlSessionFactoryBean 类。
    • b、@ConditionalOnSingleCandidate({ DataSources.class }):spring ioc 容器中必须存在 DataSource bean。
    • c、@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class }):该配置将在 DataSourceAutoConfiguration 和 MybatisLanguageDriverAutoConfiguration 配置完成后才进行,因为其依赖前者的配置结果。
  • MybatisProperties

    mybatis 的属性配置类,程序中 mybatis 的相关配置将被封装成该类,且该配置实例会被 @EnableConfigurationProperties 注解注册到 spring ioc 容器中,共后续使用。

  • SqlSessionFactory

    即 mybatis 提供的操作 SqlSession 的接口。当 spring ioc 容器中不存在该类型 bean,才会创建 SqlSessionFactoryBean bean 并设置相关属性,然后将其注册到容器中。且该 bean 会与线程进行绑定。

  • SqlSessionTemplate

    是 mybatis-spring 的核心,用来与 spring ioc 容器对接,spring 将通过该类管理 SqlSession,且其会与 spring 事务一起工作。同 SqlSessionFactory 一样,生效条件是当容器中不存在该 bean 时才会创建并注册该 bean。

  • MapperScannerRegistrarNotFoundConfiguration

    其作用是当容器中不存在 mapper 扫描相关的 bean 时(MapperFactoryBean、MapperScannerConfigurer),才会通过 @Import 引入 AutoConfiguredMapperRegistrar 自动配置 mapper 注册器来注册 mapper 扫描相关的 bean。

    实际上 AutoConfiguredMapperRegistrar 会通过 AutoConfigurationPackages 拿到引导类所在的包路径,然后扫描该路径下所有被 @Mapper 注解标注的接口,然后将其封装成 MapperFactoryBean,最后再将其注册到容器中。

   @MapperScan,其作用也是 mapper 扫描,原理是将指定包路径下的所有接口都注册到容器中。其和 MybatisAutoConfiguration 配置类提供的 mapper 扫描的区别是:前者会将指定包路径下的所有接口都会注册到容器中;后者则是将引导类所在包路径下的所有被 @Mapper 注解标注的接口注册到容器中。

   mapper scanning

  • mapper 接口加 @Mapper 注解。
  • mapper 接口不加 @Mapper 注解,但需要在引导类上添加 @MapperScan 注解,并指定 mapper 接口所在的包路径。
  • mybatis-plus 中的 mapper 扫描同上,但 mapper 接口需要继承 BaseMapper 接口。
4.3.4 TransactionAutoConfiguration

  spring boot 提供了两个事务相关自动配置类,即 DataSourceTransactionManagerAutoConfiguration 和 TransactionAutoConfiguration,分别是数据源事务自动配置类和 spring 事务配置类。

DataSourceTransactionManagerAutoConfiguration

  即 spring boot 提供的关于数据源事务的自动配置类。其生效条件是程序中存在 DataSource 类和 JdbcTemplate 类。其会基于 DataSource 和 JdbcTemplate bean 向容器中注册 DataSourceTransactionManager bean,且其实际上注入的是 JdbcTransactionManager bean。

TransactionAutoConfiguration

  即 spring boot 提供的关于 spring 事务的自动配置类,其依赖于上述配置的结果 DataSourceTransactionManager bean。该配置类提供了三个 bean 和一个配置类,分别是:

  • TransactionManagerCustomizers

    即事务管理器自定义扩展器,其作用是对事务管理器提供一些自定义扩展处理。当容器不存在 TransactionManagerCustomizers bean 才会注入该 bean。

  • TransactionOperator

    即响应式事务,其作用是提供响应式事务。若为响应式编程,则应该使用响应式事务管理器管理事务。当容器中存在 ReactiveTransactionManager bean 时才会注入该 bean。

  • TransactionTemplate

    即编程式事务,其作用是提供编程式事务。当容器中存在 PlatformTransactionManager bean 时才会注入该 bean。

  • EnableTransactionManagementConfiguration

    即开启事务管理配置的配置类。而其开始事务管理的方式则是直接使用了 spring 提供的 @EnableTransactionManagement 注解。该配置类内部又提供了两个配置,分别是:

    • 基于接口的 jdk 动态代理的事务配置。
    • 基于子类的 cglib 动态代理的事务配置,默认使用 cglib 动态代理的事务配置。

@EnableTransactionManagement

  即 spring 提供的关于开启 spring 事务的注解。其原理是通过 @Import 引入 TransactionManagementConfigurationSelector,该类想容器中注册了 AutoProxyRegistrar bean 和 ProxyTransactionManagementConfiguration bean。

  • AutoProxyRegistrar

    即自动代理注册器。其本质是一个 BPP,其会为被 @Transactional 注解标注的方法所在的类的实例创建代理对象,然后将其注册到容器中。

  • ProxyTransactionManagementConfiguration

    其作用是将一个切面 bean 注册到容器中。

    • a、BeanFactoryTransactionAttributeSourceAdvisor:即事务相关的切面类。
    • b、TransactionAttributeSource:即事务属性源,其本质是一个切点,切点来源于其内置的事务注解解析器所解析到的方法(即被 @Transactional 注解标注的方法)。
    • c、TransactionInterceptor:即事务拦截器,其本质是一个通知,通知内容是事务的提交、回滚等逻辑。

  注:若提供了自定义实现的 DataSourceTransactionManager 或则引导类上标注了 @EnableTransactionManagement 注解,则以自定义配置为准。

4.3.5 web auto configuration

  spring boot 提供的源于 spring web 相关的自动配置类,主要包括 ServletWebServerFactoryAutoConfiguration、DispatcherServletAutoConfiguration、WebMvcAutoConfiguration、MultipartAutoConfiguration、ErrorMvcAutoConfiguration 等配置类。这些配置类的主要作用是向容器中注册一些 spring web 运行时需要的默认 bean 实例。

ServletWebServerFactoryAutoConfiguration

  即有关内嵌 web 容器的自动配置类。其作用是为 spring boot 提供内嵌 web 容器。其支持 tomcat、jetty、undertow 三种 web 容器,默认内嵌 tomcat 容器。其内嵌 tomcat 的原理是通过 @Import 引入了 ServletWebServerFactoryConfiguration 类的内部类 EmbeddedTomcat 配置类,该配置类的作用是在条件成立的情况下向容器中注册 TomcatServletWebServerFactory bean。在 spring 容器刷新时(即执行 refresh() 方法时)会使用该 bean 来创建 tomcat 容器。此外,该配置类还提供了以下两个 bean 的注册:

  • ServletWebServerFactoryCustomizer:即关于 web 容器自定义的 bean。
  • TomcatServletWebServerFactoryCustomizer:即关于 tomcat 容器的自定义操作的 bean。

DispatcherServletAutoConfiguration

  即有关 DispatcherServlet 的自动配置类。其作用是向容器中注册 DispatcherServlet bean 和 DispatcherServletRegistrationBean bean。其在 ServletWebServerFactoryAutoConfiguration 配置完成后才执行,且其依赖于 WebMvcProperties,来为 DispatcherServlet 添加各种参数。

WebMvcAutoConfiguration

  即有关 spring web mvc 的自动配置类。其作用是向容器中注册 web mvc 相关的各种类型 bean,主要包括以下几种:

  • HandlerMapping:处理器映射器。
  • HandlerAdapter:处理器适配器。
  • MessageConverter:消息转换器。
  • DataBinder:数据绑定器(内含数据类型转换器)。
  • ViewResolver:视图解析器。
  • PathMatcher:路径匹配器。
  • ExceptionHandler:异常处理器。

MultipartAutoConfiguration

  即有关处理 multipart/form-data 格式数据的自动配置类。其作用是向容器中注册了 StandardServletMultipartResolver bean,该 bean 的主要作用是用来解析 multipart/form-data 格式的数据。

ErrorMvcAutoConfiguration

  即有关 web mvc 的 error 处理相关的自动配置类。其作用是向容器中注册 BasicErrorController bean。该 bean 的主要作用是提供 web error 的处理,包括 error page 和 error message 两种方式。

  那就像风一样吧,无爱且自由。 ——单车佬。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值