SpringBoot 二 自动配置的基本原理

SpringBoot 二 自动配置的基本原理,第1张

SpringBoot 二 自动配置的基本原理 了解自动配置的基本原理 1、SpringBoot特点 1.1、依赖管理

​ 在之前的SSM项目中,我们为了将各个框架集成到spring中,往往需要导入很多的依赖。而为什么在springboot中我们只需要在pom文件中加入web模块的启动器依赖,就能省掉这么多的依赖呢?

​ 其实并不是省了,而是我们加入了启动器依赖后,boot自动地帮我们把有关web开发的所有依赖都帮我们引入了,给程序员省去了一大堆麻烦的步骤,那springboot是如何做到的呢?

​ 首先我们看pom文件,在springboot项目中pom文件都会有一个父工程依赖,我们都知道父项目一般都是用来做依赖管理的,负责统一依赖的版本信息,子项目子需要继承该父项目就不用担心版本依赖冲突的问题了

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

我们按住CTRL点击进入该父项目,发现这个里面也有一个父项目

然后我们以同样的方式进入该spring-boot-dependencies,里面有一个properties,几乎声明了我们目前开发遇到的所有的依赖的版本信息。

既然spring boot帮我们导入了相关的依赖以及控制依赖的版本,那么我们在使用依赖的时候, 无需关注版本号,自动版本仲裁
1、引入依赖默认都可以不写版本
2、引入非版本仲裁的jar,要写版本号。

而且假如我们不想要springboot中默认的版本号,我们也可以进行修改,方法是在我们的项目的pom文件中添加一个properties,里面修改我们要修改的依赖的版本

  
  		 想改的版本
  

接下来在pom文件中还有一个web场景启动器的依赖spring-boot-starter-web,正是这个场景启动器帮我们引入了有关springweb开发 所需要的所有依赖。

在pom文件中加入了上面的启动器,我们可以看到如下的依赖信息

既引入了spring、springmvc的依赖,还引入了包括但不限于json、tomcat等。所以说,我们只需要在pom文件中引入一个简简单单的web场景启动器,springboot就自动地帮我们引入了那么多的依赖信息,大大的方便了我们的开发。将来我们会在springboot中见到很多以spring-boot-starter-* 开头的依赖 ,关于这些starter在官方文档中有介绍。

https://docs.spring.io/spring-boot/docs/2.4.12/reference/html/using-spring-boot.html#using-boot-starter

关于某一种模块的开发,我们只需要引入相关的一个starter场景启动器依赖即可

Pom文件就先分析到这里,我们要知道的是它给我们做的事情主要是管理依赖的版本信息以及引入web开发相关的依赖

2、容器功能

在我们学习SSM框架的时候,我们要启动一个项目之前肯定要配置一大堆相关的组件的,然后再启动加载它们。而springboot中根本不需要我们进行配置,在底层,它自己就帮我们自动配置好了,在外面了解它是如何帮我们进行配置之前我们先来介绍几个常见的注解以及一些前提知识。

2.1、组件的添加 2.1.1、使用@Configuration注解给容器添加组件

在我们之前的SSM框架,如果要往IOC容器中添加一个bean对象,就要往spring的配置文件中写上相关的配置内容或者在相应的类上面加上注解 . 现在springboot不用这种方式添加对象了,它也有几种方式去添加组件,它不用去创建一个专门的配置文件,而是创建一个专门的类,在找个类上加上一个@Configuration注解,就表明该类是一个配置类,也相当于一个配置文件. ,在这个类中就可以创建对象且往容器加入组件。

做法如下:

1、在类上添加一个@Configuration注解告诉SpringBoot这是一个配置类

2、在该配置类上写方法,方法上加上 @Bean给容器中添加组件,这是以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例。
Person类

public class Person {
    
    public  String name;
    public  Integer age;
    public  Pet pet;
     //相应的get/set方法、构造器、toString方法
    			 ....
   } 			 

