@Configuration 注解主要用于给容器添加组件(Bean),下面实践其用法:
项目基本结构:
两个Bean组件:
User.java
package com.menergy.boot.bean;
/**
* 用户
*/
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;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Pet.java
package com.menergy.boot.bean;
/**
* 宠物
*/
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;
}
@Override
public String toString() {
return "Pet{" +
"name='" + name + '\'' +
'}';
}
}
以前Spring 配置文件方式是这样给容器添加组件的:
现在Spring Boot 已经不写上面的xml配置了,在Spring Boot 底层可以用@Configuration 注解给容器中添加组件。如下:
注解类MyConfig.java
package com.menergy.boot.config;
import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 1. 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2. 配置类本身也是组件
* 3. proxyBeanMethods: 代理Bean 方法:
* Full(proxyBeanMethods = true): 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
* Lite(proxyBeanMethods = false): 在容器中不会保留代理对象,外部多次调用这些组件时,每次调用都会产生一个新的对象
* 用于解决组件依赖场景
*/
@Configuration(proxyBeanMethods = true) //告诉SpringBoot 这是一个配置类 == 以前的配置文件
public class MyConfig {
/**
* 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
* @return
*/
@Bean //给容器中添加组件,以方法名作为主键id,返回类型就是组件类型,返回值就是组件在容器中的实例
public User user01(){
return new User("dragon",18);
}
@Bean("tomcatPet")
public Pet pet01(){
return new Pet("dragonPet");
}
}
主类MainApplication.java 中测试调用:
package com.menergy.boot;
import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import com.menergy.boot.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import sun.awt.geom.AreaOp;
/**
* 主程序类
* 这个注解相当于告诉Spring Boot: 这是一个Spring boot 应用
*/
//@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.menergy.boot")
public class MainApplication {
public static void main(String[] args) {
// SpringApplication.run(MainApplication.class, 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 pet1 = run.getBean("tomcatPet", Pet.class);
Pet pet2 = run.getBean("tomcatPet", Pet.class);
System.out.println("组件: " + (pet1 == pet2));
// 4. com.menergy.boot.config.MyConfig$$EnhancerBySpringCGLIB$$3779496a@67a056f1
MyConfig myConfig = run.getBean(MyConfig.class);
System.out.println(myConfig);
//如果@Configuration(proxyBeanMethods = true)代理对象调用方法, Spring Boot 总会检查这个组件是否在容器中有,如果有则不会新建,保持组件单实例。
User user01 = myConfig.user01();
User user02 = myConfig.user01();
System.out.println(user01 == user02);
}
}
输出的部分结果:
上面的例子,重点落在@Configuration(proxyBeanMethods = true) 注解。 该注解告诉SpringBoot ,被注解的类是一个配置类, 相当于以前的配置文件xml中的“bean配置”。该注解有如下特性:
1. 该注解的配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的。
2. 被这个注解的配置类本身也是组件。
3. 该注解的属性proxyBeanMethods 可以通过“true” 和 “false” 配置值,来控制使用的模式:
(1)Full模式(proxyBeanMethods = true): 为true时,外部无论对配置类中的组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象。
(2)Lite模式(proxyBeanMethods = false): 为false时,在容器中不会保留代理对象,外部多次调用这些组件时,每次调用都会产生一个新的对象。
这两种模式的存在主要用于解决组件依赖场景。
1和2 两点特性上面的例子中都有体现, 接下来重点实践第三点特性:
实践proxyBeanMethods:
基于上面的例子,首先修改User.java类,加上宠物Pet的依赖:
package com.menergy.boot.bean;
/**
* 用户
*/
public class User {
private String name;
private Integer age;
private Pet pet;
public User() {
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public User(String name, Integer age, Pet pet) {
this.name = name;
this.age = age;
this.pet = pet;
}
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;
}
public Pet getPet() {
return pet;
}
public void setPet(Pet pet) {
this.pet = pet;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", pet=" + pet +
'}';
}
}
在配置类MyConfig.java 中加入user01对象对用pet对象,同时使用Full模式(proxyBeanMethods = true):
package com.menergy.boot.config;
import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 1. 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2. 配置类本身也是组件
* 3. proxyBeanMethods: 代理Bean 方法:
* Full模式(proxyBeanMethods = true): 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
* Lite模式(proxyBeanMethods = false): 在容器中不会保留代理对象,外部多次调用这些组件时,每次调用都会产生一个新的对象
* 用于解决组件依赖场景
*/
@Configuration(proxyBeanMethods = true) //告诉SpringBoot 这是一个配置类 == 以前的配置文件
public class MyConfig {
/**
* 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
* @return
*/
@Bean //给容器中添加组件,以方法名作为主键id,返回类型就是组件类型,返回值就是组件在容器中的实例
public User user01(){
User dragonUser = new User("dragon",18);
// User 组件依赖了Pet 组件,当proxyBeanMethods 为 true 时,这种依赖关系成立
dragonUser.setPet(pet01());
return dragonUser;
}
@Bean("tomcatPet")
public Pet pet01(){
return new Pet("dragonPet");
}
}
主类MainApplication.java:
package com.menergy.boot;
import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import com.menergy.boot.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import sun.awt.geom.AreaOp;
/**
* 主程序类
* 这个注解相当于告诉Spring Boot: 这是一个Spring boot 应用
*/
//@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.menergy.boot")
public class MainApplication {
public static void main(String[] args) {
// SpringApplication.run(MainApplication.class, 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 pet1 = run.getBean("tomcatPet", Pet.class);
Pet pet2 = run.getBean("tomcatPet", Pet.class);
System.out.println("组件: " + (pet1 == pet2));
// 4. com.menergy.boot.config.MyConfig$$EnhancerBySpringCGLIB$$3779496a@67a056f1
MyConfig myConfig = run.getBean(MyConfig.class);
System.out.println(myConfig);
//如果@Configuration(proxyBeanMethods = true)代理对象调用方法, Spring Boot 总会检查这个组件是否在容器中有,如果有则不会新建,保持组件单实例。
User user01 = myConfig.user01();
User user02 = myConfig.user01();
System.out.println(user01 == user02);
//测试 @Configuration(proxyBeanMethods = true/false)
User user011 = run.getBean("user01", User.class);
Pet tomcatPet = run.getBean("tomcatPet", Pet.class);
System.out.println("用户的宠物:" + (user011.getPet() == tomcatPet));
}
}
运行结果:
可以看出,Full模式(proxyBeanMethods = true)时,输出true,说明是从容器中获取的同一个组件(用户的宠物就是容器中的宠物)。
接下来,改用Lite模式(proxyBeanMethods = false):即基于上面实例,将配置类MyConfig.java 中的注解的属性proxyBeanMethods 改成false值,如下:
MyConfig.java:
package com.menergy.boot.config;
import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 1. 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2. 配置类本身也是组件
* 3. proxyBeanMethods: 代理Bean 方法:
* Full模式(proxyBeanMethods = true): 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
* Lite模式(proxyBeanMethods = false): 在容器中不会保留代理对象,外部多次调用这些组件时,每次调用都会产生一个新的对象
* 用于解决组件依赖场景
*/
@Configuration(proxyBeanMethods = false) //告诉SpringBoot 这是一个配置类 == 以前的配置文件
public class MyConfig {
/**
* 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
* @return
*/
@Bean //给容器中添加组件,以方法名作为主键id,返回类型就是组件类型,返回值就是组件在容器中的实例
public User user01(){
User dragonUser = new User("dragon",18);
// User 组件依赖了Pet 组件,当proxyBeanMethods 为 true 时,这种依赖关系成立
dragonUser.setPet(pet01());
return dragonUser;
}
@Bean("tomcatPet")
public Pet pet01(){
return new Pet("dragonPet");
}
}
运行结果:
可以看出,Lite模式(proxyBeanMethods = false)时,输出false,说明是从容器中获取的不是同一个组件(用户的宠物不是容器中的宠物, 相当于new 了另一个对象)。
总结:配置类包括了全模式(Full)和轻量级模式(Lite)两种。当proxyBeanMethods 是true时,Spring Boot 每次都会检查容器中是否有相应的组件,如果proxyBeanMethods 是false, 则不检查容器中是否有没有相应的组件,而是直接new一个。这也是Spring Boot 新增的一个很重要的特性。
最佳实战:如果只是向容器中增加组件,别的地方也不会调用这个组件,我们可以将其调为false 模式,这样Spring Boot 启动起来非常快,加载起来也非常快。 如果别的地方明显要用,要依赖,我们就把其调成true,保证依赖的组件就是容器中的组件。
注: 前面的例子中,在配置类中用到@Been 注解来指定组件, 其实Spring Boot 底层还用到了其他一些以前常用的注解来指定组件,包括@Component、@Controller、@Service、@Repository。这些类似于@Been 原理,也是用于向容器中注册组件。
除此之外,底层还用到@ComponentScan 注解来说明容器的包扫描,还有@Import 和@Conditional 来向容器添加组件。很多注解是以前常用的,接下来主要说明@Import 和@Conditional 注解。
首先,从@Import 注解类中可以看到该注解的定义,以及知道其属性是一个Class类型的数组,说明这个注解的作用是向容器中导入一批组件:
接下来,实践一下:
首先在配置类上加入@Import 注解,并向容器中导入两个组件,一个是自己定义的类,一个是从第三方Jar 包中任意的一个类:
主类加入如下测试:
运行结果:
结果说明:
“com.menergy.boot.bean.User” 是通过@Import 导入的组件。(默认的组件名称是全类名)
“user01” 是之前用@Bean 方法添加进去的
“org.apache.logging.log4j.util.StringBuilders@4482469c” 也是通过@Import 导入的组件。
@Conditional 是条件装配:当满足@Conditional指定的条件时, 才向容器中注入组件,或干相应的事。
在全局Jar包中搜索@Conditional 类:双击Shift键,选择Classes,输入@Conditional搜索。
注:如果调不出这个窗口,请参考: (98条消息) IDEA 操作与设置笔记_龙泉太阿的博客-CSDN博客https://blog.csdn.net/menergy/article/details/123827363?spm=1001.2014.3001.5501
打开Conditional 类后,选中“Conditional” 类名,“Ctrl + H” 键调出这个类的继承树:
注:如果快捷键失效,请确定如下快捷键设置:
从前面的@Conditional 的继承树可以看出,@Conditional 有非常多的派生注解,每个注解都代表不同的功能,从派生注解的注解名称可以大概知道其功能用意,例如@ConditionalOnBean 注解代表当容器中存在某个Bean时才干某些事情, @ConditionalOnMissingBean 注解代表当容器中不存在某个Bean时才干某些事情。
接下来,以@ConditionalOnBean 为例,进行实践:
首先,我们将前面的MyConfig.java 中宠物的“@Bean” 注解注释掉,那么这个方法相当于常规方法,没有向容器中注册该组件,如下:
完整代码:
package com.menergy.boot.config;
import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import org.apache.logging.log4j.util.StringBuilders;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* 1. 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2. 配置类本身也是组件
* 3. proxyBeanMethods: 代理Bean 方法:
* Full模式(proxyBeanMethods = true): 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
* Lite模式(proxyBeanMethods = false): 在容器中不会保留代理对象,外部多次调用这些组件时,每次调用都会产生一个新的对象
* 用于解决组件依赖场景
*
* 4. @Import({User.class, StringBuilders.class})
* 自动调用类的无参构造器创建出这两个类型的组件
*
*/
@Import({User.class, StringBuilders.class}) // 例子中的User 是自定义的类,StringBuilders 是第三方Jar包中的类
@Configuration(proxyBeanMethods = false) //告诉SpringBoot 这是一个配置类 == 以前的配置文件
public class MyConfig {
/**
* 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
* @return
*/
@Bean //给容器中添加组件,以方法名作为主键id,返回类型就是组件类型,返回值就是组件在容器中的实例
public User user01(){
User dragonUser = new User("dragon",18);
// User 组件依赖了Pet 组件,当proxyBeanMethods 为 true 时,这种依赖关系成立
dragonUser.setPet(pet01());
return dragonUser;
}
// @Bean("tomcatPet")
public Pet pet01(){
return new Pet("dragonPet");
}
}
为了方便测试,将主方法中前面的测试代码注释掉 ,加入如下测试代码:
完整代码:
package com.menergy.boot;
import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import com.menergy.boot.config.MyConfig;
import org.apache.logging.log4j.util.StringBuilders;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
/**
* 主程序类
* 这个注解相当于告诉Spring Boot: 这是一个Spring boot 应用
*/
//@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.menergy.boot")
public class MainApplication {
public static void main(String[] args) {
// SpringApplication.run(MainApplication.class, 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 pet1 = run.getBean("tomcatPet", Pet.class);
//
// Pet pet2 = run.getBean("tomcatPet", Pet.class);
//
// System.out.println("组件: " + (pet1 == pet2));
//
//
// // 4. com.menergy.boot.config.MyConfig$$EnhancerBySpringCGLIB$$3779496a@67a056f1
// MyConfig myConfig = run.getBean(MyConfig.class);
// System.out.println(myConfig);
//
// //如果@Configuration(proxyBeanMethods = true)代理对象调用方法, Spring Boot 总会检查这个组件是否在容器中有,如果有则不会新建,保持组件单实例。
// User user01 = myConfig.user01();
// User user02 = myConfig.user01();
// System.out.println(user01 == user02);
//
// //测试 @Configuration(proxyBeanMethods = true/false)
// User user011 = run.getBean("user01", User.class);
// Pet tomcatPet = run.getBean("tomcatPet", Pet.class);
// System.out.println("用户的宠物:" + (user011.getPet() == tomcatPet));
//
// // 5. 从容器中获取组件。测试@Import({User.class, LogBuilder.class})
// String[] beanNamesForType = run.getBeanNamesForType(User.class);
// System.out.println("-----------------------------------------");
// for (String s : beanNamesForType) {
// System.out.println(s);
// }
//
// StringBuilders bean1 = run.getBean(StringBuilders.class);
// System.out.println(bean1);
boolean user01 = run.containsBean("user01"); // 容器中是否包括这个组件
System.out.println("容器中user01组件是否存在:" + user01);
boolean tomcatPet = run.containsBean("tomcatPet"); // 容器中是否包括这个组件
System.out.println("容器中tomcatPet 组件是否存在:" + tomcatPet);
}
}
运行结果:
结果表示在容器中存在“user01”组件,不存在“tomcatPet”组件。
基于这个场景,接着我们来实践@ConditionalOnBean注解:
点击进入ConditionalOnBean 类,可以看到很多对应的字段属性,这里试着选择name 属性来注解:
完整代码:
package com.menergy.boot.config;
import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import org.apache.logging.log4j.util.StringBuilders;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* 1. 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2. 配置类本身也是组件
* 3. proxyBeanMethods: 代理Bean 方法:
* Full模式(proxyBeanMethods = true): 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
* Lite模式(proxyBeanMethods = false): 在容器中不会保留代理对象,外部多次调用这些组件时,每次调用都会产生一个新的对象
* 用于解决组件依赖场景
*
* 4. @Import({User.class, StringBuilders.class})
* 自动调用类的无参构造器创建出这两个类型的组件
*
*/
@Import({User.class, StringBuilders.class}) // 例子中的User 是自定义的类,StringBuilders 是第三方Jar包中的类
@Configuration(proxyBeanMethods = false) //告诉SpringBoot 这是一个配置类 == 以前的配置文件
public class MyConfig {
/**
* 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
* @return
*/
@ConditionalOnBean(name = "tomcatPet") //表示容器中有tomcatPet组件时,才在容器中注入user01组件
@Bean //给容器中添加组件,以方法名作为主键id,返回类型就是组件类型,返回值就是组件在容器中的实例
public User user01(){
User dragonUser = new User("dragon",18);
// User 组件依赖了Pet 组件,当proxyBeanMethods 为 true 时,这种依赖关系成立
dragonUser.setPet(pet01());
return dragonUser;
}
// @Bean("tomcatPet")
public Pet pet01(){
return new Pet("dragonPet");
}
}
注解@ConditionalOnBean(name = "tomcatPet") 表示容器中有tomcatPet组件时,才在容器中注入user01组件。这里加上这个条件装配注解后,看一下运行结果:
可以看出跟前面相比,这里时false表示容器中没有user01组件(因为加了条件注解)。
也可以把条件注解标注在类上,表示条件成立时,整个类的方法才生效,否则都不生效:
再看运行结果:
其他条件注解也是类似的原理,比如下面我实践@ConditionalOnMissingBean(name = "tomcatPet") 表示容器中不存在tomcatPet组件时,该类中标注的全部组件才生效。这里可以看到“tomcatPet” 的@Bean注解已经被去掉了,所以容器中不存在“tomcatPet” 组件,符合条件注解,所以容器中会注册“user01”组件,如下:
运行结果:
@ImportResource 表示将指定路径下的配置文件导入为组件,使用场景一般在一下旧系统还在使用xml配置Bean的方法注册主键时,为了不想手动一个个改成注解方式,可以采用这个@ImportResource注解,或者引用了一些旧版本的包,使用了xml方式,对第三方包又不方便修改,这时采用@ImportResource就很方便将其转成注解方式。
下面实践这个注解:
先看以前配置方式下,这里用beans.xml配置了两个组件,但是系统没有引用它,所以这两个组件应该不生效,如下:
beans.xml:
组件名称分别为“user02” 和“pet02”
主类MainApplication.java:
完整代码:
package com.menergy.boot;
import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import com.menergy.boot.config.MyConfig;
import org.apache.logging.log4j.util.StringBuilders;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
/**
* 主程序类
* 这个注解相当于告诉Spring Boot: 这是一个Spring boot 应用
*/
//@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.menergy.boot")
public class MainApplication {
public static void main(String[] args) {
// SpringApplication.run(MainApplication.class, 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 pet1 = run.getBean("tomcatPet", Pet.class);
//
// Pet pet2 = run.getBean("tomcatPet", Pet.class);
//
// System.out.println("组件: " + (pet1 == pet2));
//
//
// // 4. com.menergy.boot.config.MyConfig$$EnhancerBySpringCGLIB$$3779496a@67a056f1
// MyConfig myConfig = run.getBean(MyConfig.class);
// System.out.println(myConfig);
//
// //如果@Configuration(proxyBeanMethods = true)代理对象调用方法, Spring Boot 总会检查这个组件是否在容器中有,如果有则不会新建,保持组件单实例。
// User user01 = myConfig.user01();
// User user02 = myConfig.user01();
// System.out.println(user01 == user02);
//
// //测试 @Configuration(proxyBeanMethods = true/false)
// User user011 = run.getBean("user01", User.class);
// Pet tomcatPet = run.getBean("tomcatPet", Pet.class);
// System.out.println("用户的宠物:" + (user011.getPet() == tomcatPet));
//
// // 5. 从容器中获取组件。测试@Import({User.class, LogBuilder.class})
// String[] beanNamesForType = run.getBeanNamesForType(User.class);
// System.out.println("-----------------------------------------");
// for (String s : beanNamesForType) {
// System.out.println(s);
// }
//
// StringBuilders bean1 = run.getBean(StringBuilders.class);
// System.out.println(bean1);
boolean user01 = run.containsBean("user01"); // 容器中是否包括这个组件
System.out.println("容器中user01组件是否存在:" + user01);
boolean tomcatPet = run.containsBean("tomcatPet"); // 容器中是否包括这个组件
System.out.println("容器中tomcatPet 组件是否存在:" + tomcatPet);
boolean user02 = run.containsBean("user02"); // 容器中是否包括这个组件
System.out.println("容器中user02组件是否存在:" + user02);
boolean pet02 = run.containsBean("pet02"); // 容器中是否包括这个组件
System.out.println("容器中pet02组件是否存在:" + pet02);
}
}
运行结果:
可以看到,组件“user02” 和“pet02”都不存在。
基于这个场景,接着我们来加入@ImportResource 注解,这个注解可以加入到任意一个配置类上都可以生效,这里就选择加入到MyConfig.java 配置类上:、
完整代码:
package com.menergy.boot.config;
import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import org.apache.logging.log4j.util.StringBuilders;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
/**
* 1. 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2. 配置类本身也是组件
* 3. proxyBeanMethods: 代理Bean 方法:
* Full模式(proxyBeanMethods = true): 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
* Lite模式(proxyBeanMethods = false): 在容器中不会保留代理对象,外部多次调用这些组件时,每次调用都会产生一个新的对象
* 用于解决组件依赖场景
*
* 4. @Import({User.class, StringBuilders.class})
* 自动调用类的无参构造器创建出这两个类型的组件
*
*/
@Import({User.class, StringBuilders.class}) // 例子中的User 是自定义的类,StringBuilders 是第三方Jar包中的类
@Configuration(proxyBeanMethods = false) //告诉SpringBoot 这是一个配置类 == 以前的配置文件
//@ConditionalOnBean(name = "tomcatPet") //表示容器中有tomcatPet组件时,该类中标注的全部组件才生效
//@ConditionalOnMissingBean(name = "tomcatPet") //表示容器中不存在tomcatPet组件时,该类中标注的全部组件才生效
@ImportResource("classpath:beans.xml")
public class MyConfig {
/**
* 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
* @return
*/
// @ConditionalOnBean(name = "tomcatPet") //表示容器中有tomcatPet组件时,才在容器中注入user01组件
@Bean //给容器中添加组件,以方法名作为主键id,返回类型就是组件类型,返回值就是组件在容器中的实例
public User user01(){
User dragonUser = new User("dragon",18);
// User 组件依赖了Pet 组件,当proxyBeanMethods 为 true 时,这种依赖关系成立
dragonUser.setPet(pet01());
return dragonUser;
}
// @Bean("tomcatPet")
public Pet pet01(){
return new Pet("dragonPet");
}
}
再次运行主类,看结果:
结果都为true,表示容器中导入了这两个配置的组件。
@ConfigurationProperties 注解实现配置绑定功能。主要运用场景是将properties配置文件中的配置绑定到具体的Java Bean里面,或者反过来将Java Bean中的属性抽取到可配置的文件中。(如果这个过程用基本的Java原生代码来实现是比较麻烦的,需要读取配置文件的内容,然后从配置文件中遍历出所要的属性值,再封装到具体的Java Bean里面。)
下面通过具体实践说明:
先定义一个Car.java类:
package com.menergy.boot.bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 只有在容器中的组件,才拥有Spring Boot 提供的强大功能,比如配置绑定
*/
@Component //表示将Car加入到容器中
@ConfigurationProperties(prefix = "mycar")
public class Car {
private String brand; //品牌
private Integer price; //价格
public Car() {
}
public Car(String brand, Integer price) {
this.brand = brand;
this.price = 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 +
'}';
}
}
现在把这个Car类中相关的属性配置在配置文件 application.properties 中,如下:
以前要把配置文件中汽车的两个信息封装到Car类中的属性比较麻烦,现在采用@ConfigurationProperties 注解就很简单:
先看ConfigurationProperties 类定义的属性:
prefix属性表示对应配置文件中的前缀, 配置如下:
(同时加入@Component注解,将Car加入到容器中,因为只有在容器中的组件,才拥有Spring Boot 提供的强大功能,比如配置绑定)
写个Controller进行测试:
完整代码:
package com.menergy.boot.controller;
import com.menergy.boot.bean.Car;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
//@ResponseBody
//@Controller
@RestController
public class HelloController {
@Autowired //自动装配
Car car;
@RequestMapping("/car")
public Car car(){
return car;
}
// @ResponseBody
@RequestMapping("/hello")
public String handle01(){
return "Hello, Spring Boot 2";
}
}
运行服务,浏览器访问"localhost:8888/car":
结果是配置文件里的值,实践成功。
上面的采用的是“@Component + @ConfigurationProperties” 方式实现,也可以采用“@EnableConfigurationProperties + @ConfigurationProperties” 方式实现,如下:
首先,在配置类中加入@EnableConfigurationProperties注解,表示开启属性配置功能。
@EnableConfigurationProperties(Car.class) 有两个作用:
1. 开启Car的配置绑定功能 2. 把这个Car这个组件自动注册到容器中
(应用场景:有时欧美绑定的类是第三方Jar包中的类,我们不能在其上加入@Component ,这时就可以采用这种配置绑定方式)
运行结果:
基于上面的注解基础,我们可以深入Spring Boot 的代码底层,了解Spring Boot是如何实现自动配置的。
所有的分析从主程序开始,先看一下主程序:
点击@SpringBootApplication 注解进入SpringBootApplication 类:
从中可以看出,@SpringBootApplication 注解相当于下面三个注解:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
之间点击@SpringBootConfiguration注解进入SpringBootConfiguration类,发现该类上有@Configuration 注解,可以看出SpringBootConfiguration 就是一个配置类, 也就有说明主程序MainApplication 也是一个配置类(核心配置类):
该注解指定扫描哪些包,也是常用的注解,这里也不做深入分析。
所有,三个主要注解中,分析的重心就是这个@EnableAutoConfiguration 注解。
点击进入这个注解类,发现出了语言信息注解外,主要就两个注解:
也就是说 @EnableAutoConfiguration 注解是
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
这两个注解的合成。
这个注解的字面意思是自动配置包。点击进入这个注解,发现其实是一个@Import 类型的注解:
基于前面的的实践,我们知道@Import其实就是给容器中导入组件。这里这个组件叫“AutoConfigurationPackages.Registrar” , 然后点击进入Registrar类:
发现Registrar 有两个方法,其实这里是利用Registrar 给容器中批量导入(注册)组件。
究竟批量注册哪些组件呢?
这里采用Debug方式看一下:
可以看出,其中一个参数是AnnotationMetadata 类型的元信息,这个注解元信息是标在了主方法MainApplication 上:
从这里可以看出,该方法是首先拿到主方法,然后获取主方法的包名,然后转成字符串。
具体看一下,选中“new PackageImports(metadata).getPackageNames().toArray(new String[0])” 右键:
计算一下:
得到包名“com.menergy.boot”, 所以相当于Registrar 是将某个包下面的所有组件全部注册进容器里。
总结:@AutoConfigurationPackage 注解的功能是将指定的一个包下的所有组件导入进来。(默认情况下,将Spring Boot 将主程序所在包下的所有组件注册到容器中)
(所有,每一个默认规则在底层都有源码的体现)
待续。。。