Spring详解(八)----通过@Configuration和@Bean注解注册Bean
1、@Configuration注解
在Spring4以后,官方推荐使用 JavaConfig 来代替 application.xml 声明将Bean交给容器管理。在Spring Boot 中,JavaConfig 的使用完全代替了application.xml 实现了xml的零配置,所以下面来介绍下@Configuration和@Bean注解的使用。@Configuration与@Bean一般配合使用,作用主要用于在 Java 代码中实现 Spring 的配置,它的目的是代替Spring的xml配置文件。下面来简单介绍一下这两个注解:
- @Configuration:标注在类上,让这个类的功能等同于一个bean xml配置文件(包含命名空间)。
@Configuration
public class BeanConfig {
}
上面代码类似于下面的xml:
<?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 definitions here -->
</beans>
如果要获取它们,我们可以使用AnnotationConfigApplicationContext 或 AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。但是此时ConfigBean类中没有任何内容,相当于一个空的xml配置文件,此时我们要在ConfigBean类中注册bean,那么我们就要用到@Bean注解了。
2、@Bean注解
这个注解类似于bean.xml配置文件中的bean元素,用来在Spring容器中注册一个Bean。@Bean注解用在方法上,表示通过方法来定义一个Bean,默认将方法名称作为Bean名称,将方法返回值作为Bean对象,注册到Spring容器中。
@Configuration
public class BeanConfig {
@Bean
public User user() {
return new User();
}
}
上面代码类似于下面的xml:
<?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 definitions here -->
<bean id="user" class="com.thr.spring.pojo.User"/>
</beans>
@Bean注解的配置项中包含了一些属性,所以我们来看一下其源码:
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) //@1
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
@AliasFor("name")
String[] value() default {};
@AliasFor("value")
String[] name() default {};
@Deprecated
Autowire autowire() default Autowire.NO;
boolean autowireCandidate() default true;
String initMethod() default "";
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}
其中@Bean的配置项中包含的6个配置项含义:
- value:等同于下面的name属性
- name:相当于bean的id,<bean id="">,它是一个字符串数组,允许配置多个 BeanName,如果不配置,则默认是方法名
- autowire:标志是否是一个引用的 Bean 对象,默认值是 Autowire.NO,这个参数上面标注了@Deprecated,表示已经过期了,不建议使用了
- autowireCandidate:是否作为其他对象注入时候的候选Bean
- initMethod:自定义初始化方法
- destroyMethod:自定义销毁方法
3、@Configuration与@Bean案例
下面是使用@Configuration与@Bean在Java代码中给容器之中添加Bean的代码案例:
[1]、首先创建一个User类,如下:
/**
* 用户实体类 用@Component注解将User类标注为一Bean
*
* @author tanghaorong
*/
@Data
public class User {
@Value(value = "2020")
private Integer userId;
@Value(value = "小唐")
private String userName;
@Value(value = "20")
private Integer userAge;
@Value(value = "123456")
private String userPwd;
@Value(value = "中国北京")
private String userAddress;
}
[2]、创建BeanConfig类,用来启动容器和注册Bean对象:
package com.thr.spring.config;
import com.thr.spring.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BeanConfig {
//bean名称为方法默认值:user1
@Bean
public User user1() {
return new User();
}
//bean名称通过value指定了:user2Bean
@Bean("user2Bean")
public User user2() {
return new User();
}
//bean名称为:user3Bean,2个别名:[user3BeanAlias1,user3BeanAlias2]
@Bean({"user3Bean", "user3BeanAlias1", "user3BeanAlias2"})
public User user3() {
return new User();
}
}
[3]、测试代码(这里使用AnnotationConfigApplicationContext类来获取,它会将配置类中所有的Bean注册到Spring容器中):
/**
* Spring测试代码
*
* @author tanghaorong
*/
public class SpringRunTest {
public static void main(String[] args) {
//1.初始化Spring容器,通过注解加载
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
//2.通过容器获取实例
User user1 = applicationContext.getBean("user1", User.class);
User user2Bean = applicationContext.getBean("user2Bean", User.class);
User user3Bean = applicationContext.getBean("user3Bean", User.class);
User user3BeanAlias1 = applicationContext.getBean("user3BeanAlias1", User.class);
User user3BeanAlias2 = applicationContext.getBean("user3BeanAlias2", User.class);
//3.调用实例中的属性
System.out.println("user1 = " + user1);
System.out.println("user2Bean = " + user2Bean);
System.out.println("user3Bean = " + user3Bean);
System.out.println("user3BeanAlias1 = " + user3BeanAlias1);
System.out.println("user3BeanAlias2 = " + user3BeanAlias2);
}
}
[4]、运行测试代码,查看控制台打印结果:
从上面的运行结果来看,Bean对象已经创建成功了。
4、去掉@Configuration注解会怎样
这里提出一个这样的问题:我们一般都是@Configuration和@Bean注解一起结合使用,如果不使用@Bean注解什么事情也没有,那么如果不加@Configuration注解,那能不能只通过@Bean注解注册Bean呢?下面来验证一下。
案例一:Bean之间是没有依赖关系的
实体对象User:
@Data
public class User {
@Value(value = "2020")
private Integer userId;
@Value(value = "小唐")
private String userName;
@Value(value = "20")
private Integer userAge;
@Value(value = "123456")
private String userPwd;
@Value(value = "中国北京")
private String userAddress;
}
配置类对象:
@Configuration
public class BeanConfig {
//bean名称为方法默认值:user1
@Bean
public User user1() {
return new User();
}
//bean名称通过value指定了:user2Bean
@Bean("user2Bean")
public User user2() {
return new User();
}
//bean名称为:user3Bean,2个别名:[user3BeanAlias1,user3BeanAlias2]
@Bean({"user3Bean", "user3BeanAlias1", "user3BeanAlias2"})
public User user3() {
return new User();
}
}
运行测试类
/**
* Spring测试代码
*
* @author tanghaorong
*/
public class SpringRunTest1 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
for (String beanName : context.getBeanDefinitionNames()) {
String[] aliases = context.getAliases(beanName);
System.out.printf("bean名称:%s,别名:%s,bean对象:%s%n",
beanName,
Arrays.asList(aliases),
context.getBean(beanName));
}
}
}
运行输出(部分截取):
有@Configuration注解的
有@Configuration注解的
对比得出:
- 对比最后3行,可以看出:有没有@Configuration注解,@Bean都会起效,都会将@Bean修饰的方法作为bean注册到容器中
- 两个内容的第一行有点不一样,被@Configuration修饰的bean最后输出的时候带有EnhancerBySpringCGLIB 的字样,而没有@Configuration注解的bean没有Cglib的字样;有EnhancerBySpringCGLIB 字样的说明这个Bean被cglib处理过的,变成了一个代理对象。
目前为止我们还是看不出二者本质上的区别,继续向下看。
案例二:Bean之间是有依赖关系的
public class User {
private GirlFriend girlFriend;
public User(GirlFriend girlFriend) {
this.girlFriend = girlFriend;
}
@Override
public String toString() {
return "User{" +
"girlFriend=" + girlFriend +
'}';
}
}
public class GirlFriend {
}
配置代码
@Configuration
public class BeanConfig1 {
@Bean
public GirlFriend girlFriend() {
System.out.println("调用了girlFriend创建方法");
return new GirlFriend();
}
@Bean
public User user1() {
System.out.println("调用了user创建方法一");
GirlFriend girlFriend = this.girlFriend();
return new User(girlFriend);
}
@Bean
public User user2() {
System.out.println("调用了user创建方法二");
GirlFriend girlFriend = this.girlFriend();
return new User(girlFriend);
}
}
运行测试代码还是用上面的测试类就行。
运行输出(部分截取):
有@Configuration注解的
调用了girlFriend创建方法
调用了user创建方法一
调用了user创建方法二
bean名称:girlFriend,别名:[],bean对象:com.thr.spring.pojo.GirlFriend@564718df
bean名称:user1,别名:[],bean对象:User{girlFriend=com.thr.spring.pojo.GirlFriend@564718df}
bean名称:user2,别名:[],bean对象:User{girlFriend=com.thr.spring.pojo.GirlFriend@564718df}
没有@Configuration注解的
调用了girlFriend创建方法
调用了user创建方法一
调用了girlFriend创建方法
调用了user创建方法二
调用了girlFriend创建方法
bean名称:girlFriend,别名:[],bean对象:com.thr.spring.pojo.GirlFriend@76a3e297
bean名称:user1,别名:[],bean对象:User{girlFriend=com.thr.spring.pojo.GirlFriend@4d3167f4}
bean名称:user2,别名:[],bean对象:User{girlFriend=com.thr.spring.pojo.GirlFriend@ed9d034}
通过对比可以看出:
- 有@Configuration注解的,被@Bean修饰的方法都只被调用了一次,所有的GirlFriend都是同一个
- 没有@Configuration注解的,被@Bean修饰的方法都只被调用了一次,但是所有的GirlFriend都不是同一个
这是为什么呢?因为被@Configuration修饰的类,Spring容器中会通过cglib给这个类创建一个代理,代理会拦截所有被@Bean 修饰的方法,默认情况(Bean为单例)下确保这些方法只被调用一次,从而确保这些Bean是同一个Bean,即单例的。所以@Configuration修饰的类有cglib代理效果,默认添加的Bean都为单例。
到目前为止加不加@Configuration注解,有什么区别,大家估计比我都清楚了,简单总结:
- 不管@Bean所在的类上是否有@Configuration注解,都可以将@Bean修饰的方法作为一个Bean注册到Spring容器中
- @Configuration注解修饰的类,会被Spring通过cglib做增强处理,通过cglib会生成一个代理对象,代理会拦截所有被@Bean注解修饰的方法,可以确保一些Bean是单例的