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>
现在我直接在容器中找 liSi 和 lanMao 组件:
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-class 为 true 这个配置类都生效
而这个配置类是用来开启简易aop功能的配置类
总结
- springboot先加载所有的自动配置类
- 每个自动配置类按照条件进行生效(
@Conditionalxxx
),默认都会绑定指定的配置文件(@EnableConfigurationProperties(xxxProperties.class)
)并获取配置文件中的值 - 生效的配置类会在容器中装配很多组件,只要容器中有这些组件,相当于这个程序就有了这些功能
- springboot会默认在底层配置好所有组件,但是当用户自己配置了,则以用户的配置优先!(
@ConditionalOnMissingBean
)
用户自定义配置:
- 使用
@Bean
替换底层组件 - 修改对应的配置文件的值(如
server.port=8088
)
以上是关于springboot依赖管理和自动配置源码分析的主要内容,如果未能解决你的问题,请参考以下文章