springboot自动配置原理分析

Posted 坤坤就爱露露

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot自动配置原理分析相关的知识,希望对你有一定的参考价值。

06、springboot的自动装配原理

6.1、问题引入:

我们ssm框架阶段,需要编写的大量的配置文件来整合springMvc和mybatis以及一些其他的插件等等,dao层,dataseorce的配置

mybatis的sqlsessionFactoryBean配置,以及自动包扫描mapper包等等,service配置事务等等,springmvc,处理器,视图解析器,乱码解决,dispatchservlet配置等等,最后部署到Tomcat上,等等,最后才可以运行,但是在springboot中我们好像啥都没有做,就是导入一个web模块的启动器,编写了一个controller而已,其他啥都没有做,就完成了项目运行,访问。

我们不免有一个疑问,springboot底层又是如替我们完成了其他操作,为我们配置好其他需要配置的呢?下面详细道来

6.2 、先了解一下IOC容器的功能

6.2.1、添加组件

1、@Configuration

类上标注这个注解表示这个类是一个springBoot的配置类,相当于ssm阶段的spring的xml配置文件,我们之前在spring配置文件中配置一个bean使用标签,但是在配置类中使用@Bean注解来配置一个bean,以方法名作为组件的id,返回类型就是组件的类型,返回值,就是组件在容器中的实例,与配置文件一样,默认为单实例(在@Configuration(proxyBeanMethods = true)情况下)。

/**
 * 外部无论对配置类总的这个组件注册方法调用多少次都是获取之前注册容器中的单实例对象
 * @Bean注解相当于xml配置文件的<bean></bean>标签,用来添加组件 以方法名作为组件的id,返回类型就是组件的类型,返回值,就是组件在容器中的实例
 * 注册组件,默认为单实例组件
 */
@Bean
public User user01() {
    return new User("zhangsan", 18);
}
  • Full模式与Lite模式

    • 示例
    • 最佳实战
      • 配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
        • @Configuration(proxyBeanMethods = false)表示不开启代理对象使用Lite模式,创建多实例,跳过对springIOC容器检查是否存在此实例对象,直接创建
        • 配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
      • 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
        • @Configuration(proxyBeanMethods = true)表示开启对象代理,使用Full模式,创建单实例,每次调用都会检查IOC中是否存在实例对象,又就是拿来用,无的话在创建
        • 配置 类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
        • 解决组件依赖@Configuration(proxyBeanMethods = true)
  • @configuration默认为true
    在这里插入图片描述
    示例:

#############################Configuration使用示例######################################################
/**
 * 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2、配置类本身也是组件
 * 3、proxyBeanMethods:代理bean的方法
 *      Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
 *      Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
 *      组件依赖必须使用Full模式默认。其他默认是否Lite模式
 *
 *
 *
 */
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {

    /**
     * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
     * @return
     */
    @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        //user组件依赖了Pet组件
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

    @Bean("tom")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}


################################@Configuration测试代码如下########################################
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
public class MainApplication {

    public static void main(String[] args) {
        //1、返回我们IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

        //2、查看容器里面的组件
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }

        //3、从容器中获取组件

        Pet tom01 = run.getBean("tom", Pet.class);

        Pet tom02 = run.getBean("tom", Pet.class);

        System.out.println("组件:"+(tom01 == tom02));


        //4、com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892
        MyConfig bean = run.getBean(MyConfig.class);
        System.out.println(bean);

        //如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。
        //保持组件单实例
        User user = bean.user01();
        User user1 = bean.user01();
        System.out.println(user == user1);


        User user01 = run.getBean("user01", User.class);
        Pet tom = run.getBean("tom", Pet.class);

        System.out.println("用户的宠物:"+(user01.getPet() == tom));
    }
}

2、@Bean,@Component、@Controller、@Service、@Repository

@Bean

// 但是在配置类中使用@Bean注解来配置一个bean,以方法名作为组件的id,返回类型就是组件的类型,返回值,就是组件在容器中的实例

@Bean
public User user01() {
    return new User("zhangsan", 18);
}

@Component表明这是一个组件(通用的一个注解),添加到IOC中

