今天学习了 SpringBoot 中的一些注解,不过有些注解是 Spring 中的,它们分别是 @Configuration、@Import、@Bean、@Conditional、@ImportResource、@ConfigurationProperties、@EnableConfigurationProperties 注解。
学完这些注解之后,突然有一个想法。就是之前在 SpringBoot 的 Web 模块的时候,SpringBoot 好像还是需要我们来自己编写拦截器,这就感到费时间,有点不符合 SpringBoot 的少量配置的特点。暑假想自己尝试写一个基于 Spring MVC 的拦截器的第三方库。
我觉得这个还是等到把雷神的 SpringBoot 给弄完,能在暑假之内做出来是最好的了。
Configuration 注解
Configutation 注解,标记在某一个类上,这个类就成为了一个配置类了。标记的效果等同于在 Spring 的 xml 配置文件一样。
有一个名词叫”注册“,注册的意思就是创建一个对象,像是 new Random(); 这种写法。
既然和 xml 的配置文件一样注册对象,那它向容器中注册对象的方式是这样的。也就是说,需要先声明一个方法,需要在方法上面标记一个 Bean 注解,Bean 注解就是在 Spring 的 ioc 容器(后续简称容器)中添加对象,默认是以方法名作为对象id,返回类型就是对象的类型。
@Configuration
public class MyConfig {
@Bean("zhangsan")
public User user01() {
User zhangsan = new User("zhangsan", 10);
return zhangsan;
}
@Bean
public User user02() {
User lisi = new User("lisi", 10);
return lisi;
}
@Bean
public Pet pet01() {
return new Pet("tomcat");
}
@Bean
public Pet pet02() {
return new Pet("tomcat222");
}
}
其中,我们可以自己在 Bean 注解中指定id,比如 @Bean("zhangsan")
,指定名字完之后,那么这个对象的 id 就不再是 user01 ,而是 zhangsan 。
如何得到容器中的对象
获取容器的对象也非常简单,和以前使用 ApplicationContext 对象一样,也是利用 getBean 来获取的。
// Spring IOC容器
ConfigurableApplicationContext run = SpringApplication.run(SpringBootApplication.class, args);
// 获取 user02
User user02 = (User) run.getBean("user02");
System.out.println(user02);
// 获取 zhangsan
User zhangsan = run.getBean("zhangsan", User.class);
System.out.println(zhangsan);
运行结果如下:
User{brand='lisi', age=10}
User{brand='zhangsan', age=10}
单实例
如果我们多次获取容器中的对象,那么我们获取到的对象是否为同一个呢?通过以下代码进行验证。
// 两个实例是不是一样的?
Pet pet1 = (Pet) run.getBean("pet01");
Pet pet2 = (Pet) run.getBean("pet01");
System.out.println("pet1==pet2 ? " + (pet1 == pet2));
运行结果如下:
pet1==pet2 ? true
运行结果为 true ,意味着所得到的对象实例是单实例的。
配置类本身也是对象
我们创建的配置类其实本身也是一个对象。我们也可以将它给调用出来
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);
运行结果如下:
top.bestguo.config.MyConfig$$EnhancerBySpringCGLIB$$137f765f@62315f22
不过,我们得到的配置类是一个加强版的类。一看到 CGLIB 就知道,这是一个被代理过的对象,里面的对象也是由 CGLIB 进行代理的。那它代理了什么呢?
其实,在 SpringBoot 2.3 之后的版本,由于 SpringBoot 2.3 是基于 Spring 5.2+ 的,Configuration 注解多了一个叫做 proxyBeanMethod 的属性,是因为它可以设置 true 和 false ,如果设置成了 true 就是这个对象是被代理的,设置成 false 就是未被代理的。
那,代理的意义何在呢?🧐🧐
proxyBeanMethod
该注解中的属性,默认值为 true 。
上面部分提到过这个属性设置成 true 和 false ,配置类分别就是被代理和未被代理的。上面也有提到,配置类也是一个对象,配置类对象里面不是有很多方法吗?那我们调用一下这里面的方法试试看。
如果我多次调用这个配置类对象中的方法,直接获取 ioc 容器中注册的对象,还是仅仅只是单纯的方法调用,每次的调用返回的对象都是不同的呢?
MyConfig bean = run.getBean(MyConfig.class);
User user01 = bean.user01();
User user011 = bean.user01();
System.out.println("从配置容器中调用的方法,是一样的吗?" + (user01 == user011));
运行结果如下,发现得到的方法是同一个对象。
从配置容器中调用的方法,是一样的吗?true
那我们把 proxyBeanMethod 值改成 false 会怎么样呢?运行结果如下。
top.bestguo.config.MyConfig@5bdaf2ce
从配置容器中调用的方法,是一样的吗?false
很显然,MyConfig 这个配置类不再被代理了,并且这两次的调用都是 false。那我们直接通过 getBean 的方式看看得到的两个对象是否为同一个。
User zhangsan = run.getBean("zhangsan", User.class);
User zhangsan2 = run.getBean("zhangsan", User.class);
System.out.println("是不是同一个?" + (zhangsan == zhangsan2));
运行结果如下,发现这是同一个对象。
是不是同一个?true
所以,proxyBeanMethod 对象的用途,如果设置成 true ,那么在调用 MyConfig 配置类中的方法时,就会先去拿到这个 Bean 的 id,来确认这个对象是不是存在于容器中,如果存在与容器中,调用配置类中的方法时就直接去拿容器中的对象。
如果是 false ,它就不会通过 id 去确认容器中是否存在该对象,相当于是直接调用 MyConfig 中的方法而已,并且这个方法返回的对象并不是从容器中直接获取的,而是单纯的在方法中 new 一个对象出来。每一次 new 出来的对象都是不同的。
使用场景 – 组件依赖
组件的意思就是容器中所实例化的对象。
一位名叫 zhangsan 的主人,它刚刚养了一只狗,这只狗狗的名字叫 ”tomcat“。刚没养几天,主人就要回老家三个月有事情,它只好将这只狗狗放到了宠物临时收养的机构进行收养。等主人回到家,回到宠物临时收养机构领取自己的宠物狗狗。
回到家之后,它去领养。宠物临时收养机构管理人员可能将该主人的狗狗信息丢了,只记得狗狗的名字;或者也有可能什么都记得,管理人员这种行为可将其类比成 proxyBeanMethod 值为 false 和proxyBeanMethod 值为 true 的两种情况。
以下是主人来领取宠物的代码。
public class MyConfig {
@Bean("zhangsan")
public User user01() {
User zhangsan = new User("zhangsan", 10);
zhangsan.setPet(pet01());
return zhangsan;
}
@Bean
public Pet pet01() {
return new Pet("tomcat");
}
}
如果 proxyBeanMethod 值为 false 时,也就是狗狗信息丢了,只记得狗狗的名字。我们来验证验证工作人员是否找对了。
// 组件依赖示例
User zhangsan = run.getBean("zhangsan", User.class);
// 判断用户的宠物是容器中的宠物吗
Pet pet1 = (Pet) run.getBean("pet01");
System.out.println("用户zhangsan的宠物是它的吗?" + (zhangsan.getPet() == pet1));
那么执行的结果如下,值为 false。很遗憾,工作人员的失职行为已经是非常严重的了。
用户zhangsan的宠物是它的吗?false
如果 proxyBeanMethod 值为 true 时,也就是狗狗信息还在。那么执行的结果如下。很好,工作人员做的不错。
用户zhangsan的宠物是它的吗?true
例子总结
proxyBeanMethod 为 true 或者为 false 时,对应的模式如下。
- Full(proxyBeanMethods = true) – 如果组件之间有依赖,有依赖的就设置为 true
- Lite(proxyBeanMethods = false) – 如果只是单纯的创建组件到容器中,且组件之间并没有依赖。那么就设置成为 false
Import 注解
直接通过该注解,将对象实例化之后保存到容器中。能标记的地方非常之多,比如 SpringBoot 的主程序类、配置类、被 Service、Repository、Controller 标记的地方都可以将其标记。
存在的意义就是,有些对象是封装在一个外部的 jar 包中的。封装在 jar 包中的源代码是无法直接更改的,也就是说没办法直接标记这些 Component 等等上面提及到的那些注解。所以可以通过这个注解来将对象保存到容器中。
@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = true)
public class MyConfig {
// coding......
}
测试代码:
// 获取 User 类型的组件
String[] strings = run.getBeanNamesForType(User.class);
for (String string : strings) {
System.out.println(string);
}
System.out.println(run.getBean(DBHelper.class));
运行结果如下,其中 DBHelper 本身就是来自于外部的 jar 包。
top.bestguo.bean.User
zhangsan
user02
ch.qos.logback.classic.db.DBHelper@6e4ea0bd
Conditional 注解
该注解也称之为条件注解,该注解的意图就是在特定的场合下,将某一些组件给保存到容器中。其中,它的衍生注解有很多种。如下图所示。
ConditionalOnBean 注解
在学习的时候学习到了 ConditionalOnBean 这个注解,它的意思是当容器中存在某个组件时,那么就将这个对象给实例化保存到容器中,否则就不进行任何的操作。
它被标记到配置类中方法上
@Configuration(proxyBeanMethods = true)
public class MyConfig {
@ConditionalOnBean(name = "pet01") // 当容器中有 pet01 的时候,那么就注册 zhangsan 组件。
@Bean("zhangsan")
public User user01() {
User zhangsan = new User("zhangsan", 10);
zhangsan.setPet(pet01());
return zhangsan;
}
@Bean
public User user02() {
User lisi = new User("lisi", 10);
return lisi;
}
@Bean
public Pet pet01() {
return new Pet("tomcat");
}
@Bean
public Pet pet02() {
return new Pet("tomcat222");
}
}
运行测试代码
boolean pet01 = run.containsBean("pet01");
System.out.println("容器中有 pet01 组件吗?" + pet01);
boolean pet02 = run.containsBean("pet02");
System.out.println("容器中有 pet02 组件吗?" + pet02);
boolean zhangsan = run.containsBean("zhangsan");
System.out.println("容器中有 zhangsan 组件吗?" + zhangsan);
测试结果如下:
容器中有 pet01 组件吗?true
容器中有 pet02 组件吗?true
容器中有 zhangsan 组件吗?false
测试结果很奇怪,并没有按照我想象中的那样子,也就是 pet01 组件创建它就会创建。后面经过 bilibili 的弹幕中才知道,组件在进行注册的过程中似乎和顺序相关。所以改改顺序看看,将 user01 放到最后。
@Configuration(proxyBeanMethods = true)
public class MyConfig {
@Bean
public User user02() {
User lisi = new User("lisi", 10);
return lisi;
}
@Bean
public Pet pet01() {
return new Pet("tomcat");
}
@Bean
public Pet pet02() {
return new Pet("tomcat222");
}
@ConditionalOnBean(name = "pet01") // 当容器中有 pet01 的时候,那么就注册 zhangsan 组件。
@Bean("zhangsan")
public User user01() {
User zhangsan = new User("zhangsan", 10);
zhangsan.setPet(pet01());
return zhangsan;
}
}
运行结果如下,发现这才是我预想的结果
容器中有 pet01 组件吗?true
容器中有 pet02 组件吗?true
容器中有 zhangsan 组件吗?true
目前只学习到这个注解,后面在学习到其它的条件注解再来这里记录记录。
ImportResource 注解
这个注解的用途是加载一个 Spring 的 xml 配置文件,将 xml 文件中配置好的对象放入到容器中。
主程序类
@SpringBootApplication(scanBasePackages = "top.bestguo")
@ImportResource("classpath:bean.xml")
public class SpringBootApplication {
public static void main(String[] args) {
// Spring IOC容器
ConfigurableApplicationContext run = SpringApplication.run(SpringBootApplication.class, args);
boolean haha = run.containsBean("haha");
System.out.println("容器中有 haha 组件吗?" + haha);
boolean hehe = run.containsBean("hehe");
System.out.println("容器中有 hehe 组件吗?" + hehe);
}
}
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 class="top.bestguo.bean.Pet" id="haha">
<property name="name" value="haha" />
</bean>
<bean class="top.bestguo.bean.Pet" id="hehe">
<property name="name" value="hehe" />
</bean>
</beans>
运行结果如下,发现已经导入成功。
容器中有 haha 组件吗?true
容器中有 hehe 组件吗?true
ConfigurationProperties 注解
在某个类上标记,且设置完 prefix 前缀之后,可以在 application.properties 配置文件的属性值注入到实例化组件中。
汽车类
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "mycar")
public class Car {
private String brand;
private Integer price;
// coding......
}
application.properties 配置文件
mycar.brand=BMW
mycar.price=8000
当然,配置完成之后需要在主程序类上标记一个 EnableConfigurationProperties 注解,然后传入 ”类名.class“ 即可。
@ImportResource("classpath:bean.xml")
@EnableConfigurationProperties(Car.class) // 传入”类名.class“
@SpringBootApplication(scanBasePackages = "top.bestguo")
public class SpringBootApplication {
// coding......
}
通过 Web 模块访问,看看能否正常得到。
@RequestMapping("/car")
@ResponseBody
public Car car() {
return car;
}
运行结果如下,配置文件中的值已经成功注入进来了。
请勿发布违反中国大陆地区法律的言论,请勿人身攻击、谩骂、侮辱和煽动式的语言。