文章目录
- @configuration
- 基本使用
- Full 模式与 Lite 模式
- SpringBoot 在底层 Configuration 的两个配置
- 组件依赖
- 两个模式使用场景
@configuration
基本使用
两个组件,一个是 User(用户)组件、一个是 Pet(宠物)组件,将这两个组件加到容器中,可以这样做:
① 创建一个 Spring 的配置文件 beans.xml;
② 以前 Spring xml 配置的方式:使用 bean 标签,给容器中添加组件,在里边可以给组件添加属性;
③ SpringBoot 已经不写 xml 配置文件了,SpringBoot 可以在底层用 @configuration 注解:创建一个类,在类上方使用这个注解,这个注解就是告诉 SpringBoot 这是一个配置类,等同于以前的配置文件。以前用 bean 标签,给容器中添加组件,现在用方法构造出来,并在上方使用 @Bean 注解,以方法名作为组件的id,返回类型就是组件类型,方法返回的值(对象)就是组件在容器中的实例。
- 目录结构:
- Pet 类:
package com.cky.boot.bean;
import javax.swing.*;
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;
}
}
- User 类:
package com.cky.boot.bean;
import org.omg.PortableInterceptor.ServerRequestInfo;
import javax.naming.InsufficientResourcesException;
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;
}
}
- MyConfig 配置类:
package com.cky.boot.config;
import com.cky.boot.bean.Pet;
import com.cky.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.jws.soap.SOAPBinding;
@Configuration //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
@Bean //给容器中添加组件,以方法名作为组件的id,返回类型就是组件类型,方法返回的值(对象)就是组件在容器中的实例
public User user01(){
return new User("zhangsan",18);
}
@Bean("Caty") //如果不想让方法名作为组件名也可以在Bean标签直接给一个自定义的名字
public Pet Cat(){
return new Pet("aimi");
}
}
④ 验证容器中有这两个组件:
- MainApplication 启动类:
@SpringBootApplication
public class MainApplication {
public static void main(String[] args){
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//验证容器中有这两个组件
String[] names = run.getBeanDefinitionNames();
for(String name:names){
System.out.println(name);
}
}
}
⑤ 配置类里面使用 @Bean 标注在方法上给容器注册组件,默认是单实例的(Bean 默认是单例模式)
@SpringBootApplication
public class MainApplication {
public static void main(String[] args){
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//验证容器中有这两个组件
String[] names = run.getBeanDefinitionNames();
for(String name:names){
System.out.println(name);
}
//从容器中多次获取组件
Pet Caty01 = run.getBean("Caty",Pet.class);
Pet Caty02 = run.getBean("Caty",Pet.class);
System.out.println("组件:"+(Caty01 == Caty02)); //判读组件是不是单实例的
}
}
- 运行时报错,找不到名字为 “Caty” 的组件。
- 因为 SpringBoot 默认扫描的是启动类(MainApplication)所在目录下的子包和类,配置类与启动类不在同一包下,扫描不到配置类,因此报错,添加语句 @ComponentScan(“com.cky.boot”),扩大扫描包的范围,解决以上问题。
- 运行结果:
- Configuration 标注的这个类 MyConfig 它本身也是一个组件,配置类也是容器中的一个组件,验证:
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);
- 运行结果:
- Configuration.class
⑥ 在 SpringBoot2.0 以后的版本里边,基于 Spring5.2 以后,Configuration 类多了一个属性proxyBeanMethods 默认是 true;这是与 SpringBoot1.0 的不同。proxyBeanMethods:代理 Bean 的方法。
- MyConfig.class
- 在 MyConfig 类多次调用 user01 方法,那么方法返回的对象是从容器中拿还是就是普通的调用方法?验证如下:
package com.cky.boot.controller;
import com.cky.boot.bean.Pet;
import com.cky.boot.bean.User;
import com.cky.boot.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import java.security.SecureRandom;
@ComponentScan("com.cky.boot")
@SpringBootApplication
public class MainApplication {
public static void main(String[] args){
//返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for(String name:names){
System.out.println(name);
}
//从容器中多次获取组件
Pet Caty01 = run.getBean("Caty",Pet.class);
Pet Caty02 = run.getBean("Caty",Pet.class);
System.out.println("组件:"+(Caty01 == Caty02)); //判读组件是不是单实例的
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);
}
}
- 这说明,外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册到容器中的单实例对象。user、user1两个对象相等的原因在于 Configuration 的这个属性 @Configuration(proxyBeanMethods = true),容器中获取组件的这个对象 MyConfig 不是一个普通对象,而是 MyConfig$$EnhancerBySpringCGLIB,实际上是被 SpringCGLIB 增强了的代理对象,也就是说我们获取到的是代理对象,代理对象调用 user01 ,SpringBoot 里的默认逻辑就是如果 @Configuration(proxyBeanMethods = true),我们这个类(MyConfig),我们获取到的就是代理对象,代理对象调用方法,SpringBoot 默认就会检查容器中有没有这个方法已经返回的组件,如果有就从缓存里取,没有在新创建,保持组件单实例。 ⑦ 若将 proxyBeanMethods 属性改为 false,运行结果如下:
- 获取到的 MyConfig 不再是代理对象,多次调用方法得到的不是同一个实例,也就是说调用方法每次都会新创建一个对象。
Full 模式与 Lite 模式
SpringBoot 在底层 Configuration 的两个配置
- Full 全配置模式(@Configuration(proxyBeanMethods = true)保证每个 @Bean 方法被调用多少次返回的组件都是单实例的;
- Lite 轻量级配置模式(@Configuration(proxyBeanMethods = false)每个 @Bean 方法被调用多少次返回的组件都是新创建的。
组件依赖
举例子用户养宠物:
- 修改 User.class
public class User {
private String name;
private Integer age;
private Pet pet;
public Pet getPet(){
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
@Configuration(proxyBeanMethods = true)
public class MyConfig {
public User user01(){
User zhangsan = new User("zhangsan",18);
//User组件依赖了Pet组件
zhangsan.setPet(Cat());
return zhangsan;
}
@Bean("Caty")
public Pet Cat(){
return new Pet("aimi");
}
}
- MainApplication.class
@ComponentScan("com.cky.boot")
@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 caty = run.getBean("Caty",Pet.class);
System.out.println("用户的宠物:"+(user01.getPet() == caty));
}
}
- 运行结果说明,用户的宠物就是容器中的宠物。
- 若将 proxyBeanMethods = true 改为 false,运行结果说明用户的宠物不是容器中的宠物。
两个模式使用场景
- 配置类组件之间无依赖关系用 Lite 模式,加速容器启动过程,减少判断。
- 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,可以快速获取到 bean,减少新生成实例的消耗,减少 jvm 垃圾回收,用 Full 模式。