Pet

public class Pet {
    
    public String name;
    public Integer age;
     //相应的get/set方法、构造器、toString方法

配置类

import mdy.helloboot.mdy.bean.Person;
import mdy.helloboot.mdy.bean.Pet;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig {
    //Bean注解将该person对象加入容器,组件id就是方法名person1
    @Bean 
    public Person person1(){
        Person person = new Person();
        person.setAge(18);
        person.name="李四";
        return person;
    }
    //若果想自定义组件id,可以在Bean注解中这样写,这个组件的id就变成了dog而不是方法名了
    @Bean("dog")
    public Pet  pet1(){
        Pet myDog = new Pet("啊狗", 5);
        return myDog;
    }
}

在配置类中写了如上内容,就相当于在我们自己往IOC容器中创建了两个对象,接下来我们写一段代码来验证一下在IOC中是否存在该对象。因为我们的主程序类中有这样一行启动代码,其返回值其实就是我们的IOC容器对象。

所以我们暂时可以在主程序类中验证一下我们的组件有没有被注册进ioc容器。而在我们学习spring的时候,我们知道在ioc容器对象中有一个方法可以查看容器中定义的对象。

@SpringBootApplication
public class MdyApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(MdyApplication.class, args);
        //getBeanDefinitionNames:  获取定义的组件名字
        String[] names = run.getBeanDefinitionNames();
        for (String name:names
             ) {
            System.out.println(name);
        }
    }
}

测试结果:可以在控制台中看到输出了如下信息,其中就包括了我们刚才注册的person、dog组件。

我们再获取这两个组件看看里面的属性被注入了没有

@SpringBootApplication
public class MdyApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(MdyApplication.class, args);
        Person person = (Person) run.getBean("person1");
        Pet dog = (Pet) run.getBean("dog");
        System.out.println("人类信息:"+person);
        System.out.println("宠物信息:"+dog);
    }
}

输出结果

注意:
1、 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
2、配置类本身也是组件

proxyBeanMethods属性

假如假在配置类上的注解加上这个属性:proxyBeanMethods=true。它表示注册组件进容器是使用代理bean的方法,即不论我们在主程序类中调用多少次注册方法获取到的对象都是同一个。
原因就是因为这个属性,当它的值为true的时候就表示我们的组件注册方法是被代理对象调用的。
当它的值是false的时候,它就不是用代理对象去调用注册方法,那么调用多次产生的对象就不是同一个了。

所以由proxyBeanMethods属性又引出了两种配置方式

  • Full全配置模式 (proxyBeanMethods = true)、【保证每个@Bean方法被配置类对象调用多少次返回的组件都是单实例的】
  • Lite轻量级模式 (proxyBeanMethods = false)【每个@Bean方法被配置类对象调用多少次返回的组件都是新创建的组件】

这两种不同模式的相互配合,可以解决组件依赖的问题。比如我在Person类中添加一个Pet对象的属性,修改Person的注册方法。(配置类默认模式是full全配置模式)

import mdy.helloboot.mdy.bean.Person;
import mdy.helloboot.mdy.bean.Pet;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = true)
public class MyConfig {
    //Bean注解将该person对象加入容器,组件id就是方法名person1
    @Bean
    public Person person1(){
        Person person = new Person();
        person.setAge(18);
        person.name="李四";
        //调用下面的pet1()注册方法给Pet属性注入值
        person.setPet(pet1());
        return person;
    }
    //若果想自定义组件id,可以在Bean注解中这样写,这个组件的id就变成了dog而不是方法名了
    @Bean("dog")
    public Pet  pet1(){
        Pet myDog = new Pet("啊狗", 5);
        return myDog;
    }
}

