Spring系列:@Configuration @Bean

前提:

之前我们都是通过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容器中

你可能感兴趣的:(Java,Spring,spring,java)