springboot依赖管理和自动配置源码分析

Posted 364.99°

tags:

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

1.依赖管理

打开pom.xml,看到如下标签<parent>

	依赖管理:
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.12</version>
    </parent>

ctrl点击spring-boot-starter-parent

 父项目
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.5.12</version>
  </parent>

再次ctrl点击spring-boot-dependencies

几乎声明了我们日常开发所需的所有依赖及其版本号

所以,当我们需要某些依赖的时候,不用写版本号

如下,记得刷新

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

然后就会自动导入某一版本的依赖依赖

这就是springboot的 版本仲裁 ,可以防止版本兼容性问题。

当我们需要自定义版本号的时候:去 https://mvnrepository.com/search 查询版本号,然后在pom.xml中修改版本号(注释掉之前的starter):

<!--        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.6.5</version>
        </dependency>

2.自动配置

依赖管理引入依赖,自动配置常用组件。

        //返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(Boot01Application.class,args);
        //查看容器组件
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) 
            System.out.println(name);
        

1.springboot底层注解

springboot核心注解:

@SpringBootApplication

其底层注解:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan

@Configuration

案例

@SpringBootConfiguration 的底层注解是 @Configuration

@Configuration 告诉springboot这是一个配置类(功能和配置文件相同)

User类

public class User 
    private String name;
    private int age;

   /*
		setter、getter、constructor...
	*/

配置类

@Configuration  //告诉springboot这是一个配置类(功能和配置文件相同)
public class MyConfig 
    @Bean//给容器中添加组件,以方法名为组件id,返回类型是组件类型。返回的值是组件在容器中的实例
    public User user1()
        return new User("张三",17);
    

启动类

        //返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(Boot01Application.class,args);
        //从容器中获取组件
        System.out.println(run.getBean(MyConfig.class));//配置类本身也是组件
        System.out.println(run.getBean("user1",User.class));

属性

案例一

@Configuration(proxyBeanMethods = true)
        MyConfig bean = run.getBean(MyConfig.class);
        //代理对象调用方法,springboot总会检查这个组件是否存在于容器中,
        //如果存在,就保持组件单实例
        User user3 = bean.user1();
        User user4 = bean.user1();
        System.out.println(user3 == user4);

返回true

@Configuration(proxyBeanMethods = false)
        MyConfig bean = run.getBean(MyConfig.class);
        //代理对象调用方法,不会去容器中检查,直接创建新的对象
        User user3 = bean.user1();
        User user4 = bean.user1();
        System.out.println(user3 == user4);

返回false


案例二

新建一个Pet类

public class Pet 
    private String name;
    private int age;
   /*
		setter、getter、constructor...
	*/

网User类中添加Pet属性

    private Pet pet;

    public void setPet(Pet pet) 
        this.pet = pet;
    

在配置类中使user组件对pet组件产生依赖

    @Bean
    public User user1()
        User zhang = new User("张三",17);
        zhang.setPet(pet1());
        return zhang;
    
    @Bean
    public Pet pet1()
        return new Pet("二哈",3);
    

proxyBeanMethods = true时,正常运行

proxyBeanMethods = false时,报错:


小结: 如果只是向容器中注册组件,且组件之间没有依赖,就把 proxyBeanMethods 设置为false,这样springboot启动过程会比较快。

@ComponentScan

根据定义的扫描路径,把符合扫描规则的类装配到spring容器中

默认情况下,springboot只会扫描启动类同级或者子包之下的文件,如图:
这种情况,springboot启动类能扫描到MyConfig

这种情况也能

但是当我要把它移动到启动类的父包之下时,就会报错:即这个位置,启动类扫描不到

修改springboot启动类扫描包的范围的方法1:

@SpringBootApplication(scanBasePackages = "com.example")

这样就可以扫描到MyConfig了

修改springboot启动类扫描包的范围的方法2:
可以直接用@SpringbootApplication的三个底层注解中的@ComponentScan

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.example")

@Import

@EnableAutoConfiguration的底层两个注解分别为:

  • @AutoConfigurationPackage
  • @Import(AutoConfigurationImportSelector.class)