因为我们的配置类也是组件,所以自然可以从容器中获取该配置类对象,如果是这样配置的话,因为@Configuration(proxyBeanMethods = true) 默认使用配置类对象调用注册方法时,在IOC容器中的对象都是单实例的,那么person1方法中注入的Pet对象就会跟我们在下面的注册方法pet1的实例一样,这种依赖是成立的。
测试代码:

@SpringBootApplication
public class MdyApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(MdyApplication.class, args);
        MyConfig myConfig = (MyConfig) run.getBean("myConfig");
        Person person = myConfig.person1();
        Person person1 = myConfig.person1();
        System.out.println("是否是单实例对象"+(person==person1));
        System.out.println(person);
        Pet pet = myConfig.pet1();
        Pet pet1 = myConfig.pet1();
        System.out.println("是否是单实例对象"+(pet==pet1));
        System.out.println(pet);
    }
}

结果

倘若设置为@Configuration(proxyBeanMethods = false) ,那么这种依赖就不会成立就会报错。

启动之后虽然也能注入属性但两个属性对象不是同一个属性对象

注意啊,上面测试的都是用配置类对象来调用注册方法获取组件的,而直接从ioc容器中获取的话,

@SpringBootApplication
public class MdyApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(MdyApplication.class, args);
        MyConfig myConfig = (MyConfig) run.getBean("myConfig");
        Person person = myConfig.person1();
        Person person1 = myConfig.person1();
        System.out.println("是否是单实例对象"+(person==person1));
        System.out.println(person);
        Pet pet = myConfig.pet1();
        Pet pet1 = myConfig.pet1();
        System.out.println("是否是单实例对象"+(pet==pet1));
        System.out.println(pet);
        System.out.println("**********直接从ioc容器中获取对象**************");
        Person person11 = (Person) run.getBean("person1");
        Person person22 = (Person) run.getBean("person1");
        System.out.println("person是否单实例:"+ (person11==person22));
    }
}

结果

关于这两种模式的选择:

    ■ 配置类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
    ■ 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
2.1.2、使用@import注解给容器添加对象

在springboot中除了用@Configuration注解去注册组件,我们也可以使用之前添加组件的方式,比如@Bean、@Component、@Controller、@Service、@Repository、@ComponentScan等注解,这些东西的使用方式跟之前spring的用法一样,这里就不再多说了。

现在我们来说一下@import注解给容器中添加组件,这个注解可以写在任何类上面,参数是任何你想要添加进容器的类,例如

@import({Person.class, Pet.class})
@Configuration(proxyBeanMethods = true)  
public class MyConfig {}

通过调用其无参构造方法,给容器中自动创建出这两个类型的组件、默认组件的id名字就是全限定类名(首字母不小写)

例子这里就不写了,测试方法都一样的

2.1.3、使用@Conditional注解给容器添加组件

顾名思义,Conditional条件装配,即满足Conditional指定的条件,则进行组件注入。
我们进入该注解内部打开该注解的继承树可以看出其下有很多个子注解

  • ConditionalOnBean :当容器中存在某个组件时才将其添加进容器
  • ConditionalOnMissingBean:当容器中不存在某个组件时才将其添加进容器
  • ConditionalOnSingleCandidate:当容器中只存在一个实例时才将其添加进容器

  • 太多了但它的作用都跟名字有关,我们现在只对其中一个进行演示,ConditionalOnBean当容器中存在某个组件时才将其添加进容器
@Configuration 
public class MyConfig {

    //表示当容器中存在组件id为dog的组件才会创建person1对象
    @ConditionalOnBean(name="dog")
    @Bean
    public Person person1(){
        Person person = new Person();
        person.setAge(18);
        person.name="李四";
        return person;
    }

    //@Bean("dog")  注释,暂时不将该对象放进容器
    public Pet  pet1(){
        Pet myDog = new Pet("啊狗", 5);
        return myDog;
    }
}

测试

@SpringBootApplication
public class MdyApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(MdyApplication.class, args);
        Person person1 = (Person) run.getBean("person1");
        System.out.println(person1);
    }
}

