【SpringBoot】自动配置原理
发布日期:2021-05-07 02:51:48 浏览次数:26 分类:精选文章

本文共 13452 字,大约阅读时间需要 44 分钟。

文章目录

一、SpringBoot的特点

1.依赖管理

1.1 父项目做依赖管理

org.springframework.boot
spring-boot-starter-parent
2.3.4.RELEASE

打开 org.springframework.boot ,进入到他的父工程内部,可以看到如下图所示的内容:

image-20210421141429378

再点击 spring-boot-dependencies,进入依赖配置文件,可以看到如下内容:

image-20210421141851484

父项目中的依赖管理,几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制!!!


1.2 开发导入场景启动器 Starters

Starters are a set of convenient dependency descriptors that you can include in your application. You get a one-stop shop for all the Spring and related technologies that you need without having to hunt through sample code and copy-paste loads of dependency descriptors. For example, if you want to get started using Spring and JPA for database access, include the spring-boot-starter-data-jpa dependency in your project.

有了项目的依赖管理后,我们在做开发的时候只需要进一步导入场景启动器的配置,就可以自动导入相对应的场景所需要的所有依赖配置,例如下面的web场景启动器:

org.springframework.boot
spring-boot-starter-web

点击 spring-boot-starter-web ,进入内部配置我们可以看到其中具体的依赖配置,下面是场景依赖树

image-20210421160924054

其中,spring-boot-starter 是所有场景最基础的启动器:

org.springframework.boot
spring-boot-starter
2.3.4.RELEASE
compile


1.3 修改版本号

通过使用父项目的依赖管理,我们无序关注版本号的关联,它内部使用自动版本仲裁机制,可以自动匹配,但是如果某些依赖需要改动版本号,这里以数据库连接为例:

  • 首先查看父项目的数据库连接配置版本

    image-20210421142746986
    image-20210421142812831

  • 明确需要修改的数据库版本号

    • 假设这里我的数据库版本为5.1.43
  • 进入配置 pom.xml进行版本号配置

    5.1.43


2.自动配置

2.1 自动配置 Tomcat

  • 引入Tomcat 依赖

    org.springframework.boot
    spring-boot-starter-tomcat
    2.3.4.RELEASE
    compile
  • 自动配置好Tomcat


2.2 自动配置 Spring MVC 的全套組件

org.springframework
spring-webmvc
5.2.9.RELEASE
compile

我们可以通过运行的主程序获取IOC容器 ConfigurableApplicationContext对象,去getBeanDefinitionNames()获取组件的名称,做个简单的浏览:

image-20210421162733504


2.3 自动配置好Web常见功能

  • 例如:字符编码、视图等


2.4 默认包结构

**官方解释: ** We generally recommend that you locate your main application class in a root package above other classes. The is often placed on your main class, and it implicitly defines a base “search package” for certain items. For example, if you are writing a JPA application, the package of the @SpringBootApplication annotated class is used to search for @Entity items. Using a root package also allows component scan to apply only on your project.

整体意思就是,我们通常建议您将主应用程序类放在根包中,置于其他类之上。@SpringBootApplication注释经常放在主类上,它隐式地为某些项定义了一个基本的“搜索包”。例如,如果您正在编写一个|PA应用程序,则使用@springBootApplication注释类的包来搜索@Entity项。使用根包还允许组件扫描只应用于您的项目。

结构示意:

image-20210421163629241

总的来说,也就是主程序同包及其下面的子包中的组件都会被自动扫描进去!(如果放在其上级包中的组件不会被扫描到,测试时 404)

注意:

  • 想要改变扫描路径,@SpringBootApplication(scanBasePackages=“指定组件所在的包路径”)

  • 或者@ComponentScan 指定扫描路径

  • @SpringBootApplication等同于@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan("com.zyx.core.boot")


2.5 各种配置都有默认值

  • 后期可以通过 application.properties 自定义的配置文件进行修改!
  • 默认配置最终都是映射到某个类上,如:MultipartProperties、ServerProperties

image-20210421165105911

  • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象