@Controller 控制层的组件注解

@Service 服务层的组件注解

@Repository层的注解

3、@ComponentScan、@Import

@ComponentSan组件扫描,可以指定扫描哪些包,扫描指定包以及子包的所有组件添加到IOC容器中去

@import:导入某个组件,默认组件名为全类名

 * 4@Import({User.class, DBHelper.class})
 *      给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
 */
@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
}

4、@Conditional 按条件装配

@Conditional是一个父注解,他有很多实现类

ctrl+h打开继承树可以发现,根据判断条件分为很多种注解,判断是否存在指定的bean,是否存在指定的组件,是否存在指定的配置文件信息等等。。。。。。。。

条件装配:满足Conditional指定的条件,则进行组件注入,加载类上此注解的具体实现注解,只有满足条件,此类下的组件才会注册到IOC容器中,单纯的标注到方法上,满足条件此方法返回值得bean才会注册到IOC容器中。

SpringBoot底层自动装配会使用到大量的此注解以及实现注解。

这也是springBoot为啥只有导入了那个启动器,对应的配置才会自动的生效。
在这里插入图片描述

=====================测试条件装配==========================
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
//@ConditionalOnBean(name = "tom")
@ConditionalOnMissingBean(name = "tom")
public class MyConfig {


    /**
     * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
     * @return
     */

    @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        //user组件依赖了Pet组件
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

    @Bean("tom22")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}

public static void main(String[] args) {
        //1、返回我们IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

        //2、查看容器里面的组件
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }

        boolean tom = run.containsBean("tom");
        System.out.println("容器中Tom组件:"+tom);

        boolean user01 = run.containsBean("user01");
        System.out.println("容器中user01组件:"+user01);

        boolean tom22 = run.containsBean("tom22");
        System.out.println("容器中tom22组件:"+tom22);

    }

6.2.2 、原生配置文件引入@ImportResource

就是springxml配置文件中配置的bean想转换为配置类中注册,使用@ImportResource注解,指定配置文件的地址即可

自动注册,迁移

xml配置文件

======================beans.xml=========================
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="haha" class="com.atguigu.boot.bean.User">
        <property name="name" value="zhangsan"></property>
        <property name="age" value="18"></property>
    </bean>

    <bean id="hehe" class="com.atguigu.boot.bean.Pet">
        <property name="name" value="tomcat"></property>
    </bean>
</beans>

配置类

@ImportResource("classpath:beans.xml")
public class MyConfig {}

======================测试=================
        boolean haha = run.containsBean("haha");
        boolean hehe = run.containsBean("hehe");
        System.out.println("haha:"+haha);//true
        System.out.println("hehe:"+hehe);//true

6.2.3、配置绑定(配置文件注入JavaBean属性)

1、原生的Java方式

public class getProperties {
     public static void main(String[] args) throws FileNotFoundException, IOException {
         Properties pps = new Properties();
         pps.load(new FileInputStream("a.properties"));
         Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
         while(enum1.hasMoreElements()) {
             String strKey = (String) enum1.nextElement();
             String strValue = pps.getProperty(strKey);
             System.out.println(strKey + "=" + strValue);
             //封装到JavaBean。
         }
     }
 }

2、springboot注解方式

方式一:使用@Component,@ConfigurationProperties组合

使用@ConfigurationProperties注解在Javabean类上,prefix指定配置文件前缀,按照属性名一致注入属性值

/**
	方式一: 使用@Component,@ConfigurationProperties组合
 * 只有在容器中的组件,才会拥有SpringBoot提供的强大功能
 */
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {

    private String brand;
    private Integer price;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\\'' +
                ", price=" + price +
                '}';
    }
}
方式二:@EnableConfigurationProperties + @ConfigurationProperties

@EnableConfigurationProperties开启属性配置绑定功能,标注在配置类上,value=需要开启属性注入的类.class,

eg:@EnableConfigurationProperties(Car.class),起到两个作用,一个是开启了car的属性配置绑定的功能,二是把car这个组件自动注册到容器中。

@EnableConfigurationProperties(Car.class)
//1、开启Car配置绑定功能
//2、把这个Car这个组件自动注册到容器中
public class MyConfig {
}

6.3、SpringBoot的依赖管理

打开我们创建springBoot项目的pom.xml文件有下面的写法

指定了我们的springBoot项目的父项目为哪一个,GAV信息

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.0</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

进入我们的springboot项目的父项目中又可以发现他也有一个父项目

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.5.0</version>
</parent>

进入这个父项目的父项目发现,是管理者所有的依赖版本号

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

<properties>
  <activemq.version>5.16.2</activemq.version>
  <antlr2.version>2.7.7</antlr2.version>
  <appengine-sdk.version>1.9.88</appengine-sdk.version>
  <artemis.version>2.17.0</artemis.version>
  <aspectj.version>1.9.6</aspectj.version>
  <assertj.version>3.19.0</assertj.version>
  <atomikos.version>4.0.6</atomikos.version>
  <awaitility.version>4.0.3</awaitility.version>
  <build-helper-maven-plugin.version>3.2.0</build-helper-maven-plugin.version>
  <byte-buddy.version>1.10.22</byte-buddy.version>
  <caffeine.version>2.9.1</caffeine.version>
    ......................
    .................
    ..................
    ................
    几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制
</properties>

我们在pom.xml配置文件中引入的spring-boot-starter-xxxx

会自动为我们导入此模块所有依赖

1、见到很多 spring-boot-starter-* : *就某种场景
2、只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
3、SpringBoot所有支持的场景
https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
4、见到的  *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
5、所有场景启动器最底层的依赖
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
  <version>2.3.4.RELEASE</version>
  <scope>compile</scope>
</dependency>

无序关注依赖版本号,自动进行版本仲裁

1、引入的依赖默认都是可以不写版本号

2、引入非版本仲裁的jar包,需要写入版本号

修改默认版本号

通过对springBoot项目的父项目的父项目,管理依赖版本号的方式我们可以发现,它是使用来管理依赖版本号,所以我们可以 使用Maven的依赖导入就近原则,在我们项目的pom.xml配置文件使用来切换修改默认版本号。

1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
2、在当前项目里面重写配置
3、如何写可以参考对应的父项目的父项目的写法
    <properties>
        <mysql.version>5.1.43</mysql.version>
    </properties>

6.3、SpringBoot自动配置

  • 自动配好SpringMVC

    • 引入SpringMVC全套组件
    • 自动配好SpringMVC常用组件(功能)
  • 自动配好Web常见功能,如:字符编码问题

    • SpringBoot帮我们配置好了所有web开发的常见场景
  • 默认的包结构

    • 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
    • 无需以前的包扫描配置
    • 想要改变扫描路径,@SpringBootApplication(scanBasePackages=“com.atguigu”)
      • 或者@ComponentScan 指定扫描路径
@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
  • 各种配置拥有默认值

    • 默认配置最终都是映射到某个类上,如:MultipartProperties(我们上面简述的javaBean开启配置属性绑定)
    • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象(@enableConfigurationProperties)
  • 按需加载所有自动配置项(注解@Conditional 条件装配)

    • 非常多的starter
    • 引入了哪些场景这个场景的自动配置才会开启
    • SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面

6.4、SpringBoot自动配置原理入门

6.4.1、@SpringBootApplication

找到我们的主启动类,进入@SpringBootApplication注解如下

被三个注解修饰

1、@SpringBootConfiguration

2、@ComponentScan

3、@EnableAutoConfiguration
在这里插入图片描述

6.4.1.1、@SpringBootConfiguration

进入此注解中发现它又被@configuration修饰,所以这个@SpringBootConfiguration注解只是一个表示当前是一个配置类,

说明主程序启动类也就是一个springBoot的配置类

在这里插入图片描述

6.4.1.2、@ComponentScan

指定扫描哪些包下组件,Spring注解

SpringBoot自动配置原理分析

springboot自动配置原理分析

SpringBoot自动配置原理分析

SpringBoot自动配置原理(源码分析)

SpringBoot 高级 原理分析 -- SpringBoot 自动配置:Condition

SpringBoot05:自动配置原理