测试结果,容器中没有person1对象

我们将pet1注册方法注册的dog对象添加进容器,再进行测试

还是报错了

这是因为,在加载配置类的时候是从上往下扫描的,当扫描到person1 的时候,还没有将dog对象添加进容器,这个时候去获取它,不能获取到,所以我们需要将dog的注册方法放在person1注册方法之前。再进行测试即可。

2.2、原生配置文件引入@importResource

有时候我们想给我们的spring项目引入我们以前的SSM项目的配置文件中的对象,可以使用该注解,这样子xml配置文件中的对象都会被加入到容器了

@importResource("classpath:beans.xml")
public class MyConfig {}
2.3、配置绑定

​ SpringBoot在不断地版本迭代中陆续提供了不同的配置参数绑定的方式,我们可以单独获取一个配置参数也可以将一系列的配置映射绑定到JavaBean的属性字段 。

而我们一般把经常需要变换的值放在配置文件中,然后通过java去读取配置文件,并且把配置文件中值封装到javabean中; 比如学Springboot之前,我们要进行数据绑定(绑定数据库的数据源),会把一些有关数据库的配置写到配置文件中,然后通过Properties类来进行绑定,很麻烦…

在spring boot中如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用呢? 我们有三种方式进行绑定:

2.3.1、@Component+@ConfigurationProperties

​ 首先我们注意一点,我们现在使用的绑定方法是由springboot提供的,我们要将进行绑定的JavaBean对象配置到容器中才行, 要不然就不能使用@ConfigurationProperties注解来进行绑定,所以 Component注解 的作用跟在Spring中的作用一样,是用来将组件添加到容器中的 , 保证该对JavaBean对象在IoC容器中 。

​ 在进行演示之前我们先来看看这个ConfigurationProperties注解类的内部

ConfigurationProperties注解是将类中的属性跟配置文件中的配置进行绑定,prefix和value指的是前缀的意思,它们的作用一样,都是找到与配置文件中前缀一样的配置信息,也就是说@ConfigurationProperties(value = "person")与@ConfigurationProperties(prefix = "person")是一样的作用,只是写法不一样。

@Component注解的作用是往容器中添加对象,大家都懂的了,就不用研究它了。接下来我们演示一个这种方式如何进行绑定JavaBean

做法:

1、在JavaBean中加上上面的两个注解(Component注解的作用是将其加入容器,用其他能将其添加进容器的注解也行,比如@Service等)

2、在配置文件中配置信息

JavaBean

@Component
@ConfigurationProperties(prefix = "myuser")  //注意这个prefix的值不要带大写比如myUser
public class User {
    public String name;
    public Integer age;

   //加上相应的get/set方法,没有的话属性值为空,不能注入
}

application.properties配置文件

请求方法

@RestController
public class HelloController {
    //自动注入user属性
    @Autowired
    User user;
    @RequestMapping("/hello")
    public User handle(){
        //将这段字符串返回给浏览器
        return user;
    }
}

发起请求

可以看出,在自动注入car属性的时候将配置文件中的值注入进去了,也就是说将配置属性绑定了

2.3.2、@EnableConfigurationProperties + @ConfigurationProperties

@ConfigurationProperties注解我们看过了,我们来看看@EnableConfigurationProperties 注解的内部

好像其内部我们暂时没什么能看懂的东西,所以我们在这里先不管它了。 @EnableConfigurationProperties的意思是开启属性配置功能 ,作用也是开启属性配置功能。比如我们要想开启Person对象的绑定功能,我们就在@EnableConfigurationProperties的参数 加上Person.class 。它的作用就相当于将Person组件添加到容器中。

做法:

1、在JavaBean的类上加上ConfigurationProperties注解

2、在一个配置类上添加EnableConfigurationProperties 注解,并加上JavaBean参数

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

