Hey, Nice to meet You. 

必有过人之节.人情有所不能忍者,匹夫见辱,拔剑而起,挺身而斗,此不足为勇也,天下有大勇者,猝然临之而不惊,无故加之而不怒.此其所挟持者甚大,而其志甚远也.          ☆☆☆所谓豪杰之士,

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]、运行测试代码,查看控制台打印结果:

image

从上面的运行结果来看,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注解的

image

有@Configuration注解的

image

对比得出:

  1. 对比最后3行,可以看出:有没有@Configuration注解,@Bean都会起效,都会将@Bean修饰的方法作为bean注册到容器中
  2. 两个内容的第一行有点不一样,被@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}

通过对比可以看出:

  1. 有@Configuration注解的,被@Bean修饰的方法都只被调用了一次,所有的GirlFriend都是同一个
  2. 没有@Configuration注解的,被@Bean修饰的方法都只被调用了一次,但是所有的GirlFriend都不是同一个

这是为什么呢?因为被@Configuration修饰的类,Spring容器中会通过cglib给这个类创建一个代理,代理会拦截所有被@Bean 修饰的方法,默认情况(Bean为单例)下确保这些方法只被调用一次,从而确保这些Bean是同一个Bean,即单例的。所以@Configuration修饰的类有cglib代理效果,默认添加的Bean都为单例。

到目前为止加不加@Configuration注解,有什么区别,大家估计比我都清楚了,简单总结:

  1. 不管@Bean所在的类上是否有@Configuration注解,都可以将@Bean修饰的方法作为一个Bean注册到Spring容器中
  2. @Configuration注解修饰的类,会被Spring通过cglib做增强处理,通过cglib会生成一个代理对象,代理会拦截所有被@Bean注解修饰的方法,可以确保一些Bean是单例的
posted @ 2020-12-20 21:12  唐浩荣  阅读(1586)  评论(0编辑  收藏  举报