spring成神之路第十七篇:@Configration 和@Bean 注解詳解(bean 批量注冊)


上次去頭條面試:

面試官:spring中,類上加不加@Configuration注解,有什么區別?

:當某個類上有@Configuration注解的時候,可以在這個類中使用@Bean注解向spring容器中注冊bean;如果不加@Configuration注解,不能通過@Bean注解注冊bean。

面試官:你確定不用@Configuration注解,不能通過@Bean注解來注冊bean?

:看着面試官,猶豫了10秒鍾,說:不加@Configuration通過@Bean注解也可以注冊bean

面試官:你確定可以注冊?

:嗯。。。。嗯。。。。嗯,我確定可以注冊

面試官:那加不加到底有什么區別呢?

:好像沒有什么區別啊

面試官:好像沒區別。。。。先回去等通知吧!

結果可想而知,沒戲了!

回去之后立即看spring的源碼,終於搞清了這個問題,原來加不加@Configuration還是有相當大的區別的。

下面我們就來看看@Configuration注解到底是干什么的,加不加到底有什么區別,下次你們去面試的時候就可以給面試官吹吹了。

之前我們都是通過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注解了。

總結一下

@Configuration使用步驟:

  1. 在類上使用@Configuration注解

  2. 通過AnnotationConfigApplicationContext容器來加@Configuration注解修飾的類

@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:說明這個注解可以用在方法和注解類型上面。

每個參數含義:

  1. value和name是一樣的,設置的時候,這2個參數只能選一個,原因是@AliasFor導致的

    @AliasFor這個注解不清楚的可以看這個文章:詳解注解

  2. value:字符串數組,第一個值作為bean的名稱,其他值作為bean的別名

  3. autowire:這個參數上面標注了@Deprecated,表示已經過期了,不建議使用了

  4. autowireCandidate:是否作為其他對象注入時候的候選bean,之前的文章中專門介紹過這個屬性,不清楚的可以去看看:autowire-candidate詳解

  5. initMethod:bean初始化的方法,這個和生命周期有關,以后詳解

  6. destroyMethod:bean銷毀的方法,也是和生命周期相關的,以后詳解

案例

User類

package com.javacode2018.lesson001.demo20;

public class User {
}

Bean配置類:ConfigBean

package com.javacode2018.lesson001.demo20;

import 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();
    }

}

上面通過@Bean注解定義了3個bean,比較簡單

來個測試類

package com.javacode2018.lesson001.demo20;

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)));
        }
    }
}

@1:通過AnnotationConfigApplicationContext來加載配置類ConfigBean,會將配置類中所有的bean注冊到spring容器中

for循環中輸出了bean名稱、別名、bean對象

運行test1方法輸出

bean名稱:org.springframework.context.annotation.internalConfigurationAnnotationProcessor,別名:[],bean對象:org.springframework.context.annotation.ConfigurationClassPostProcessor@3bd82cf5
bean名稱:org.springframework.context.annotation.internalAutowiredAnnotationProcessor,別名:[],bean對象:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@544fa968
bean名稱:org.springframework.context.annotation.internalCommonAnnotationProcessor,別名:[],bean對象:org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@247bddad
bean名稱:org.springframework.context.event.internalEventListenerProcessor,別名:[],bean對象:org.springframework.context.event.EventListenerMethodProcessor@d35dea7
bean名稱:org.springframework.context.event.internalEventListenerFactory,別名:[],bean對象:org.springframework.context.event.DefaultEventListenerFactory@7770f470
bean名稱:configBean,別名:[],bean對象:com.javacode2018.lesson001.demo20.ConfigBean$$EnhancerBySpringCGLIB$$dde45976@5e5d171f
bean名稱:user1,別名:[],bean對象:com.javacode2018.lesson001.demo20.User@24313fcc
bean名稱:user2Bean,別名:[],bean對象:com.javacode2018.lesson001.demo20.User@7d20d0b
bean名稱:user3Bean,別名:[user3BeanAlias2, user3BeanAlias1],bean對象:com.javacode2018.lesson001.demo20.User@77f1baf5