2.6 按需加载所有自动配置项

  • 非常多的starter

  • 引入了哪些场景这个场景的自动配置才会开启

  • SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面

等等。。。。。


二、容器功能 — 底层注解

1.组件添加

1.1 @Configuration

基本使用

创建类

package com.zyx.core.boot.bean;/** * 电脑工具 */public class Computer {       private String typeName;    public Computer(String s) {           this.typeName = s;    }    public void setTypeName(String typeName) {           this.typeName = typeName;    }    public String getTypeName() {           return typeName;    }    @Override    public String toString() {           return "Computer{" +                "typeName='" + typeName + '\'' +                '}';    }}package com.zyx.core.boot.bean;/** * 用户 */public class User {       private String name;  // 用户名称    private Integer age;  // 用户年龄    // 有参构造    public User(String name, Integer age) {           this.name = name;        this.age = age;    }    // setter、getter方法集    public String getName() {           return name;    }    public void setName(String name) {           this.name = name;    }    public Integer getAge() {           return age;    }    public void setAge(Integer age) {           this.age = age;    }    // toString()    @Override    public String toString() {           return "User{" +                "name='" + name + '\'' +                ", age=" + age +                   '}';    }}

配置

  • @Configuration的使用其实就是完全注解形式的开发
    • 创建一个Config配置类
    • 使用@Configuration进行注释
    • 添加组件实现功能
package com.zyx.core.boot.config;import com.zyx.core.boot.bean.Computer;import com.zyx.core.boot.bean.User;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration  // 使用完全注解形式开发public class MyConfig {       /**     *  使用注解来给容器中添加组件  --- 创建User对象     *  方法名   ---  组件的id    id     *  返回类型  --- 组件的类型   class     *  返回值   ---  组件在容器中的实例     * @return     */    @Bean    public User user01(){           return new User("小赵",21);    }    @Bean    public Computer computer(){           return new Computer("Dell");    }}

使用上面的配置类,实际上实现的功能与下面的xml配置文件效果相同,只是在实际现实中使用注解开发更加方便容易。


注意
  • 1.使用默认的形式创建的所有对象都是单例模式,即无论引用多少次对象的获取,都是同一个对象(内存地址相同!

    image-20210421173219814


  • 2.当前我们的配置类也是一个组件image-20210421173452017

  • 3.proxyBeanMethods参数默认为true表示当前的配置类使用代理对象

    /** *  proxyBeanMethods = true 表示代理bean的方法 */@Configuration(proxyBeanMethods = true)  // 使用完全注解形式开发
    • 我们通过代理对象去调用方法,获取容器中的组件时,SpringBoot总会检查这个组件是否在容器中存在,存在就直接去调。默认情况下也是单例模式!!!image-20210421174147770

    • **当我们取消了代理,也就是 ==@Configuration(proxyBeanMethods = false)==的时候 ,我们再去通过代理对象获取具体类对象进行比较,结果就变了~同时,此时的myConfig对象也不再是代理对象。 ** 注意此时,直接通过容器对象获取的结果保持不变!

      • Full 全配置(proxyBeanMethods = true)
        • 此模式下,会生成代理对象,每次外部查询组件的时候都会通过代理对象去容器中找所需组件
      • Lite 轻量级配置(proxyBeanMethods = false)
        • 此模式下,不会保留代理对象,每次查询组件时都会创建新的。
          image-20210421174854792


  • 4.探究Full、Lite模式

    • 我们将代码部分修改,在user类中添加Computer对象属性

      package com.zyx.core.boot.bean;/** * 用户 */public class User {           private String name;  // 用户名称    private Integer age;  // 用户年龄    private Computer computer;    // 有参构造    public User(String name, Integer age) {               this.name = name;        this.age = age;    }    // setter、getter方法集    public String getName() {               return name;    }    public void setName(String name) {               this.name = name;    }    public Integer getAge() {               return age;    }    public void setAge(Integer age) {               this.age = age;    }    public Computer getComputer() {               return computer;    }    public void setComputer(Computer computer) {               this.computer = computer;    }    // toString()    @Override    public String toString() {               return "User{" +                "name='" + name + '\'' +                ", age=" + age +                ", computer=" + computer +                '}';    }}
      • Full 模式下

        image-20210421180343242

      • Lite模式下

        image-20210421180420083

在两种不同的模式下,对于对象的引用方式不同(是否使用代理对象)。Full模式下使用代理对象实现,我们创建的对象就是从容器中找,而Lite模式,则是创建新的对象。


1.2 @Import

@Import 给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名

package com.zyx.core.boot.config;import ch.qos.logback.core.db.DBHelper;import com.zyx.core.boot.bean.Computer;import com.zyx.core.boot.bean.User;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;/** *  proxyBeanMethods = true 表示代理bean的方法 *  @Import({User.class, DBHelper.class}) 自动创建出User、DBHelper类型的组件 */@Import({   User.class, DBHelper.class})@Configuration(proxyBeanMethods = true)  // 使用完全注解形式开发public class MyConfig {       /**     *  使用注解来给容器中添加组件  --- 创建User对象     *  方法名   ---  组件的id    id     *  返回类型  --- 组件的类型   class     *  返回值   ---  组件在容器中的实例     * @return     */    @Bean    public User user01(){           User user = new User("小赵",21);        // user组件依赖了pet组件        user.setComputer(computer());        return user;    }    @Bean    public Computer computer(){           return new Computer("Dell");    }}

image-20210421204151235

通过测试,我们可以得到创建的User对象集和DBHepler对象集。其中User对象集包含了两个,一个是我们使用@Bean创建的对象,另一个就是我们使用@Import创建的对象。


1.3 @Conditional

  • 满足Conditional指定的条件,则进行组件注入

    image-20210421205223437

  • 我们可以看到@ Conditional下面有很多的继承注解,举个@ConditionalOnBean小例子:

    image-20210421205753921
    image-20210421205816213
    image-20210421210012408
    image-20210421210040596

可以看到在在添加了@ConditionalOnBean注解并且注释掉computer的@Bean注解后,原来的User01组件未能够添加。

我们也可以将其添加在类上面,我们放出computer上的Bean注解,并创建computer1组件,此时在类上的注解表示在容器中含有名为computer的容器时,添加该类中的user01、computer1组件,由于并没有创建过computer的组件,所以结果全为false。如果这里使用的是@ConditionalMissingBean注解,则以上的情况全部相反。

image-20210421214346321


2.xml配置文件导入

2.1 @ImportResource

当我们在项目中既使用了xml配置文件,又使用了注解的方式或者使用了注解方式,需要用到之前配置的xml文件。可以使用

@ImportResource注解来导入配置文件,方便注解的迁移。

之前的xml配置文件:

通过测试,xml配置文件中的组件并没有加入到容器中。

image-20210421215333715

加入@ImportResource(“classpath:beans.xml”) 注解后:

image-20210421221646427
image-20210421221620462

可以看到,配置文件中的computer01、user02组件都添加到了容器中。同时,我们注释掉了computer的@Bean,则此时容器中不会添加,user01组件创建的时候添加了@ConditionalOnMissingBean,所以此时user01的组件会被创建(添加到容器中)。


3.配置绑定

在数据库操作的时候,我们通常都是使用properties配置文件,创建组件的时候获取其中的数据库配置信息。然而在这里,我们利用java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用。

3.1 @ConfigurationProperties + @Component

@ConfigurationProperties(prefix = “prop”) 是外部化配置的注释。如果要绑定和验证某些外部属性,利用前缀匹配。

package com.zyx.core.boot.bean;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;@Component    // 添加到容器@ConfigurationProperties(prefix = "prop")  // 外部化配置的注释。如果要绑定和验证某些外部属性,利用前缀匹配public class SQLBean {       /*      prop.driverClass=com.mysql.jdbc.Driver      prop.url=jdbc:mysql://localhost:3306/test01      prop.userName=root      prop.password=123456     */    private String driverClass;    private String url;    private String userName;    private String password;    public String getDriverClass() {           return driverClass;    }    public void setDriverClass(String driverClass) {           this.driverClass = driverClass;    }    public String getUrl() {           return url;    }    public void setUrl(String url) {           this.url = url;    }    public String getUserName() {           return userName;    }    public void setUserName(String userName) {           this.userName = userName;    }    public String getPassword() {           return password;    }    public void setPassword(String password) {           this.password = password;    }    @Override    public String toString() {           return "SQLBean{" +                "driverClass='" + driverClass + '\'' +                ", url='" + url + '\'' +                ", userName='" + userName + '\'' +                ", password='" + password + '\'' +                '}';    }}

Controller层编写代码,展示请求:

package com.zyx.core.boot.controller;import com.zyx.core.boot.bean.SQLBean;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controller@ResponseBodypublic class SQLController {       @Autowired    SQLBean sqlBean;    @RequestMapping("/mySQL")    public SQLBean mySQL(){           return sqlBean;    }}

主程序运行成功后,登陆页面发送请求:

image-20210421224151055

显然,我们通过测试检测了数据库信息的配置绑定!


3.2 @ConfigurationProperties + @EnableConfigurationProperties

也可以通过@EnableConfigurationProperties的形式进行~

image-20210421230653449


三、自动配置原理入门

1.引导加载自动配置类

前面说到过:

@SpringBootApplication效果等同于下面三个注解@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan("com.zyx.core.boot")    @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 {       ............}

1.1 @SpringBootConfiguration

image-20210421231711797

可以看到该注解接口上使用的是@Configuration,也就是说明当前的MainApplication也是一个配置类。


1.2 @EnableAutoConfiguration

image-20210421232059116

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration {   }
@AutoConfigurationPackage
@Import(AutoConfigurationPackages.Registrar.class)public @interface AutoConfigurationPackage {   ...}

可以发现@AutoConfigurationPackage底层实际上就是@Import,利用Registrar来批量导入组件:

image-20210421233729672

image-20210421234055688

利用注解原信息获取到所在的包名,然后将包转换成一个数组,其实就是将这一个包中的组件进行批量添加!


@Import(AutoConfigurationImportSelector.class)

image-20210421234615359

1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件

2、调用List< String > configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类(組件)
image-20210421235230807
image-20210422001125143


3.利用工厂加载 Map<String, List< String >> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件

image-20210421235737598


4、从META-INF/spring.factories位置来加载一个文件。

  • 默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件

    image-20210422000430338
  • spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories,文件里面写死了spring-boot一启动就要给容器中加载的所有配置类(基础127个)

    image-20210422000828791
    实际上,我们容器中添加的组件还包含有我们自行加入的。
    image-20210422001400312


1.3 @ComponentScan

包扫描注解!

image-20210421232756014


2.按需求开启自动配置项

虽然我们127个场景的所有自动配置启动的时候默认全部加载 ——— xxxxAutoConfiguration

aop切面编程:

image-20210422001834054

batch批处理:

image-20210422001900415

以批处理、aop切面编程为例,均需要按照条件装配规则(@Conditional),最终满足了条件才会按需配置。


3.修改自动配置

SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先。

@Bean@ConditionalOnMissingBean  // 该注解表示没有创建characterEncodingFilter的组件,就自动配置public CharacterEncodingFilter characterEncodingFilter() {   }

总结:

  • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration

  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定

  • 生效的配置类就会给容器中装配很多组件

  • 只要容器中有这些组件,相当于这些功能就有了

  • 定制化配置

    • 用户直接自己@Bean替换底层的组件
    • 用户去看这个组件是获取的配置文件什么值就去修改。(简单说就是修改application.properties配置文件)

xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 ----> application.properties

参考:

参考:


上一篇:【Spring MVC】主要基础配置
下一篇:【HTML5 CSS】display和visibility的区别

发表评论

最新留言

路过,博主的博客真漂亮。。
[***.116.15.85]2025年04月12日 03时32分51秒