1.注解
1.1Configuration
在spring中,往往在spring的xml文件中实现注入,这样子很麻烦。在SpringBoot中我们可以通过一个创建一个类(MyConfig名字自定义),给这个类加上Configuration注解声明这是一个配置类,可以在这个类中实现组件注入。
首先准备两个实体类:
package com.zhouqun.pojo;
/**
* @author 周区区
* @version 1.0
* @date 2021/7/14 10:18
*/
public class Pet {
private String petname;
private Integer petage;
public String getPetname() {
return petname;
}
public void setPetname(String petname) {
this.petname = petname;
}
public Integer getPetage() {
return petage;
}
public void setPetage(Integer petage) {
this.petage = petage;
}
}
package com.zhouqun.pojo;
/**
* @author 周区区
* @version 1.0
* @date 2021/7/14 10:17
*/
public class User {
private String username;
private Integer userid;
public User(){
}
public User(String username, Integer userid) {
this.username = username;
this.userid = userid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getUserid() {
return userid;
}
public void setUserid(Integer userid) {
this.userid = userid;
}
}
然后通过@Configuration自定义一个配置类,通过@Bean实现组件注入,也可以通过@Bean(“别名”)来给组件自定义名称:
package com.zhouqun.config;
import com.zhouqun.pojo.Pet;
import com.zhouqun.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author 周区区
* @version 1.0 自定义配置类
* @date 2021/7/14 10:19
*/
//告诉SpringBoot这是一个配置类==配置文件
@Configuration
public class MyConfig {
/**
* @Bean实现组件注入==<bean/>
* 往容器中添加组件,方法名就是组件id
* 返回值就是组件在容器中的实例
* @return
*/
@Bean
public User user(){
User user = new User("张三", 101);
return user;
}
@Bean("tom")
public Pet tomCat(){
Pet tomcat = new Pet("tomcat", 2);
return tomcat;
}
}
然后在主方法中测试组件是否注入成功:
@SpringBootApplication
public class Springboot01Application {
public static void main(String[] args) {
//返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(Springboot01Application.class, args);
//从容器中获取组件
User user = run.getBean("user", User.class);
Pet tom1 = run.getBean("tom", Pet.class);
Pet tom2 = run.getBean("tom", Pet.class);
System.out.println("tom1与tom2是否是同一个实例"+(tom1==tom2));//true
}
}
小结:
- 配置类也是一个组件。
- 配置类里面使用@Bean标签标注在方法上给容器注册组件,默认使单实例的。
1.2proxyBeanMethods
在@Configuration中,在Spring5.2之后多了个属性:proxyBeanMethods。
这个属性默认是true,这个属性的意思是:是不是代理Bean方法。
boolean proxyBeanMethods() default true;
所以@Configuration的默认写法是:
@Configuration(proxyBeanMethods=true)
Full:全配置。当proxyBeanMethods值为true时,MyConfig这个配置类就是被cglib增强的代理对象。SpringBoot总会检查容器中是否存在这个组件(user,tom调用哪个检查哪个),保持组件单实例。
Lite:轻量级配置。当proxyBeanMethods值为false时,SpringBoot不会检查,会直接创建新的实例。
这个属性的作用时为了解决组件依赖问题。
假如User中多了个Pet属性:
public class User {
private String username;
private Integer userid;
private Pet pet;
}
修改MyConfig的方法:
@Configuration(proxyBeanMethods = true)
//告诉SpringBoot这是一个配置类==配置文件
public class MyConfig {
@Bean
public User user(){
User user = new User("张三", 101);
//user组件依赖于pet组件
// 当proxyBeanMethods=true时,这种依赖是成立的
//当proxyBeanMethods=false时,这种依赖不成立
user.setPet(tomCat());
return user;
}
@Bean("tom")
public Pet tomCat(){
Pet tomcat = new Pet("tomcat", 2);
return tomcat;
}
}
当proxyBeanMethods=true时,User的Pet和容器中的Pet是同一个,但是当proxyBeanMethods=false时,那么User的Pet和容器中的Pet不是同一个。
小结:
- 当proxyBeanMethods=true时,user和pet的依赖关系成立
- 当proxyBeanMethods=false时,user和pet的依赖关系不成立
1.3Import
给容器导入组件,可以写在任何一个配置类或者组件里面。(@Import要写在@Configuration…@Service等注解之上),通过查看Import源码可知,这是一个数组:
public @interface Import {
Class<?>[] value();
}
所以Import的写法是:大括号数组格式
//在容器中自动创建出这两个类型的组件
@Import({User.class, DBHelper.class})
然后在启动类中测试:
//获取所有User类型的组件
String[] beanNamesForType = run.getBeanNamesForType(User.class);
for (String s : beanNamesForType) {
System.out.println(s);
}
测试结果:
=========
com.zhouqun.pojo.User//@Import导入
user//@Bean导入
可以看到通过@Import和@Bean注入的User组件都存在。
@Import导入的组件名字为全限定类名
1.4Conditional
条件装配:满足Conditional指定的条件,则进行组件注入。
Conditional的派生注解如下:
首先把tom组件注释掉,此时容器里面不存在tom组件了,然后给user组件加个条件:当容器中存在tom组件时,才会注入user组件:
@Configuration(proxyBeanMethods = false)
//告诉SpringBoot这是一个配置类==配置文件
public class MyConfig {
@ConditionalOnBean(name="tom")
@Bean
public User user(){
User user = new User("张三", 101);
user.setPet(tomCat());
return user;
}
// @Bean("tom")
public Pet tomCat(){
Pet tomcat = new Pet("tomcat", 2);
return tomcat;
}
去启动类中测试一下:
//当tom组件存在时才会注入user
boolean tom = run.containsBean("tom");
System.out.println("容器中的tom组件是否存在"+tom);
boolean user1 = run.containsBean("user");
System.out.println("容器中的user组件是否存在"+user1);
测试结果:
=========
容器中的tom组件是否存在false
容器中的user组件是否存在false
可见,tom组件不存在时,user不会注入,然后去修改一下条件,当tom组件不存在时,user注入:
@ConditionalOnMissingBean(name="tom")
@Bean
public User user(){
User user = new User("张三", 101);
user.setPet(tomCat());
return user;
}
测试结果:
=========
容器中的tom组件是否存在false
容器中的user组件是否存在true
啊,真好用啊!
@ConditionalOnBean(name=“tom”)还可以标注在类上:
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(name="tom")
public class MyConfig {
@Bean
public User user(){
User user = new User("张三", 101);
user.setPet(tomCat());
return user;
}
@Bean("tom")
public Pet tomCat(){
Pet tomcat = new Pet("tomcat", 2);
return tomcat;
}
}
只有在tom组件存在时,MyConfig类中的方法才会生效。
测试结果:
=========
容器中的tom组件是否存在false
容器中的user组件是否存在false
可见,程序启动时会先在容器中扫描tom组件,因为tom不存在,所以即使在MyConfig中标注了 @Bean(“tom”) MyConfig中的方法也不会执行。
1.5ImportResource
有可能项目中依旧存在spring的配置文件来实现注入,例如下面这个配置文件,注入user和pet:
<?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="user01" class="com.zhouqun.pojo.User">
<property name="username" value="张三"/>
<property name="userid" value="12"/>
<property name="pet" ref="pet01"/>
</bean>
<bean id="pet01" class="com.zhouqun.pojo.Pet">
<property name="petname" value="哈士奇"/>
<property name="petage" value="3"/>
</bean>
</beans>
当配置文件的内容非常庞大时,想要把标签挨个转换成@Bean注解的方式,这是非常麻烦的。也有可能引入的第三方包,也是使用配置文件方式来注入组件。这时可以通过@ImportResource注解来引入配置文件,并重新解析配置文件。
@Configuration(proxyBeanMethods = false)
@ImportResource("classpath:spring.xml")
public class MyConfig {
@Bean
public User user(){
User user = new User("张三", 101);
user.setPet(tomCat());
return user;
}
@Bean("tom")
public Pet tomCat(){
Pet tomcat = new Pet("tomcat", 2);
return tomcat;
}
}
去启动类测试容器中是否存在user、tom、user01、pet01:
=========
容器中的tom组件是否存在true
容器中的user组件是否存在true
容器中的user01组件是否存在true
容器中的pet01组件是否存在true
妙蛙妙蛙!
1.6配置绑定
我们习惯将经常变化的东西放在配置文件中,最常见的就是数据库连接,端口号,ip等,在需要使用的进行解析。
- @Component+@ConfigurationProperties(prefix = “配置文件中的对象名”) (写在实体类Car上)
- @EnableConfigurationProperties(写在配置类MuConfig上)开启Car配置绑定功能;将Car自动注入到容器
首先新建一个Car类:
public class Car {
private String brand;
private Integer price;
}
然后将属性brand和price的值写到SpringBoot的配置文件中:application.properties:
mycar.brand=BYD
mycar.price=10000
@Component+@ConfigurationProperties(prefix = “配置文件中的对象名”)
给Car添加两个注解解析配置文件:
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
private String brand;
private Integer price;
}
注意:一定要加@Component,因为只有将car作为组件注入,才能使用SpringBoot的强大功能。
去测试类中测试一下是否能拿到配置文件中的值:
@SpringBootTest
class Springboot01ApplicationTests {
@Autowired
Car car;
@Test
void contextLoads() {
System.out.println(car);
}
}
测试结果:
Car{brand='BYD', price=10000}
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "mycar")
public class Car {
private String brand;
private Integer price;
}
@Configuration(proxyBeanMethods = false)
@ImportResource("classpath:spring.xml")
@EnableConfigurationProperties(Car.class)
public class MyConfig {
//注入user、pet....
}
测试结果:
Car{brand='BYD', price=10000}
注意:Car上面的@ConfigurationProperties(prefix = “mycar”)不能去掉,否则绑定不了配置文件。这两种方法读取的都是SpringBoot项目中的application.yml或application.properties核心配置文件,不能随便绑定一个文件。
2.自动配置原理入门
SpringBoot的启动类上有一个注解:
@SpringBootApplication
查看这个注解的源码可以发现这是由其他三个核心注解组合而来的:
2.1引导加载自动配置类
1.@SpringBootConfiguration
点进这个注解看源码可以发现这个类实际上是一个@Cionfiguration,这是一个配置类。
2.@ComponentScan
包扫描注解,指定扫描哪些包,不过多研究
3.@EnableAutoConfiguration(核心)
主要是最后两个注解:
1)首先我们进入到@AutoConfigurationPackages,可以发现它实际上是通过@Import导入了一个Registrar组件:
去查看Registrar是个什么东西:
通过Registrar给容器批量注入组件,注入哪些组件?
先打个断点然后debug启动:
首先看断点方法的两个参数:metadata:这个是注解(@AutoConfigurationPackages)的源信息,代表这个注解标在哪。通过deBug可知这个注解作用在启动类上:
于是,打断点的这行代码可以理解为:利用注解(@AutoConfigurationPackages)源信息,获取到一个包名,然后把获取到的信息存进数组。
获取到什么包名呢?可以利用Evaluate查看一下(debug体条件下才能用):首先选中"new PackageImports(metadata).getPackageNames()" 然后快捷键“alt+f8”,可以看到获取到的包名为“com.zhouqun”。所以就是将这个包及其子包下的组件导入进来。
注意: 这就是为什么新建的包要在启动类所在包下面,因为出了启动类所在包的范围,就扫描不到了!!
2)接下来查看@Import(AutoConfigurationImportSelector.class)注解,利用Selector机制批量导入组件,导入什么组件呢,去AutoConfigurationImportSelector中看一看:
这个类中有一个方法叫selectImports(),所有的组件都是靠getAutoConfigrationEntry()方法拿到所有的配置,然后转成String数组返回。
我们进入getAutoConfigrationEntry()看拿到哪些组件,首先打个断点:
往下走两行看到一个方法getCandidateConfigrations(),拿到所有候选配置:configurations。往下可以看到对configurations进行了一系列筛选操作后封装返回。而这个configurations的长度为131,表示有131个组件都是要返回加载的:
它怎么知道是这些类呢?规则是什么?我们进入getCandidateConfigrations()方法看一看:
可以看到是利用Spring的工厂加载器来加载一些东西,进入loadFactorNames()继续看:
继续进入loadSpringFactories():
到头了,加载META-INF/spring.factories。所有启动要加载的配置类都在这个文件里面写死了。
这个文件的位置:
虽然写死了,springboot一启动就要加载所有配置类,但并不是所有的配置都生效。这就涉及到按需开启。
2.2自动按需加载
什么是自动按需加载,我们随便点进一个包:
这个类在spring.factories中,启动就加载,但是可以看到Advice.class是红的,因为这个类在容器中并不存在,所以条件不成立,所以这个注解下的这个类虽然加载但并没有生效。我们可以看上面导的包:
因为Advice所在的包没有导入,所以Advice不存在,这个类不生效。
雷神:自动装配流程
雷神的这集自动装配流程值得反复观看。
3.最佳实践
在以后的开发中,只需要遵循以下步骤。
- 引入场景依赖:springboot的starter启动器
- 查看自动配置了什么(底层原理,不需要过于关心)
在application.properties或者application.yml文件中,加入:
debug=true
然后启动项目,控制台会打印出哪些配置类生效,哪些没生效。
- 是否需要修改:参照文档修改配置项
- 自定义加入或替换组件:@Bean、@Component
- 自定义器:xxxxCustomizer
4.开发小技巧
4.1Lombok
可以简化javabean的开发。SpringBoot已经管理了Lombok的版本,我们只需要在pom.xml文件中引入依赖(也可以创建项目时勾选Lombok):
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
然后安装Lombok插件:
然后在实体类上添加lombok的注解:
@Data //生成无参构造器、get、set、equals、hashCode、toString方法
@AllArgsConstructor //生成有参构造
@NoArgsConstructor //生成无参构造
@ToString //生成toString
lombok还有个日志注解:
@Slf4j
//使用方法
log.info("这是Slf4j");
缺点:一个人用,整个团队都得用
4.2dev-tools
热更新:Springboot-develop-tools
导入官方依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
然后需要更新的时候就按【ctrl+f9】也就是Build Project(其实就是重启restart),Spring官方说如果想用纯正的热更新就去用【 JRebel 】,要付钱= =