上面的輸出,我們主要關注與最后4行,前面的可以先忽略。

從輸出中可以看出,有個名稱為configBean的bean,正是ConfigBean這個類型,可以得出,被@Configuration修飾的類,也被注冊到spring容器中了

最后3行輸出就是幾個User的bean對象了。

上面的用法應該很多人都比較熟悉,下面的屬於重點了。

去掉@Configuration會怎樣?

我們來看一下沒有@Configuration的時候,什么效果。

新建一個ConfigBean1類

內容和ConfigBean類一樣,只是將@Configuration注解去掉了,如下:

public class ConfigBean1 {

    //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();
    }

}

來個測試用例test2

代碼類似於test1,給spring容器傳遞ConfigBean1

@Test
public void test2() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigBean1.class);
    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)));
    }
}

運行輸出

bean名稱:org.springframework.context.annotation.internalConfigurationAnnotationProcessor,別名:[],bean對象:org.springframework.context.annotation.ConfigurationClassPostProcessor@333291e3
bean名稱:org.springframework.context.annotation.internalAutowiredAnnotationProcessor,別名:[],bean對象:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@479d31f3
bean名稱:org.springframework.context.annotation.internalCommonAnnotationProcessor,別名:[],bean對象:org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@40ef3420
bean名稱:org.springframework.context.event.internalEventListenerProcessor,別名:[],bean對象:org.springframework.context.event.EventListenerMethodProcessor@498d318c
bean名稱:org.springframework.context.event.internalEventListenerFactory,別名:[],bean對象:org.springframework.context.event.DefaultEventListenerFactory@6e171cd7
bean名稱:configBean1,別名:[],bean對象:com.javacode2018.lesson001.demo20.ConfigBean1@402bba4f
bean名稱:user1,別名:[],bean對象:com.javacode2018.lesson001.demo20.User@795cd85e
bean名稱:user2Bean,別名:[],bean對象:com.javacode2018.lesson001.demo20.User@59fd97a8
bean名稱:user3Bean,別名:[user3BeanAlias2, user3BeanAlias1],bean對象:com.javacode2018.lesson001.demo20.User@f5ac9e4

分析結果

我們將2個輸出的最后4行拿來對比一下:

有@Configuration注解的
bean名稱:configBean,別名:[],bean對象:com.javacode2018.lesson001.demo20.ConfigBean$$EnhancerBySpringCGLIB$$dde45976@5e5d171f
bean名稱:user1,別名:[],bean對象:com.javacode2018.lesson001.demo20.User@24313fcc
bean名稱:user2Bean,別名:[],bean對象:com.javacode2018.lesson001.demo20.User@7d20d0b
bean名稱:user3Bean,別名:[user3BeanAlias2, user3BeanAlias1],bean對象:com.javacode2018.lesson001.demo20.User@77f1baf5
沒有@Configuration注解的
bean名稱:configBean1,別名:[],bean對象:com.javacode2018.lesson001.demo20.ConfigBean1@402bba4f
bean名稱:user1,別名:[],bean對象:com.javacode2018.lesson001.demo20.User@795cd85e
bean名稱:user2Bean,別名:[],bean對象:com.javacode2018.lesson001.demo20.User@59fd97a8
bean名稱:user3Bean,別名:[user3BeanAlias2, user3BeanAlias1],bean對象:com.javacode2018.lesson001.demo20.User@f5ac9e4
對比得出
  1. 對比最后3行,可以看出:有沒有@Configuration注解,@Bean都會起效,都會將@Bean修飾的方法作為bean注冊到容器中

  2. 兩個內容的第一行有點不一樣,被@Configuration修飾的bean最后輸出的時候帶有EnhancerBySpringCGLIB的字樣,而沒有@Configuration注解的bean沒有Cglib的字樣;有EnhancerBySpringCGLIB字樣的說明這個bean被cglib處理過的,變成了一個代理對象。

目前為止我們還是看不出二者本質上的區別,繼續向下看。

@Configuration加不加到底區別在哪?

