Springboot自动装配原理
1. 零散的知识点
@Controller
@RequestMapping("/hello")
public class HelloController {
@GetMapping("h1")
@ResponseBody //此标签表示 返回一个“hello”字符串而不是“hello.jsp"页面
public String hello(){
return "hello";
}
}
@Controller :
- 控制器Controller 负责处理由DispatcherServlet 分发的请求。
- 它把用户请求的数据经过业务处理层处理之后封装成一个Model ,然后再把该Model 返回给对应的View 进行展示。
@RestController:
给view返回一个字符串
@ResponseBody:
此标签表示 返回一个“hello”字符串而不是“hello.jsp"页面
application.properties
#更改项目端口号
server.port=8081
2.自动装配原理
pom.xml
<!--启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
- 启动器:说白了就是Springboot的启动场景
- 例如spring-boot-starter-web,他就会帮我们自动导入web环境所有的依赖
- springboot会将所有的功能场景,都变成一个个的启动器
- 我们要使用什么功能,只需要找到相应的启动器就好(可以在官方文档中找到)
spring-boot-dependencies
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
spring-boot-dependencies:作为父依赖,存放了SpringBoot的核心依赖。我们在写或者引入一些SpringBoot依赖的时候,不需要指定版本,正是因为SpringBoot的父依赖已经帮我们维护了一套版本。
主程序 (重点)
在写SpringBoot项目的时候,总要写这么一个主程序,这个主程序最大的特点就是其类上放了一个**@SpringBootApplication**注解,这也正是SpringBoot项目启动的核心,也是我们要研究的重点。
//@SpringBootApplication 标注,是一个SpringBoot应用
@SpringBootApplication
public class SpringbootdemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootdemoApplication.class, args);
}
}
点开**@SpringBootApplication**,可以发现它是一个组合注解,主要是由这么几个注解构成的。
我们首先要研究的就是核心的两个注解 @SpringBootConfiguration和**@EnableAutoConfiguration**,逐个进行分析。
@SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
可以看到SpringBootConfiguration其实就携带了一个@Configuration注解,这个注解我们再熟悉不过了,他就代表自己是一个Spring的配置类。所以我们可以认为:@SpringBootConfiguration = @Configuration
@EnableAutoConfiguration
顾名思义,这个注解一定和自动配置相关,点进去看源代码之后可以发现,其内部就包含了这么两个注解。
@AutoConfigurationPackage //自动配置包
@Import(AutoConfigurationImportSelector.class)//自动配置导入选择
点进**@AutoConfigurationPackage**后发现,里面有一个注解
@Import(AutoConfigurationPackages.Registrar.class)
//利用Registrar给容器中导入一系列组件(批量导入)
//AutoConfigurationPackages.Registrar:把某一个包下的组件批量导入容器中
//哪个包呢,就是MainApplication程序所在包下
来看看**@Import(AutoConfigurationImportSelector.class)**中的内容:
它帮我们导入了AutoConfigurationImportSelector,这个类中存在一个方法可以帮我们获取所有的配置,类中有个方法叫selectImports 代码如下。
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
- 利用**getAutoConfigurationEntry(annotationMetadata);**给容器中批量导入一些组件
进入**getAutoConfigurationEntry()**方法,可以看到该方法中有一行代码:
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
- 调用**List configurations = getCandidateConfigurations(annotationMetadata, attributes)**获取到所有需要导入到容器中的配置类
从**getCandidateConfigurations()**方法一直往里找,最终可以看到一个方法,它的返回值是一个Map
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader)
- 利用工厂加载 **Map<String, List> loadSpringFactories(@Nullable ClassLoader classLoader);**得到所有的组件
该方法中有一行代码如下
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
- 从META-INF/spring.factories位置来加载一个文件。默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
可以看到里面包含了很多自动配置属性:
我们可以随便找一个自动配置点进去,比如WebMvcAutoConfiguration:
这里放了所有关于WebMvc的配置,如视图解析器、国际化等等。
分析到这里,我们就可以得出一个完整的结论了:
当我们的SpringBoot项目启动的时候,会先导入AutoConfigurationImportSelector,这个类会帮我们选择所有候选的配置,我们需要导入的配置都是SpringBoot帮我们写好的一个一个的配置类,那么这些配置类的位置,存在与META-INF/spring.factories文件中,通过这个文件,Spring可以找到这些配置类的位置,于是去加载其中的配置。
总结:
@SpringBootApplication会去spring-boot-autoconfigure-2.4.4.jar中寻找spring.factories,然后获取spring.factories中配置类的地址信息,通过地址信息找到对应的配置类并加载它。 但是不是所有存在于spring,factories中的配置都进行加载,而是通过@ConditionalOnClass注解进行判断条件是否成立(只要导入相应的stater,条件就能成立),如果条件成立则加载配置类,否则不加载该配置类。
在这里贴一个我认为的比较容易理解的过程:
- SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
- 将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
- 以前我们需要自己配置的东西 , 自动配置类都帮我们解决了
- 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
- 它将所有需要导入的组件以全类名的方式返回 , 这些组件就会被添加到容器中 ;
- 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
- 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;
3.SpringBoot给容器中注册组件
3.1 @Configuration(proxyBeanMethods = true)
- @Configuration 告诉springboot,MyConfig是一个配置类
- proxyBeanMethods(代理bean的方法)可以不写,默认值为true
- 如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件(@Bean注册的组件)是否在容器中有。保持组件单实例
注:
- 配置类本身也是组件
- 注册的组件默认都是单实例的
- proxyBeanMethods:代理bean的方法
- Full模式:(proxyBeanMethods = true)【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
- Lite模式:(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
- 组件依赖必须使用Full模式默认。其他默认是否Lite模式
com/lvhang/boot/config/MyConfig.java
@Configuration(proxyBeanMethods = true)
public class MyConfig {
/**@Bean注解:给容器中添加组件,以方法名作为组件的Id,返回类型就是组件类型。方法返回的对象就是组件在容
* 器中的实例,外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
**/
@Bean
public User user(){
return new User("zhangsan", 18);
}
@Bean("tomisacat")
public Pet getPet(){
return new Pet("tomcat");
}
}
3.2 @Bean、@Component、@Controller、@Service、@Repository
3.3 @Import({User.class, DBHelper.class}) :给容器中导入一个组件
3.4 @Conditional:条件装配,满足Conditional指定的条件,则进行组件注入
@Conditional 使用举例
//@Configuration 告诉springboot,MyConfig是一个配置类
//proxyBeanMethods(代理bean的方法)可以不写,默认值为true
@Configuration(proxyBeanMethods = true)
public class MyConfig {
//@Bean注解:给容器中添加组件,以方法名作为组件的Id,返回类型就是组件类型。方法返回的对象就是组件在容器中的实例
@Bean
//如果容器中有名为"tom"的组件时,才在容器中注册user01
@ConditionalOnBean(name="tom")
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");
}
}
3. 原生配置文件导入
@ImportResource
在随便一个配置类上加上**@ImportResource(“classpath:beans.xml”)注解,相当于把beans.xml解析放在容器中。classpath:beans.xml指的是resource路径下的beans.xml文件**。
4. 配置绑定
配置绑定就是读取properties文件中的内容,并且把它封装到JavaBean中,以供随时使用。
方法一:@ConfigurationProperties+@Component
先准备一个实体类Car.java
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 +
'}';
}
}
application.properties
mycar.brand=BYD
mycar.price=100000
给Car类加注解@ConfigurationProperties进行配置绑定
/**
* 为了使@ConfigurationProperties生效,需要用@Component把组件注册到容器中
* 只有在容器中的组件,才会拥有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 +
'}';
}
}
测试
@RestController
public class HelloController {
//使用spring的自动注入,获取容器中的Car
@Autowired
Car car;
@RequestMapping("/car")
public Car car(){
return car;
}
}
测试结果
方法二: @ConfigurationProperties+@EnableConfigurationProperties
注:
1.不是在实体类中加注解@EnableConfigurationProperties,而是在配置类中加注解
2.对应实体类一定要加@ConfigurationProperties(prefix = “mycar”)
3.方法二 一般是实体类没法加@Component时使用,如实体类为导入的第三方jar包时
@EnableConfigurationProperties(Car.class)
- 开启Car的配置绑定功能
- 把Car这个组件自动注册到容器中
//1.开启Car的配置绑定功能
//2.把这个Car这个组件自动注册到容器中
@EnableConfigurationProperties(Car.class)
public class MyConfig {
//@Bean注解:给容器中添加组件,以方法名作为组件的Id,返回类型就是组件类型。方法返回的对象就是组件在容器中的实例
@Bean
@ConditionalOnBean(name="tom")
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");
}
}