自动配置原理
1、SpringBoot特点
1、依赖管理
父项目做依赖管理
<!--依赖管理-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<!--他的父项目-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<!--几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制-->
开发导入starter场景启动器
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,要写版本号。
可以修改默认版本号
1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
2、在当前项目里面重写配置
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
2、自动配置
自动配好Tomcat
1、引入Tomcat依赖。
2、配置Tomcat
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
• 自动配好SpringMVC
1、引入SpringMVC全套组件
2、自动配好SpringMVC常用组件(功能)
• 自动配好Web常见功能,如:字符编码问题
1、SpringBoot帮我们配置好了所有web开发的常见场景
• 默认的包结构
1、主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
2、 无需以前的包扫描配置
3、想要改变扫描路径,@SpringBootApplication(scanBasePackages=“com.acoffee”)或者@ComponentScan 指定扫描路径
@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.acoffee.boot")
• 各种配置拥有默认值
1、默认配置最终都是映射到某个类上,如:MultipartProperties
2、配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
• 按需加载所有自动配置项
1、非常多的starter
2、引入了哪些场景这个场景的自动配置才会开启
3、SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面
2、容器功能
1、组件添加
1、@Configuration
Full模式与Lite模式
配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
MyConfig 类
/**
* 1.配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2.配置类本身也是组件(MyConfig类)
* 3.proxyBeanMethods:代理bean的方法
* Full:(proxyBeanMethods = true)【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
* Lite:(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
*
* 组件依赖必须使用Full模式默认。其他默认是否Lite模式
*/
@Configuration(proxyBeanMethods = true) //告诉SpringBoot这是一个配置类就相当于配置文件
public class MyConfig {
@Bean //给容器中添加组件,以方法名作为组件类型。返回的值,就是组件在容器中的实例
public User user01(){
User frank = new User("frank", 10);
//User组件依赖了Pet组件
frank.setPet(tomcat());
return frank;
}
@Bean("tom")
public Pet tomcat(){
return new Pet("tomcat");
}
}
测试
MainApplication 类:
/**
* 主程序类
* @author acoffee
* @create 2021-06-05 16:42
*/
//这个注解就说明了这是一个springboot应用
@SpringBootApplication
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));//组件:true
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);//com.acoffee.boot.config.MyConfig$$EnhancerBySpringCGLIB$$63c31af1@13cda7c9
//如果@configuation(proxyBeanMethods = true)代理对象调用方法,SpringBoot总会检查的这个组件的是否在容器中存在
//如果有的话就不会实例化,因为要保持组件单实例,但是如果我们将其改为@configuation(proxyBeanMethods = true)
//这个时候这里的结果就是false,就不会保持组件单实例了
User user = bean.user01();
User user1 = bean.user01();
System.out.println(user==user1);//true 说明外部无论对配置类中的这个组件方法调用多少次获取的都是之前注册容器中的单实例对象
//@configuation(proxyBeanMethods = true)
User user01 = run.getBean("user01", User.class);
Pet tom = run.getBean("tom", Pet.class);
System.out.println("用户的宠物:"+ (user01.getPet() == tom));//用户的宠物:true
}
}
2、@Bean、@Component、@Controller、@Service、@Repository
与Spring中的作用相同,这里不过多赘述
3、@ComponentScan、@Import
@ComponentScan:指定包扫描规则的跟以前一样。
@Import:给容器导入组件,写在容器中的组件里面,不管是配置类还是Controller组件都可以,形式为class的数组,可以导入自己有的,也可以导入第三方的组件。
/**
* 4.@Import({User.class, DBHelper.class})
* 给容器中自动创建出两个类型的组件、默认组件的名称就是全类名
*/
@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = true) //告诉SpringBoot这是一个配置类就相当于配置文件
public class MyConfig {
测试类:
//5.获取组件
String[] beanNamesForType = run.getBeanNamesForType(User.class);
System.out.println("====");
for (String s : beanNamesForType){
System.out.println(s); //com.acoffee.boot.bean.User(这个结果是@import导入的) user01(直接创建的)
}
DBHelper bean1 = run.getBean(DBHelper.class);
System.out.println(bean1); //ch.qos.logback.core.db.DBHelper@5b69d40d
}
4、@Conditional
条件装配: 满足Conditional指定的条件,则进行组件注入
我们这里测试@ConditionalOnBean注解:意思是 容器中有名为name的组件Bean才生效,没有就不生效。
@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = true) //告诉SpringBoot这是一个配置类就相当于配置文件
//@ConditionalOnBean(name = "tom") 我们也可以将@Conditional声明在这里:表示下面的bean对象没有tom组件就不生效
public class MyConfig {
@ConditionalOnBean(name = "tom")
@Bean //给容器中添加组件,以方法名作为组件类型。返回的值,就是组件在容器中的实例
public User user01(){
User frank = new User("frank", 10);
//User组件依赖了Pet组件
frank.setPet(tomcat());
return frank;
}
// @Bean("tom") 我们在这里把tom组件注解掉,即没有tom组件
public Pet tomcat(){
return new Pet("tomcat");
}
}
测试类:
boolean tom = run.containsBean("tom");
System.out.println("容器中的tom组件:"+tom);//容器中的tom组件:false
boolean user01 = run.containsBean("user01");
System.out.println("容器中的user01组件:"+user01);//容器中的user01组件:false
}
2、原生配置文件引入
1、@ImportResource
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="haha" class="com.acoffee.boot.bean.User">
<property name="name" value="frank"></property>
<property name="age" value="18"></property>
</bean>
<bean id="hehe" class="com.acoffee.boot.bean.Pet">
<property name="name" value="tomcat"></property>
</bean>
</beans>
MyConfig 类:
@ImportResource("classpath:beans.xml") //@ImportResource("classpath:beans.xml")导入Spring的配置文件,让组件进行生效
public class MyConfig {
....
测试:
boolean haha = run.containsBean("haha");
boolean hehe = run.containsBean("hehe");
System.out.println("haha:"+haha);//haha:true
System.out.println("hehe:"+hehe);//hehe:true
}
}
3、配置绑定
如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用;
1、@Component + @ConfigurationProperties:添加到容器中
application.properties文件
mycar.brand=BWM
mycar.price=100000
Car 类
@Data
@Component
@ConfigurationProperties(prefix = "mycar") //配置文件中前缀为mycar的属性
public class Car {
private String brand;
private Integer price;
}
Controller组件
@RestController
public class HelloController {
@Autowired
Car car;
@RequestMapping("/car")
public Car car(){
return car;
}
}
执行结果:
2、@EnableConfigurationProperties + @ConfigurationProperties:开启功能(的方式)
这种情况适用于,比如说你引入第三方的组件,这个组件并没有标注 @Component , 你肯定不能给人家标注,就用这种方法。
这个注解组合有两个功能:
1. 开启Car配置绑定功能
2. 把这个Car这个组件自动注册到容器中
@EnableConfigurationProperties(Car.class)
//1.开启Car配置绑定功能
//2.把这个Car这个组件自动注册到容器中
public class MyConfig {
...
Car 类
@Data
//@Component
@ConfigurationProperties(prefix = "mycar") //配置文件中前缀为mycar的属性
public class Car {
private String brand;
private Integer price;
}
Controller组件
@RestController
public class HelloController {
@Autowired
Car car;
@RequestMapping("/car")
public Car car(){
return car;
}
}
执行结果:
总结:配置绑定有两种方法要么使用添加到容器中的方法、要么使用开启功能的方式。而且绑定的属性只能在SpringBoot核心配置文件中。
3、自动配置原理入门
1、引导加载自动配置类
@SpringBootApplication 的作用是说明了这是一个springboot应用,是由以下三个注解组合而成的,我们分解每个注解作用。
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
...
1、@SpringBootConfiguration
@Configuration。代表当前是一个配置类
2、@ComponentScan
指定扫描哪些,Spring注解;
3、@EnableAutoConfiguration
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
① @AutoConfigurationPackage
自动配置包?指定了默认的包规则
@Import(AutoConfigurationPackages.Registrar.class) //给容器中导入一个组件
public @interface AutoConfigurationPackage {}
//利用Registrar给容器中导入一系列组件
//将指定的一个包下的所有组件导入进来?MainApplication 所在包下。
② @Import(AutoConfigurationImportSelector.class)
1、利用getAutoConfigurationEntry(annotationMetadata);
给容器中批量导入一些组件
2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)
获取到所有需要导入到容器中的配置类
3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);
得到所有的组件
4、从META-INF/spring.factories位置来加载一个文件。
默认扫描我们当前系统里面所有META-INF/spring.factories
位置的文件
spring-boot-autoconfigure-2.3.4.RELEASE.jar
包里面也有META-INF/spring.factories
文件里面写死了spring-boot一启动就要给容器中加载的所有配置类,类路径为spring-boot-autoconfigure-2.3.4.RELEASE.jar/META-INF/spring.factories
2、按需开启自动配置项
虽然我们127个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration
但是不会全部生效,只有满足条件装配规则(@Conditional),才会生效。
3、修改默认配置
@Bean
@ConditionalOnBean(MultipartResolver.class) //容器中有这个类型组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolver 的组件
public MultipartResolver multipartResolver(MultipartResolver resolver) {
//给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
//SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
//给容器中加入了文件上传解析器;
SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {}
例子:
给字符编码过滤器设置字符编码的时候,字符编码是从后面的properties文件中来,properties中的东西是从ServerProperties中来,ServerProperties获取到Servlet的Encoding,而ServerProperties是绑定了我们的配置文件(application.properties),所以properties得到的名字就是就是编码名.
总结:
- SpringBoot先加载所有的自动配置类
xxxxxAutoConfiguration
- 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。
xxxxProperties
里面拿。xxxProperties
和配置文件(application.properties)进行了绑定 - 生效的配置类就会给容器中装配很多组件
- 只要容器中有这些组件,相当于这些功能就有了
- 定制化配置
①用户直接自己@Bean替换底层的组件
②用户去看这个组件是获取的配置文件什么值就去修改。
xxxxxAutoConfiguration
—> 组件 —>xxxxProperties
里面拿值 ---->application.properties
4、最佳实践
- 引入场景依赖
• https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter - 查看自动配置了哪些(选做)
• 自己分析,引入场景对应的自动配置一般都生效了
• 配置文件中debug=true开启自动配置报告。Negative(不生效)\ Positive(生效) - 是否需要修改
• 参照文档修改配置项:https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#common-application-properties
• 自己分析。xxxxProperties绑定了配置文件的哪些。
• 自定义加入或者替换组件
@Bean、@Componen …
• 自定义器
xxxxCustomizer;
• …
4、开发小技巧
1、Lombok
Lombok的安装分两步:Idea Lombok插件的安装(setting中) 和 maven中pom.xml文件的导入。
简化JavaBean开发
@Data //提供该类所有属性的getter/setter方法,还提供了equals、canEqual、hashCode、toString方法。
@NoArgsConstructor //无参构造器
@AllArgsConstructor //全参构造器
@Log4j //为该类提供一个属性名为log的log4j日志对象
public class Pet {
private String name;
}
2、dev-tools
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
类似热更新实际上是自动重启,项目或者页面修改以后:Ctrl+F9;
3、Spring Initailizr(项目初始化向导)
设置包名,java的版本等
选择我们需要的开发场景
自动创建项目结构
自动依赖引入
自动编写好主配置类