之前我们都是通过xml的方式定义bean,里面会写很多bean元素,然后spring启动的时候,就会读取bean xml配置文件,然后解析这些配置,然后会将这些bean注册到spring容器中,供使用者使用。
jdk1.5里面有了注解的功能,spring也没闲着,觉得注解挺好用的,就将注解加了进来,让我们通过注解的方式来定义bean,用起来能达到xml中定义bean一样的效果,并且更简洁一些,这里面需要用到的注解就有@Configuration注解和@Bean注解。
@Configuration注解
@Configuration这个注解可以加在类上,让这个类的功能等同于一个bean xml配置文件,如下:
@Configuration
public class ConfigBean {
}
上面代码类似于下面的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-4.3.xsd">
</beans>
通过AnnotationConfigApplicationContext来加载@Configuration修饰的类,如下:
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(ConfigBean.class);
此时ConfigBean类中没有任何内容,相当于一个空的xml配置文件,此时我们要在ConfigBean类中注册bean,那么我们就要用到@Bean注解了
@Bean注解
这个注解类似于bean xml配置文件中的bean元素,用来在spring容器中注册一个bean。
@Bean注解用在方法上,表示通过方法来定义一个bean,默认将方法名称作为bean名称,将方法返回值作为bean对象,注册到spring容器中。
如:
@Bean
public User user1() {
return new User();
}
@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;
}
@1:说明这个注解可以用在方法和注解类型上面。
每个参数含义:
value和name是一样的,设置的时候,这2个参数只能选一个,原因是@AliasFor导致的
@AliasFor 详解见:https://www.jianshu.com/p/869ed7037833
value:字符串数组,第一个值作为bean的名称,其他值作为bean的别名
autowire:这个参数上面标注了@Deprecated,表示已经过期了,不建议使用了
autowireCandidate:是否作为其他对象注入时候的候选bean,不清楚的可以去看看:autowire-candidate详解
initMethod:bean初始化的方法,这个和生命周期有关
destroyMethod:bean销毁的方法,也是和生命周期相关的
用例
package com.javacode2018.lesson001.demo20;
public class User {
}
Bean配置类:ConfigBean
i
mport org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ConfigBean {
//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();
}
}
调用
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Arrays;
public class ConfigurationTest {
@Test
public void test1() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigBean.class);//@1
for (String beanName : context.getBeanDefinitionNames()) {
//别名
String[] aliases = context.getAliases(beanName);
System.out.println(String.format("bean名称:%s,别名:%s,bean对象:%s",
beanName,
Arrays.asList(aliases),
context.getBean(beanName)));
}
}
}
输出
调用serviceA()方法
调用serviceB1()方法
调用serviceB2()方法
bean名称:configBean2,别名:[],bean对象:com.javacode2018.lesson001.demo20.ConfigBean2$$EnhancerBySpringCGLIB$$ffa0178@77f1baf5
bean名称:serviceA,别名:[],bean对象:com.javacode2018.lesson001.demo20.ServiceA@41a2befb
bean名称:serviceB1,别名:[],bean对象:ServiceB{
serviceA=com.javacode2018.lesson001.demo20.ServiceA@41a2befb}
bean名称:serviceB2,别名:[],bean对象:ServiceB{
serviceA=com.javacode2018.lesson001.demo20.ServiceA@41a2befb}
分析结果
从输出中可以看出
前三行可以看出,被@Bean修饰的方法都只被调用了一次,这个很关键
最后三行中可以看出都是同一个ServiceA对象,都是`ServiceA@41a2befb`这个实例
原因:
被@Configuration修饰的类,spring容器中会通过cglib给这个类创建一个代理,代理会拦截所有被@Bean修饰的方法,默认情况(bean为单例)下确保这些方法只被调用一次,从而确保这些bean是同一个bean,即单例的。
至于底层是如何实现的,详解java中的动态代理和cglib代理。
我们再来看看将ConfigBean2上的的@Configuration去掉,效果如何,代码就不写了(有的IDE可能直接会有红色提示,别管,直接点运行就行),输出结果:
调用serviceA()方法
调用serviceB1()方法
调用serviceA()方法
调用serviceB2()方法
调用serviceA()方法
bean名称:configBean2,别名:[],bean对象:com.javacode2018.lesson001.demo20.ConfigBean2@6e171cd7
bean名称:serviceA,别名:[],bean对象:com.javacode2018.lesson001.demo20.ServiceA@402bba4f
bean名称:serviceB1,别名:[],bean对象:ServiceB{
serviceA=com.javacode2018.lesson001.demo20.ServiceA@795cd85e}
bean名称:serviceB2,别名:[],bean对象:ServiceB{
serviceA=com.javacode2018.lesson001.demo20.ServiceA@59fd97a8}
结果分析
serviceA()方法被调用了3次
configBean2这个bean没有代理效果了
最后3行可以看出,几个ServiceA对象都是不一样的
到目前为止加不加@Configuration注解,有什么区别,大家估计比我都清楚了
@Configuration注解修饰的类,会被spring通过cglib做增强处理,通过cglib会生成一
个代理对象,代理会拦截所有被@Bean注解修饰的方法,可以确保一些bean是单例的
不管@Bean所在的类上是否有@Configuration注解,都可以将@Bean修饰的方法作为一个
bean注册到spring容器中