@Import 向容器中导入指定类型的组件,可以自动在容器中创建所导入的组件
可以写在任何一个配置类或者其他组件中

        //从容器中获取组件
        String[] beanNamesForType = run.getBeanNamesForType(User.class);
        for (String s : beanNamesForType)
            System.out.println(s);
        
        System.out.println(run.getBean(EvaluatorFilter.class));


@Import 默认组件名为全类名

@Conditional

只有满足@Conditional指定的条件,才会进行组件注入

双击shift,输入 @Conditional

Ctrl + H查看继承关系

测试:

配置类:
给user1的组件注入加上条件:只有pet1组件存在于容器中,才注入user1组件

//    @Bean
    public Pet pet1()
        return new Pet("二哈",3);
    
    
    @ConditionalOnBean(Pet.class)//当容器中有容器pet1的时候才注入user1组件
    @Bean
    public User user1()
        User zhang = new User("张三",17);
        zhang.setPet(pet1());
        return zhang;
    

启动类测试

        System.out.println("容器中的pet1组件:" + run.containsBean("pet1"));
        System.out.println("容器中的user1组件:" + run.containsBean("user1"));

控制台返回false,说明由于容器中没有注入pet1,故而user1也没能注入

现在将pet1注入容器,再次测试:

    @Bean
    public Pet pet1()
        return new Pet("二哈",3);
    
    
    @ConditionalOnBean(Pet.class)//当容器中有容器pet1的时候才注入user1组件
    @Bean
    public User user1()
        User zhang = new User("张三",17);
        zhang.setPet(pet1());
        return zhang;
    

@Conditionalxxx加在方法上,表名要满足对应条件,才注册此方法的组件;如果加在类上,就表明,要满足对应条件,此配置类才会生效。

@ImportResource

@ImportResource 用于导入Spring的配置文件,让配置文件里面的内容生效;(就是以前写的springmvc.xml、applicationContext.xml)

案例:
现在,我直接向工程中导入了一个 beans.xml 配置文件,我需要将配置文件中的组件注入到IOC容器中。

配置文件如下:

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

    <bean id="liSi" class="com.example.boot01.bean.User">
        <property name="name" value="李四"></property>
        <property name="age" value="19"></property>
        <property name="pet" ref="lanMao"></property>
    </bean>

    <bean id="lanMao" class="com.example.boot01.bean.Pet">
        <property name="name" value="汤圆"></property>
        <property name="age" value="3"></property>
    </bean>
</beans>

现在我直接在容器中找 liSilanMao 组件:

        System.out.println("容器中的liSi组件:" + run.containsBean("liSi"));
        System.out.println("容器中的lanMao组件:" + run.containsBean("lanMao"));

现在我使用 @ImportResource 导入配置文件:

@ImportResource("classpath:beans.xml")//从resources下找配置文件
public class MyConfig 

再次查看容器中是否有这两个组件:

@Component + @ConfigurationProperties

@ConfigurationProperties.properties 或者 .yml 中的配置绑定到 JavaBean

测试:
新建一个car类

public class Car 
    private String brand;
    private Integer price;
    
   /*
		setter、getter、constructor...
	*/


在配置文件中给car对象配置属性

application.yml

mycar:
  brand: BBA
  price: 999999

给Car类加上注解,获取配置文件中的配置

@Component//只有在容器中的组件,才会拥有IOC容器提供的强大功能
@ConfigurationProperties(prefix = "mycar")
public class Car 

在控制类中自动注入

@RestController
public class HelloController 
    @Autowired
    Car car;

    @RequestMapping("/car")
    public Car car()
        return car;
    

测试:

@ConfigurationProperties + @EnableConfigurationProperties

@EnableConfigurationProperties(Car.class) 开启car自动配置功能,把car组件自动注入到容器中
写在配置类上

适用场景,使用第三方包,且包中的某些类并没有@Component注解

//@Component//只有在容器中的组件,才会拥有IOC容器提供的强大功能
@ConfigurationProperties(prefix = "mycar")
public class Car 
@Configuration(proxyBeanMethods = true)
@ImportResource("classpath:beans.xml")
@EnableConfigurationProperties(Car.class)
public class MyConfig 

2.springboot自动配置原理

@SpringBootConfiguration

底层就是一个 @Configuration,表名这就是一个配置类

@ComponentScan

就是指定要扫描哪些包或类

@EnableAutoConfiguration

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration 