通常情況下,bean之間是有依賴關系的,我們來創建個有依賴關系的bean,通過這個案例你就可以看出根本的區別了。

再來一個加@Configuration的案例

定義2個類。

ServiceA

package com.javacode2018.lesson001.demo20;

public class ServiceA {
}

ServiceB

package com.javacode2018.lesson001.demo20;

public class ServiceB {
    private ServiceA serviceA;

    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }

    @Override
    public String toString() {
        return "ServiceB{" +
                "serviceA=" + serviceA +
                '}';
    }
}

上面定義了2個類,ServiceB依賴於ServiceA,ServiceB通過構造器注入ServiceA。

來個@Configuration類管理上面對象。

ConfigBean2

package com.javacode2018.lesson001.demo20;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ConfigBean2 {

    @Bean
    public ServiceA serviceA() {
        System.out.println("調用serviceA()方法"); //@0
        return new ServiceA();
    }

    @Bean
    ServiceB serviceB1() {
        System.out.println("調用serviceB1()方法");
        ServiceA serviceA = this.serviceA(); //@1
        return new ServiceB(serviceA);
    }

    @Bean
    ServiceB serviceB2() {
        System.out.println("調用serviceB2()方法");
        ServiceA serviceA = this.serviceA(); //@2
        return new ServiceB(serviceA);
    }

}

上面通過@Bean注解,向容器中注冊了3個bean

注意@1和@2,通過this.serviceA()獲取需要注入的ServiceA對象。

上面每個方法第一行都輸出了一行日志。

重點關注一下@0這行日志會輸出幾次,大家先思考一下1次還是3次?

測試用例

@Test
public void test3() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigBean2.class);
    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}

分析結果

從輸出中可以看出

  1. 前三行可以看出,被@Bean修飾的方法都只被調用了一次,這個很關鍵

  2. 最后三行中可以看出都是同一個ServiceA對象,都是`ServiceA@41a2befb`這個實例

這是為什么?

被@Configuration修飾的類,spring容器中會通過cglib給這個類創建一個代理,代理會攔截所有被@Bean修飾的方法,默認情況(bean為單例)下確保這些方法只被調用一次,從而確保這些bean是同一個bean,即單例的。

至於底層是如何實現的,可以去看一下公眾號里面這篇文章:詳解java中的動態代理和cglib代理

現在各位應該感受到了,我寫的文章前后一般都是有依賴的,所以也建議大家按順序看,這樣知識就是貫通的。

不加的案例

我們再來看看將ConfigBean2上的的@Configuration去掉,效果如何,代碼就不寫了,直接上輸出結果:

調用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}

結果分析

  1. serviceA()方法被調用了3次

  2. configBean2這個bean沒有代理效果了

  3. 最后3行可以看出,幾個ServiceA對象都是不一樣的

spring這塊的源碼

spring中用下面這個類處理@Configuration這個注解:

org.springframework.context.annotation.ConfigurationClassPostProcessor

這里面重點關注這幾個方法:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) 
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) 

最后一個方法會創建cglib代理,大家可以設置斷點進去看看,有問題歡迎交流。

總結

  1. 到目前為止加不加@Configuration注解,有什么區別,大家估計比我都清楚了

  2. @Configuration注解修飾的類,會被spring通過cglib做增強處理,通過cglib會生成一個代理對象,代理會攔截所有被@Bean注解修飾的方法,可以確保一些bean是單例的

  3. 不管@Bean所在的類上是否有@Configuration注解,都可以將@Bean修飾的方法作為一個bean注冊到spring容器中

案例源碼

鏈接:https://pan.baidu.com/s/1p6rcfKOeWQIVkuhVybzZmQ 
提取碼:zr99

來源:https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648934137&idx=1&sn=3775d5d7a23c43616d1274b0b52a9f99&chksm=88621ec7bf1597d1b16d91cfb28e63bef485f10883c7ca30d09838667f65e3d214b9e1cebd47&token=1372043037&lang=zh_CN&scene=21#wechat_redirect


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM