1、SpringBoot组件添加之@configuration注解
1.1、@configuration注解基本使用
项目结构
1、添加两个组件,一个是 User(用户)组件、一个是 Pet(宠物)组件
User类:
package com.lemon.boot.bean;
/**
* @Author Lemons
* @create 2022-03-07-18:55
*/
public class User {
private String name;
private Integer age;
public User(){
}
public User(String name,Integer age){
this.name = name;
this.age = age;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public Integer getAge(){
return age;
}
public void setAge(Integer age){
this.age = age;
}
}
复制代码
Pet类
package com.lemon.boot.bean;
/**
* @Author Lemons
* @create 2022-03-07-18:54
*/
public class Pet {
private String name;
public Pet(){
}
public Pet(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
复制代码
2、现在将这两个组件加到容器中,按照原来的原生Spring方式可以这样做 :
① resource下创建一个 Spring 的配置文件 beans.xml;
② 以前 Spring xml 配置的方式:使用 bean 标签,给容器中添加上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.lemon.boot.bean.User">
<property name = "name" value="小明"></property>
<property name = "age" value="19"></property>
</bean>
<bean id = "pet01" class="com.lemon.boot.bean.Pet">
<property name = "name" value="假老练"></property>
</bean>
</beans>
复制代码
③ 但是SpringBoot 已经不写 xml 配置文件了,SpringBoot 可以在底层用 @configuration 注解:创建一个类,在类上方使用这个注解,这个注解就是告诉 SpringBoot 这是一个配置类,等同于以前的配置文件。以前用 bean 标签,给容器中添加组件,现在用方法构造出来,并在上方使用@Bean
注解,以方法名
作为组件的id
,返回类型就是组件类型,方法返回的值(对象)就是组件在容器中的实例。
这个类定义为Myconfig
,放在和主程序所在包下的config
包
@Configuration //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
//给容器中添加组件,以方法名作为组件的id,
//返回类型就是组件类型,方法返回的值(对象)就是组件在容器中的实例
@Bean
public User user01(){
return new User("小明",18);
}
//如果不想让方法名作为组件名id,也可以在Bean标签直接给一个自定义的名字
@Bean("jialaolian")
public Pet pet01(){
return new Pet("假老练");
}
}
复制代码
④ 验证容器中有这两个组件:
- MainApplication 启动类:
@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);
}
}
}
复制代码
运行主程序,结果为:可以看出,容器中是存在这两个组件的
⑤ 配置类里面使用 @Bean
标注在方法上给容器注册组件,默认是单实例的(Bean 默认是单例模式),这一点是和原生Spring一样的,如下进行验证:
@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);
}
//从容器中多次获取组件
Pet Caty01 = run.getBean("jialaolian",Pet.class);
Pet Caty02 = run.getBean("jialaolian",Pet.class);
System.out.println("组件:"+(Caty01 == Caty02)); //判读组件是不是单实例的
}
}
复制代码
结果
组件:true
复制代码
3、@Configuration
标注的这个配置类 MyConfig
它本身也是一个组件,配置类也是容器中的一个组件,验证:
//得到容器中的MyConfig类对象
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);
复制代码
结果:配置类是个CGLIB生成的代理对象
com.lemon.boot.Config.MyConfig$$EnhancerBySpringCGLIB$$a3d18d30@61efae4d
复制代码
1.2、@Configuration注解源码
1、注解源码:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
//代理 Bean 的方法
boolean proxyBeanMethods() default true;
}
复制代码
可以发现在 SpringBoot2.0 以后的版本里边,基于 Spring5.2 以后,Configuration 类多了一个属性proxyBeanMethods
,默认是 true;这是与 SpringBoot1.0 的不同。
2、现在我们在我们的自定义配置类MyConfig.class
使用这个属性
@Configuration(proxyBeanMethods = true) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
@Bean //给容器中添加组件,以方法名作为组件的id,返回类型就是组件类型,方法返回的值(对象)就是组件在容器中的实例
public User user01(){
return new User("小明",18);
}
@Bean("jialaolian") //如果不想让方法名作为组件名id也可以在Bean标签直接给一个自定义的名字
public Pet pet01(){
return new Pet("假老练");
}
}
复制代码
在 MyConfig 类多次调用 user01
方法,那么方法返回的对象是从容器中拿还是就是普通的调用方法?验证如下:
@SpringBootApplication
public class MainApplication {
public static void main(String[] args){
//返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//得到容器中的MyConfig类对象
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);
//在MyConfig类多次调用user01方法
User user = bean.user01();
User user1 = bean.user01();
System.out.println(user == user1);
}
}
复制代码
结果如下: “
true
复制代码
这说明,外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册到容器中的单实例对象。user、user1两个对象相等的原因在于 Configuration
的这个属性 @Configuration(proxyBeanMethods = true)
,容器中获取组件的这个对象 MyConfig
不是一个普通对象,而是 MyConfig$$EnhancerBySpringCGLIB
,实际上是被 SpringCGLIB
增强了的代理对象,也就是说我们获取到的是代理对象,代理对象调用 user01 ,SpringBoot 里的默认逻辑就是如果 @Configuration(proxyBeanMethods = true)
,我们这个类(MyConfig),我们获取到的就是代理对象,代理对象调用方法,SpringBoot 默认就会检查容器中有没有这个方法已经返回的组件,如果有就从缓存里取,没有在新创建,保持组件单实例。
3、若将 proxyBeanMethods 属性改为 false,运行结果则是false
,则证明获取到的 MyConfig 不再是代理对象,多次调用方法得到的不是同一个实例,也就是说调用方法每次都会新创建一个对象。
注意:SpringBoot5.2以后的版本修改为false
会标红,但是不影响程序运行
1.3、Full 模式与 Lite 模式
SpringBoot 在底层 Configuration 的两个配置:
- Full 全配置模式(@Configuration(proxyBeanMethods = true)保证每个 @Bean 方法被调用多少次返回的组件都是单实例的;
- Lite 轻量级配置模式(@Configuration(proxyBeanMethods = false)每个 @Bean 方法被调用多少次返回的组件都是新创建的。
1.3.1、组件依赖
上述两个模式就轻松的解决了组件依赖的这个问题,举例子用户养宠物:
-
修改 User.class
```java 复制代码
public class User {
private String name;
private Integer age;
private Pet pet; //增加宠物属性
public Pet getPet(){ //提供get/set
return pet;
}
public void setPet(Pet pet){
this.pet = pet;
}
public User(){
}
public User(String name,Integer age){
this.name = name;
this.age = age;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public Integer getAge(){
return age;
}
public void setAge(Integer age){
this.age = age;
}
复制代码
}
“`
- MyConfig.class:设为Full 全配置模式
@Configuration(proxyBeanMethods = true)
public class MyConfig {
@Bean
public User user01(){
User zhangsan = new User("zhangsan",18);
//User组件依赖了Pet组件
zhangsan.setPet(Cat());
return zhangsan;
}
@Bean("jialaolian") //@bean添加组件到容器
public Pet Cat(){
return new Pet("假老练");
}
}
复制代码
- MainApplication.class
@SpringBootApplication
public class MainApplication {
public static void main(String[] args){
//返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
User user01 = run.getBean("user01",User.class);
Pet jialaolian = run.getBean("jialaolian",Pet.class);
System.out.println("容器中的宠物和调用方法得到的宠物是否一样:"+(user01.getPet() == jialaolian));
}
}
复制代码
运行结果说明,用户的宠物就是容器中的宠物
容器中的宠物和调用方法得到的宠物是否一样::true
复制代码
若将 proxyBeanMethods = true
改为 false
,运行结果说明用户的宠物不是容器中的宠物
容器中的宠物和调用方法得到的宠物是否一样::false
复制代码
1.3.2、两个模式使用场景
-
配置类组件之间无依赖关系用 Lite **模式,加速容器启动过程,减少判断。**创建新组建用lite,因为容器里面没有,不需要检查
-
配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,可以快速获取到bean,减少新生成实例的消耗,减少 jvm 垃圾回收,用 Full 模式。
-
这里有无依赖多指是否在一个类中使用到另一个类,例如在user类里面,Cat类是user里的一个属性,这样user和cat类之间就产生了依赖关系
1.4、总结及注意事项
Full模式与Lite模式注意点如下:Full模式和Lite模式是针对spring配置而言的,和xml配置无关。
何时为Lite模式:
- 类上有@Component注解
- 类上有@ComponentScan注解
- 类上有@Import注解
- 类上有@ImportResource注解
- 类上没有任何注解,但是类中存在@Bean方法
- 类上有@Configuration(proxyBeanMethods = false)注解
Lite总结:运行时不用生成CGLIB子类,提高运行性能,降低启动时间,可以作为普通类使用。但是不能声明@Bean之间的依赖
何时为Full模式:
- 标注有
@Configuration
或者@Configuration(proxyBeanMethods = true)
的类被称为Full模式的配置类。
Full模式总结:单例模式能有效避免Lite模式下的错误,性能没有Lite模式好
2、SpringBoot组件添加之@Import注解
1、组件注册其他注解补充
上一小节学完了SpringBoot底层的@Configuration
注解,并结合了@Bean
注解来进行了组件注册添加演示,这个注解让我们了解了SpringBoot中的组件的添加,除了这些注解以外,@Bean、@Component、@Controller、@Service、@Repository这些以前原生Spring中的注解也都可以用来注册组件到容器中,只要包扫描正确
比如@Bean
:
//给容器中添加组件,默认以方法名作为组件的id,返回类型就是组件类型,创建对象,就是实例
@Bean
public User user01() {
return new User("zhangsan", 19);
}
复制代码
我们简要的说明一下这几个注解的作用:
- @Bean是用来往容器里面添加组件的,默认以方法名作为组件的id,返回类型就是组件类型,创建对象,就是实例,类似于spring中的
- @Component表示是一个组件
- @Controller表示是一个控制器
- @Service表示是一个业务逻辑组件
- @Repository表示是一个数据库层面组件
- 这些注解的使用和以前都是一样的
2、@Import注解导入组件
@Import
可以使用在任何一个组件或配置类上面,可以自动创建出它声明的组件,组件的名字默认是类的相对路径(全类名):底层是一个数组,所以该注解可以写多种类型
在MyConfig
类中使用@Import
@Import({User.class})
@Configuration //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
//给容器中添加组件,以方法名作为组件的id,
//返回类型就是组件类型,方法返回的值(对象)就是组件在容器中的实例
@Bean
public User user01(){
return new User("小明",18);
}
//如果不想让方法名作为组件名id,也可以在Bean标签直接给一个自定义的名字
@Bean("jialaolian")
public Pet pet01(){
return new Pet("假老练");
}
}
复制代码
在主启动类获取使用@Import
自动生成的User类型的组件:
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//1.返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//按照类型获取组件
String[] names = run.getBeanNamesForType(User.class);
for (String name : names) {
System.out.println(name);
}
}
}
复制代码
结果
com.lemon.boot.bean.User
user01
复制代码
解释:为什么会有两个User类型的组件对象?
- 第一个组件:名字叫
com.lemon.boot.bean.User
,这个组件是我们@import
注解添加到容器里去的,组件的名字默认是类的相对路径(全类名) - 第二个组件:是我们使用
@Bean
注解添加到容器里去的,默认以方法名作为组件的id,所以这里就有了两种创建组件的方式
3、@Conditional条件装配注解
条件装配注解:满足Conditional
指定的条件,则进行组件注入
- 放在配置类上表示,当容器中满足条件时,配置类中的组件才生效;
- 放在配置方法上的时候,表示的意思是当满足条件的时候配置方法才生效;
@Conditional
注解是一个根注解,下面派生了许多新的注解,如图: 不同的注解有不同的功能,我们来说明一下@ConditionalOnBean
这个注解
演示:
首先将MyConfig类中的程序的@Bean("jialaolian")
删掉,使之cat()
方法成为一个普通方法,这样我们就不能创建jialaolian
这个组件了到容器里了。然后进行主程序的测试:结果当然是没有
//查看容器中是否包含pet这个组件
boolean pet = run.containsBean("jialaolian"); //false
System.out.println("是否有pet组件:"+ pet);
复制代码
当然我们的user01组件是有的,但是现在假设我们设置,只有存在jialaolian
这个组件的时候才能创建user01
组件注册到容器中:如下使用在方法上
@Configuration
public class MyConfig {
//当有名字为jialaolian的组件时该方法才会生效,才会注入user01组件
@ConditionalOnBean(name="jialaolian" )
@Bean
public User user01(){
return new User("小明",18);
}
public Pet pet01(){
return new Pet("假老练");
}
}
复制代码
测试:获取user01对象,程序没有任何输出,获取不到对象
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//1.返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//按照类型获取组件
String[] names = run.getBeanNamesForType(User.class);
for (String name : names) {
System.out.println(name);
}
}
}
复制代码
如果将@ConditionalOnBean
标注在类上,说明必须满足该条件,类中所有代码才会全部生效
@Import({User.class})
@Configuration
@ConditionalOnBean(name = "pet")
public class MyConfig {
//当有名字为pet的组件时以下代码才会生效
@Bean
public User user01() {
return new User("zhangsan", 19);
}
@Bean("pet22")
public Pet cat() {
return new Pet("tomcat");
}
}
复制代码
测试:获取不到结果
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//1.返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
boolean user = run.containsBean("user01" ) ;//false
System.out.print1n("容器中user01组件:"+ user);
boolean tom22 = run.containsBean("pet22" ) ;//false
System.out.print1n("容器中pet22组件:"+ tom22);
}
}
复制代码
4、springBoot原生配置文件导入:@ImportResource注解
步骤:
- 将原生的
Spring.xml
的配置文件,通过@ImportResource
导入SpringBoot
进行解析,完成对应的组件注册 - 标注位置:在某个配置类的上方
- 参数:输入你要导入的配置文件的类路径即可
演示:原生的Spring.xml
的配置文件beans.xml
如下:在类路径classpath:resource
下
<?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 = "user22" class="com.lemon.boot.bean.User">
<property name = "name" value="小明"></property>
<property name = "age" value="19"></property>
</bean>
<bean id = "pet33" class="com.lemon.boot.bean.Pet">
<property name = "name" value="假老练"></property>
</bean>
</beans>
复制代码
这种用原生xml
的方式肯定是在容器中没有我们创建的这两个组件[主程序启动类中验证],但是比如遇到一些老项目,就是用这些原生配置文件来写的,你要按照SpringBoot格式转变为注解形式的的,一个一个迁移太麻烦,于是可以直接使用下面这种引入资源的方式:直接在配置类上面用@ImportResource
注解引入资源
测试:
配置类
@ImportResource("classpath:beans.xml") //导入原生xml文件
@Import({User.class})
@Configuration
@ConditionalOnMissingBean(name = "pet")
public class MyConfig {
//当有名字为pet的组件不存在时以下代码才会生效
@Bean
public User user01() {
return new User("zhangsan", 19);
}
@Bean("pet22")
public Pet cat() {
return new Pet("tomcat");
}
}
复制代码
主启动类进行测试:
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//1.返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
boolean user = run.containsBean("user01" ) ;//true
System.out.println("容器中user01组件:"+ user);
boolean tom22 = run.containsBean("pet22" ) ;//true
System.out.println("容器中pet22组件:"+ tom22);
boolean user22 =run.containsBean("user22");
boolean pet33 = run.containsBean("pet33");
System.out.println( "user22: "+user22);//true
System.out.println("pet33:" + pet33);//true
}
}
复制代码
5、配置绑定–@ConfigurationProperties
如何使用Java读取到properties
文件中的内容,并且把它封装到JavaBean中,以供随时使用;比如连接mysql
以前需要这样的操作才可以:
在SpringBoot中,将变得非常简单,如下:
5.1、方式1:@Component + @ConfigurationProperties
对应要绑定的类:自定义一个Car类:
注意:
- 该创建的类car一定要加入到容器中才能生效
- 前缀必须小写
//只有在容器中的组件,才会拥有SpringBoot提供的强大功能
@Component //Component表示是一个 组件
@ConfigurationProperties(prefix = "mycar")//指定要与配置文件中以某前缀开始的值进行属性匹配
public class Car {
private String brand;
private Integer price;
//提供get/set/toString方法
}
复制代码
对应的配置文件:resource下的appliction.properties
文件,内容如下
mycar.brand=byd
mycar.price=100000
复制代码
启动类测试查看是否绑定成功:
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//1.返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
Car car = run.getBean(Car.class);
System.out.println(car);//true
}
}
复制代码
结果:容器中是有这个对象的
Car{brand=byd, price=100000}
复制代码
或者这样进行测试:在上面的基础上,通过浏览器进入
@RestController
public class HelloController {
@Autowired //注入容器中的对象
Car car;
@RequestMapping("/car")
public Car hand02() {
return car;
}
@RequestMapping("/hello2")
public String hand01() {
return "Hello 世界这么大!";
}
}
复制代码
结果:
从两次打印结果看出:
- 我们的容器里面是有创建的组件的
- 并且也封装成为了javabean
5.2、方式2:@EnableConfigurationProperties + @ConfigurationProperties
对应要绑定的类: Car类:
@ConfigurationProperties(prefix = "mycar")//指定要与配置文件中以某前缀开始的值进行属性匹配
public class Car {
private String brand;
private Integer price;
//提供get/set/toString方法
}
复制代码
对应的配置文件:
mycar.brand=byd
mycar.price=100000
复制代码
启动类使用注解@EnableConfigurationProperties
测试查看是否绑定成功: :
//1.开启Car配置绑定功能
//2.把这个car组件自动注册到容器中
@EnableConfigurationProperties(Car.class)
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//1.返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
Car car = run.getBean(Car.class);
System.out.println(car);//true
}
}
复制代码
当然,我们也可以通过上述浏览器方式进行测试,这个注解也可以标注在配置类上进行[通常我们也标注在配置类上] ,结果:
小总结:一般使用方式2 :该方式可以保证中文没有乱码