@AutoConfigurationPackage

@AutoConfigurationPackage 自动配置包

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage 

利用register给容器导入一系列组件

接着进入 Registrar 类:

打上断点,开始debug:


选择new PackageImports(metadata).getPackageNames(),右键评估(Evaluate…)

可看到,这里已经利用注解元数据metadata获取到了我们的包 com.example.boot01,接着将这个包封装到了数组中.toArray(new String[0])

到了这里我们就可以知道:Registrar 就是将包注册进容器

小结: @EnableAutoConfiguration 就是将指定的包下面的所有组件导入容器,而这个包就是由 @ComponentScan 指定扫描的包。

@Import(AutoConfigurationImportSelector.class)

源码debug追踪自动配置文件原理

键入 AutoConfigurationImportSelector

这个类有一个方法:

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) 
		if (!isEnabled(annotationMetadata)) 
			return NO_IMPORTS;
		
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	

从方法代码中可以看出:这个方法就是通过 getAutoConfigurationEntry(annotationMetadata); 给容器批量导入一些组件

getAutoConfigurationEntry(AnnotationMetadata annotationMetadata)打上断点,进行debug:

可以看到,configurations 列表的数量从137变成了27

这里能看出:List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); 获取到所有需要导入到容器中的配置类

所以,接下来,我们在这个地方打上断点进行分析:

可以看到:


继续step into:

查看 loadSpringFactories 方法:

 private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) 
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        ......

就是通过这个方法加载获取到所有的组件

然后继续打上断点,进行debug查看加载方法:


可以看到是从 META-INF/spring.factories 中加载到文件的,即默认扫描我们当前系统中所有 META-INF/spring.factories 中的文件。

所以,当我们打开依赖包的时候,就可以看到有些包下面的 META-INF 包下面有文件:

spring-boot-test-autoconfigure-2.6.7.jar 这个包下面也有 spring.factories 文件

可以发现所有springboot启动时自动配置的文件已经在这里面被写死了

但是,我们从上面的debug可以发现,我们最终只加载了27个,这是为啥?

按需开启配置

虽然137个场景的所有自动配置会在启动的时候全部加载,但最终只会按需配置27个

随便打开自动配置包下面的文件:

可以看到这里面有很多 @Conditionalxxx 条件装配的注解

所有 按照条件配置的规则,很多不满足条件的场景不会被加载

分析AopAutoConfiguration的自动配置

先一点一点地看源码:

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration 
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)

上述代码说明:需要在配置文件中配置是否存在 spring.aop.auto 的配置,并且配置的值为 true ,则此配置就生效。
但是又加上了个 matchIfMissing = true ,说明,就算你没有在配置文件中配置,我也认为你配了,此类依然生效。

由于我们没有导入 org.aspectj.weaver.Advice; ,故而此配置类失效,跳过不看

查看第二个配置类:

由于我们正巧没有 org.aspectj.weaver.Advice;,故而此配置类生效

	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
			matchIfMissing = true)

这段代码和之前的一样,不管你配没配 spring.aop.proxy-target-classtrue 这个配置类都生效

而这个配置类是用来开启简易aop功能的配置类

总结
  • springboot先加载所有的自动配置类
  • 每个自动配置类按照条件进行生效(@Conditionalxxx),默认都会绑定指定的配置文件(@EnableConfigurationProperties(xxxProperties.class))并获取配置文件中的值
  • 生效的配置类会在容器中装配很多组件,只要容器中有这些组件,相当于这个程序就有了这些功能
  • springboot会默认在底层配置好所有组件,但是当用户自己配置了,则以用户的配置优先!(@ConditionalOnMissingBean

用户自定义配置:

  • 使用@Bean替换底层组件
  • 修改对应的配置文件的值(如server.port=8088

创作打卡挑战赛 赢取流量/现金/CSDN周边激励大奖

以上是关于springboot依赖管理和自动配置源码分析的主要内容,如果未能解决你的问题,请参考以下文章

springBoot自动配置原理源码分析+自定义starter启动器+可视化监控+mybatisPlus使用

SpringBoot原理深入及源码剖析

SpringBoot--自动配置原理分析

Springboot -自动配置分析

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

SpringBoot源码分析----SpringBoot自动配置原理