@Configuration
该注解是Spring的注解,传统的Spring中注册bean要通过xml去配置,使用该注解可以脱离xml,完全使用java类去配置
对比两种方式注册bean
-
传统方式用xml注册bean
<?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.plasticine.boot.bean.User"> <property name="name" value="吴签"/> <property name="age" value="31"/> </bean> </beans>
-
使用
@Configuration
注解写一个配置类MyConfig
@Configuration public class MyConfig { @Bean public User user01() { return new User("吴签", 31); } }
两种注册方式是可以一 一对应的,@Configuration
注解了的类就代表了一个xml配置文件,@Bean
注解的方法就代表了xml中的一个<bean
>标签,id
就是方法名,返回的类型就是bean组件的类型,返回的值就是组件在IoC容器中的实例
@Bean组件可以接收一个参数,这个参数用来替代id
,getBean的时候就可以用这个参数的值去获取Bean
@Configuration
public class MyConfig {
@Bean("wuqian")
public User user01() {
return new User("吴签", 31);
}
}
查看注册的组件
- 查看容器中的组件,看看我们刚刚注册的组件是否存在容器中
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
// 1. 返回 IoC 容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
// 2. 查看容器中的组件
String[] beanDefinitionNames = run.getBeanDefinitionNames();
for (String name : beanDefinitionNames) {
System.out.println(name);
}
}
}
可以看到容器中确实有我们的myConfig
配置类和user01
组件
获取组件
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
// 1. 返回 IoC 容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
// 2. 查看容器中的组件
String[] beanDefinitionNames = run.getBeanDefinitionNames();
for (String name : beanDefinitionNames) {
System.out.println(name);
}
// 3. 获取组件
User user01 = run.getBean("user01", User.class);
System.out.println(user01);
}
}
可以看到能够获取到注册的组件
配置类的特点
-
配置类里面使用
@Bean
标注在方法上给容器注册组件,默认是单例的// 3. 获取组件 User user01 = run.getBean("user01", User.class); User user02 = run.getBean("user01", User.class); System.out.println(user01 == user02); // true
-
配置类本身也是组件
MyConfig myConfig = run.getBean(MyConfig.class); System.out.println(myConfig); // com.plasticine.boot.config.MyConfig$$EnhancerBySpringCGLIB$$37c595a1@411291e5
-
配置类注解
@Configuration
中有一个参数 --proxyBeanMethods
,这个参数用于控制是否代理控制bean@Configuration(proxyBeanMethods = false)
该参数默认是
false
,表示不是单例的,即表示每次调用组件注册方法都是重新new出来的一个对象而不是直接从容器中拿的// 4. 验证 proxyBeanMethods = false 的情况 MyConfig myConfig = run.getBean(MyConfig.class); User user1 = myConfig.user01(); User user2 = myConfig.user01(); System.out.println(user1 == user2); // false
- 调用了两次配置类的方法,获取到的实例是不同的,说明不是单例的
- 而
proxyBeanMethods
为true
时,user1 == user2
的结果为true
,是单例的
所以
proxyBeanMethods
这个参数就是用来控制组件注册方法的调用是否是单例的- true --> 单例
- false --> 非单例
从更深层次去理解,
proxyBeanMethods
置为true
时,拿到的myConfig这个对象是一个代理对象,这样每次调用user01()
都是让代理对象去调用,代理对象会做处理,如果已经有了user01
的实例就会直接返回这个实例,而不是去new一个返回,如果proxyBeanMethods
置为false,返回的myConfig是一个真实对象,自然每次调用user01()
的时候都会new一个了,所以user1和user2自然就不是同一个实例
进而引申出springboot代理bean的两种模式
- Full --> proxyBeanMethods=true
- Lite --> proxyBeanMethods=false
这两种模式的特性用在什么地方呢?
-
根据组件之间是否有依赖而定
-
比如现在再注册一个宠物组件,并且假设一个用户会有一只狗,那么这个时候就要求是单例模式的
@Configuration(proxyBeanMethods = false) public class MyConfig { @Bean public User user01() { User user = new User("吴签", 31); user.setPet(pet01()); return user; } @Bean public Pet pet01() { return new Pet("吴签的狗"); } }
User依赖于Pet -- 通过组合的方式
public class User { private String name; private Integer age; private Pet pet; }
如果不是单例模式的话,即
proxyBeanMethods = false
,那么在getBean获取到的user实例中,他的pet和getBean获取的pet不是同一个// 5. 验证组件依赖 User user = run.getBean("user01", User.class); Pet pet = run.getBean("pet01", Pet.class); System.out.println(user.getPet() == pet); // false
如果是单例模式的话,即
proxyBeanMethods = true
,那么结果会是true -
为什么名字是叫Full模式和Lite模式呢?
- Full 代表每次获取组件的时候,都会先去被@Configuration注解的类中查看是否有已存在的实例,这个过程要耗费一些性能
- Lite 代表每次获取组件都是直接去new,并不会经过代理对象去获取,所以如果组件之间没有依赖,即组件之间没有通过组合建立关联的话,就可以设为false,这样会快一些
-
@Import
当需要用到不在包扫描范围中的组件时,可以用该注解进行导入
public @interface Import {
Class<?>[] value();
}
该注解接收一个类型为反射类数组的参数,也就是我们要导入的组件,这里就随便导入一个没有注册过的组件为例,然后查看容器中是否有这个组件
- 导入
@Import({DBAppender.class})
@Configuration
public class MyConfig {
...
}
- 查看容器中是否有该组件
// 6. 验证 @Import 注解导入不在包扫描范围内的组件
DBAppender testBean = run.getBean(DBAppender.class);
System.out.println(testBean); // ch.qos.logback.classic.db.DBAppender[null]
可以看到外部的组件被导入到容器中了
@Conditional条件装配注解
顾名思义,就是根据条件是否成立从而去确定是否进行装配
@Conditional
下有多个条件装配注解,都是看名字就能知道条件是什么,这里就不一 一解释了
这里就以@ConditionalOnBean
为例,根据是否有某一个Bean组件来决定是否装配
@Configuration
public class MyConfig {
@ConditionalOnBean(name="myPet")
@Bean
public User user01() {
User user = new User("吴签", 31);
user.setPet(pet01());
return user;
}
// @Bean("myPet")
@Bean
public Pet pet01() {
return new Pet("吴签的狗");
}
}
- 这里对
user01
这个组件进行条件装配,如果有myPet
这个组件,才会装配user01
,否则不会装配
// 7. 验证条件装配 --> @ConditionalOnBean
boolean isContainMyPet = run.containsBean("myPet");
boolean isContainUser01 = run.containsBean("user01");
System.out.println("isContainMyPet = " + isContainMyPet); // false
System.out.println("isContainUser01 = " + isContainUser01); // false
由于不存在myPet
这个组件,所以user01
这个组件也就不会装配,自然也就获取不到了
@ImportResource
当要把已有的xml配置文件中的bean注册进来的时候,一个一个手动转成java代码的方式太繁琐,可以直接把整个xml导入
@ImportResource("classpath:other-bean.xml")
@Configuration
public class MyConfig {
...
}
@ConfigurationProperties
用于从配置文件中去注入Bean
- 比如有一个汽车bean,要根据配置文件中的配置去注入汽车bean的品牌和价格
汽车bean
-
把Car声明为一个组件,用@Component注解
@Component @ConfigurationProperties(prefix = "mycar") public class Car { private String brand; private Integer price; // ... getter and setter }
-
使用
@EnableConfigurationProperties
开启Car的配置文件绑定功能-
@EnableConfigurationProperties
注解用在配置类上
@EnableConfigurationProperties(Car.class) @Configuration public class MyConfig { ... }
- 汽车Bean只需要使用
@ConfigurationProperties
注解即可
@ConfigurationProperties(prefix = "mycar") public class Car { private String brand; private Integer price; // ... getter and setter }
-
- prefix是用于匹配配置文件中的前缀的
要使用该注解需要先在pom.xml中引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
在application.properties
中添加汽车配置
mycar.brand=BYD
mycar.price=1000000
在controller中自动装配并使用
@RestController
public class HelloController {
@Autowired
private Car car;
@RequestMapping("/car")
public Car car() {
return car;
}
}