前言
今天这篇文章带大家学习@Configuration和@Bean的使用,并通过具体的实例体验一下注解的方便快捷。
基于Java类的配置选项
对于@Configuration注解的配置类有如下要求。
- 配置类不能是 final 类(没法动态代理)。
- 配置类必须是非本地的(即不能将配置类定义在其他类的方法内部,不能是private)。
- 配置类必须有一个无参构造函数。
基本使用方法
符合上述条件的类,就可以使用@Configuration来进行注解,表示这个类可以使用Spring IoC容器作为bean定义的来源。@Bean注解在该类的方法上,AnnotationConfigApplicationContext将配置类中标注了@Bean的方法的返回值识别为Spring Bean,并注册到容器中,归入IoC容器管理。Spring在解析该类时,会识别出标注@Bean的所有方法,执行并将方法的返回值(MysqlDataSource和OracleDataSource对象)注册到IoC容器中。默认情况下,方法名即为Bean的名字。
@Configuration的属性
下面我们先通过@Configuration演示一个简单的示例:
1. MysqlDataSource :
package com.itheima.course.configuration;
public class MysqlDataSource {
public MysqlDataSource() {
System.out.println("Call MysqlDataSource constructor");
}
}
2. OracleDataSource:
package com.itheima.course.configuration;
public class OracleDataSource {
public OracleDataSource() {
System.out.println("Call OracleDataSource constructor");
}
}
3. AppConfig
package com.itheima.course.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public MysqlDataSource mysqlDataSource() {
oracleDataSource();
return new MysqlDataSource();
}
@Bean
public OracleDataSource oracleDataSource() {
return new OracleDataSource();
}
}
4. TestAppconfig
package com.itheima.course.configuration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @description configuration详解举例
* @date 2021-02-18 18:26:22
*/
public class TestAppconfig {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
//MysqlDataSource mysqlDataSource1 = (MysqlDataSource) ac.getBean("mysqlDataSource");
}
}
运行TestAppconfig结果:
Call OracleDataSource constructor
Call MysqlDataSource constructor
按照Java的语法,OracleDataSource的构造函数应该是被调用了两次,但是为什么只有输出一句Call OracleDataSource constructor。这其实就是@Configuration再发挥作用,这里不妨去掉@Configuration,再去运行一下,就会发现OracleDataSource的构造函数被执行了两次。官方给出了这样一段解释对于被@Configuration注解的类
In common scenarios,@Bean methods are to be declared within@Configuration classes, ensuring that “full” mode is always used and that cross-method references therefore get redirected to the container’s lifecycle management.
在一般情况下,@Bean method 是被声明在@Configuration类中的,以确保 full mode总是被使用,并且跨方法的引用会被重定向到容器生命周期管理。
怎么理解呢?原来Spring将被@Configuration注解的配置类定义为full configuration, 而将没有被@Configuration注解的配置类定义为lite configuration。full configuration能重定向从跨方法的引用,从而保证上述代码中的OracleDataSource bean是一个单例。下面我们可以跟踪下@Configuration源码看下,如下。
@Configuration的定义代码(源代码):
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
String value() default "";
}
@Configuration注解本身定义时被@Component标注了,因此本质上来说@Configuration也是一个@Component,只不过我们在具体使用的过程中基本用不到它的实例化对象。因此@Configuration也可以替换为@Componment,但使用@Configuration时会产生Cglib动态代理(主要是为了让@Bean生成的bean是单例),这里稍微提一下为什么要使用cglib
,而不是jdk动态代理
,主要是因为jdk动态代理
是基于接口的,而这里AppConfig并没有实现任何接口,所以必须用cglib
技术。而用Componment时不会产生动态代理。即被@Configuration 注解的类,是 full configuration class,该类会被增强(通过cglib
),从而实现跨方法引用调用被重定向到Spring 生命周期管理,最终保证@Bean method生成的bean是一个单例。
@Bean属性
@Bean注解的具体代码定义(源代码):
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
@AliasFor("name")
String[] value() default {};
@AliasFor("value")
String[] name() default {};
Autowire autowire() default Autowire.NO;
String initMethod() default "";
String destroyMethod() default "(inferred)";
}
可以看出@Bean具有以下属性:
- name :指定一个或者多个Bean的名字。等价于XML配置中的name属性,示例中的@Bean(name = “oracleDataSource”)。
- initMethod:容器在初始化完Bean之后,会调用该属性指定的方法。等价于XML配置中的init-method属性。
- destroyMethod:该属性与initMethod功能相似,在容器销毁Bean之前,会调用该属性指定的方法。等价于XML配置中的destroy-method属性。
- autowire:指定Bean属性的自动装配策略,取值是Autowire类型的三个静态属性。Autowire.BY_NAME,Autowire.BY_TYPE,Autowire.NO。与XML配置中的autowire属性的取值相比,少了constructor,因为 constructor在这里已经没有意义了。
@Bean默认是单例模式,并且没有提供指定作用域的属性,可以通过@Scope来实现该功能。使用@Scope("prototype")后,只有在用到的时候才会进行实例化,容器启动的时候不会实例化。
@Bean
@Scope("prototype")
public MysqlDataSource mysqlDataSource() {
return new MysqlDataSource();
}
@Lazy 延迟初始化
@Lazy : 表明一个bean 是否延迟加载,可以作用在方法上,表示这个方法被延迟加载;可以作用在@Component注释的类上,表明这个类中所有的bean 都被延迟加载。如果没有@Lazy注释,或者@Lazy 被设置为false,那么该bean 就会急切渴望被加载;
Spring扫描加载
当配置完Spring扫描指定包及其子包中的类时,会识别所有标记了@Component、@Controller、@Service、@Repository注解的类,由于@Configuration注解本身也用@Component标注了,Spring将能够识别出 @Configuration标注类。
实例
现在对上面的示例进行单元测试,其中MysqlDataSource和OracleDataSource类中分别提供了打印日志的方法:
public void print(){
System.out.println("I am MysqlDataSource!");
}
和
public void print(){
System.out.println("I am OracleDataSource!");
}
指定单元测试的代码如下:
public class ConfigBeanTest {
@Test
public static void testGetBean() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(DataSourceConfig.class);
MysqlDataSource mysqlDataSource = ctx.getBean(MysqlDataSource.class);
mysqlDataSource.print();
}
}
当执行单元测试,成功打印出:
I am MysqlDataSource!
实战技巧
如果我们在DataSourceConfig中再添加一个方法,这个方法用到了前面实例化的两个bean对象,那么该如何处理?常规的想法是在DataSourceConfig中添加如下代码,然后再直接使用属性:
@Autowired
private MysqlDataSource mysqlDataSource;
其实不必如此,直接调用方法mysqlDataSource()方法即可。比如,需要新增下面类的实例化:
public class CommonDataSource {
private MysqlDataSource mysqlDataSource;
private OracleDataSource oracleDataSource;
// 省略getter/setter方法
}
那么,我们只用在DataSourceConfig中添加如下代码即可:
@Bean
public CommonDataSource commonDataSource() {
CommonDataSource commonDataSource = new CommonDataSource();
commonDataSource.setMysqlDataSource(mysqlDataSource());
commonDataSource.setOracleDataSource(oracleDataSource());
return commonDataSource;
}
注意:set方法内直接set的是mysqlDataSource()。
小结
这节课我们讲解了Spring注解中@Configuration和@Bean使用方法,在Springboot中集成其他三方框架时,这种写法使用的越来越普遍。如果一时无法转换思维,可对照xml文件的配置进行逐一切换过来,比如xml要定义一个bean,那么用注解就是@Bean注解一个方法。如果方法里面的参数还有其他的依赖,那就采用上面介绍的实战技巧依次追踪set对应的参数,并将其先通过@Bean实例化。
文章参考:
1. SPRING注解之@CONFIGURATION和@BEAN使用详解
2. @Configuration简介