这种方式主要用在引用第三方包时,比如第三方包中有一个Person类,该类中没有使用@Component,我们也不能够给第三方包中的类加上@Component,这个时候就可以使用在配置类上加上@EnableConfigurationProperties注解的方法。

2.3.3、@ConfigurationProperties注解+@import注解

首先@import注解的用法是: @import(Person.class)
它的作用是给容器中自动创建出该类型的组件、默认组件的名字就是全类名 。用法也很简单,跟上面两个方法差不多,只不过是使用了import注解将JavaBean组件添加进容器而已。

3、自动配置原理入门

好,了解了前面那些入门知识,我们来看看springboot到底是如何帮我们自动配置了那么多东西让我们可以减少工作量。

3.1、引导加载自动配置类 @SpringBootApplication注解

Pom文件我们已经分析过了,现在让我们从我们的主程序开始分析,我们可以看到在主程序类的上面有一个@SpringBootApplication注解,所以我们的分析其实是从该注解开始。按住Ctrl键点击进入该注解,发现里面还配置了如下注解

@SpringBootApplication注解里面主要还包含了以下三个注解:
1、 @SpringBootConfiguration
2、 @ComponentScan
3、 @EnableAutoConfiguration

@SpringBootConfiguration

我们先进入@SpringBootConfiguration注解,发现@SpringBootConfiguration注解里面是一个@Configuration注解

@Configuration注解的作用是告诉springboot该类是一个注解类,故我们知道@SpringBootConfiguration注解其实也可以用来表示该类是一个配置类,只不过他是主配置类罢了。

@ComponentScan注解

我们知道了@SpringBootConfiguration注解的作用之后,再来看看ComponentScan注解,在我们过去学习过的ComponentScan注解我们知道,该注解是用来设置默认扫描包的,在这里它也是默认扫描包,只不过在这里它默认了两个自定义的扫描器。

@EnableAutoConfiguration注解

接下来这个@EnableAutoConfiguration注解才是启动的关键,同样我们进入该注解内部,发现其也是一个合成注解:由@AutoConfigurationPackage与@import(AutoConfigurationimportSelector.class)注解组成。

@AutoConfigurationPackage注解

其中@AutoConfigurationPackage注解是自动配置包,指定了默认的包规则,我们进入其内部发现其实它是一个import注解

我们已经说过import注解的作用就是给容器中导入一个组件,这里导入的是对象Registrar,我们进入该对象内部看看其作用是什么

public void registerBeanDefinitions(Annotationmetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new Packageimports(metadata).getPackageNames().toArray(new String[0]));
		}

到这里我们就明白了,@AutoConfigurationPackage注解的作用是将主程序所在的包以及其目录下的子包中的所以组件都扫描进ioc容器。所以说我们写的请求方法、JavaBean什么的都是写在主程序类所在的包以及子包下的。

@import(AutoConfigurationimportSelector.class)

@EnableAutoConfiguration注解里除了@AutoConfigurationPackage注解,还有一个@import(AutoConfigurationimportSelector.class)注解,由该注解的名字可以大致推断出,它也是用来将某些组件批量导入ioc容器的。让我们看看它导入的AutoConfigurationimportSelector类里面有什么。我们看到,该类内部有一个方法是selectimports。

发现该selectimports方法内部有一个语句调用了getAutoConfigurationEntry方法,调用该方法会得到一个配置对象,然后再调用该对象的getConfigurations方法得到所有的配置后将其转化为一个字符串数组返回。这便是selectimports方法主要干的事。所以说,调用getAutoConfigurationEntry方法获取配置对象是关键的一步,我们按CTRL点进该方法的实现再打上个断点debug启动主程序跟踪一下流程看看它获取的配置对象的细节是什么。

debug启动来到该getAutoConfigurationEntry方法内部

运行到这一步的getCandidateConfigurations方法,其作用是获取所有候选的配置
放行该方法所在的语句后来到下一行代码,也就是说执行完这个方法后会对该方法返回的对象进行一系列的 *** 作,在看这些 *** 作之前我们先来看看执行完这个方法后的结果是什么。我们可以看到,在这给返回的List集合对象configurations中有很多个String对象,不同版本有不同的数量,这里是131个。都是一些配置类的全限定类名。

那getCandidateConfigurations方法是如何获取这131个全限定类名的呢?我们再次给getCandidateConfigurations方法加上断点重新启动,进入该方法的内部看看他到底是从哪里、又是如何获取的这131个全限定类名的。

step into进入getCandidateConfigurations方法内部后发现它首先是利用工厂加载器方法SpringFactoriesLoader.loadFactoryNames加载某些东西

按住CTRL点击进入该loadFactoryNames方法内部看看它到底在加载什么东西

再次进入这个loadSpringFactories方法

这个返回的Map集合就是封装了131个配置类的全限定类名。故这个方法是关键方法,我们在该方法内部打一个断点重新debug启动看看它是如何获取的全限定类名。

所以该语句就是去扫描所有目录为"meta-INF/spring.factories"的文件,有些jar包里面没有这个文件,但是下图所示这个jar包一定会有这个文件

我们打开该文件看看里面的内容,发现里面就是我们的场景自动配置

也就是说,这个文件中写死了我们的springboot项目一启动就要去加载的自动配置类。
虽然在启动的时候会默认将所有的自动配置类全部加载了,但是我们的springboot并不会一股脑的全部配置进去,而是按需配置。什么叫按需配置呢?我们来随便举个例子,打开AopAutoConfiguration类

AopAutoConfiguration是用来做Spring AOP 功能的自动配置类,那么在这里AOP功能能否实现呢?

显然现在是不能使用AOP功能的,我们来看,下面的AspectJAutoProxyingConfiguration上的注解

而只有导入了相应的包才会有Advice这个类。没有导入就不能使用。
所以说AOP功能还不能够使用,要引入相应的依赖,即场景启动器。这就叫按需分配,我们没有引入与AOP相关的场景启动器,说明我们暂时还不需要它,所以springboot不会将其配置好,我们也就不能使用AOP的功能。

到这里我们就分析完了@EnableAutoConfiguration注解里面的两个注解AutoConfigurationPackage以及@import(AutoConfigurationimportSelector.class) 。综合两个注解的功能,@EnableAutoConfiguration注解的功能大概就:
1、配置默认扫描包是扫描主程序类所在的包以及子包
2、加载所有场景的自动配置类,然后按需进行配置。

以上便是springboot自动配置的基本原理!!!!

4、开发小技巧 4.1、Lombok插件

​ 之前我们写JavaBean的时候,需要自己手动的给bean添加上get/set方法、有无参数构造器、以及toString方法。有了Lombok插件之后,我们不需要做这么多麻烦的事,它会自动生成这些代码。

使用步骤:

因为在springboot中已经有该插件的版本信息了,故我们只需要引入该插件即可而不需要写版本信息。

1、引入依赖

  
            org.projectlombok
            lombok
  

2、在idea中安装插件

3、在JavaBean中使用@Data注解生成get/set方法

@ToString注解  :生成toString方法
@Data注解   :生成get/set方法
@AllArgsConstructor注解:生成全参构造器
@NoArgsConstructor注解: 生成无参构造器
@EqualsAndHashCode注解:重写hashCode方法与Equals方法

4、该插件还有一个功能,就是使用日志功能

4.2、dev-tools工具

使用该工具之前,我们每次修改项目的时候都得重新启动,很麻烦,而有了该工具,项目或者页面修改以后我们只需要按Ctrl+F9即可看到效果。

使用方法:添加依赖

  
            org.springframework.boot
            spring-boot-devtools
            true
        
          

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5078718.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-16
下一篇 2022-11-16

发表评论

登录后才能评论

评论列表(0条)

保存