Spring注解之@Configration結合@Bean定義配置類


 Spring注解之@Configration定義配置類

  • 之前我們都是通過xml的方式定義bean,里面會寫很多bean元素,然后spring啟動的時候,就會讀取bean xml配置文件,然后解析這些配置,然后會將這些bean注冊到spring容器中,供使用者使用。
  • jdk1.5里面有了注解的功能,spring也沒閑着,覺得注解挺好用的,就將注解加了進來,讓我們通過注解的方式來定義bean,用起來能達到xml中定義bean一樣的效果,並且更簡潔一些,這里面需要用到的注解就有@Configuration注解和@Bean注解。

Spring @Configuration注解

Spring3.0開始,@Configuration用於定義配置類,定義的配置類可以替換xml文件一般和@Bean注解聯合使用

@Configuration注解主要標注在某個上,相當於xml配置文件中的<beans>

@Bean注解主要標注在某個方法上,相當於xml配置文件中的<bean>

1、@Configuration注解

1.用法

注意:@Configuration注解的配置類有如下要求:

  1. @Configuration必須是非本地的(即不能將配置類定義在其他類的方法內部,不能是private)。
  2. @Configuration必須有一個無參構造函數。
  3. @Configuration不可以是final類型(沒法動態代理);
  4. @Configuration不可以是匿名類;
  5. 嵌套的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注解了

總結一下

  1. @Configuration使用步驟:
  2. 在類上使用@Configuration注解
  3. 通過AnnotationConfigApplicationContext容器來加@Configuration注解修飾的類

2、@Bean注解

1.用法

這個注解類似於bean xml配置文件中的bean元素,用來在spring容器中注冊一個bean。

  1. @Bean默認是單例模式,並且沒有提供指定作用域的屬性,可以通過@Scope來實現該功能。
  2. @Bean注解用在方法上,表示通過方法來定義一個bean,默認將方法名稱作為bean名稱,將方法返回值作為bean對象,注冊到spring容器中。

如:

@Bean
public User user1() {
    return new User();
}

2.舉幾個簡單例子回顧下,XML跟config配置方式的區別

表達形式層面

基於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-3.0.xsd"
    default-lazy-init="true">
  <!--bean定義-->
</beans>

而基於JavaConfig的配置方式是這樣:

@Configuration
public class MockConfiguration{
  //bean定義
}

任何一個標注了@Configuration的Java類定義都是一個JavaConfig配置類。

注冊bean定義層面

基於XML的配置形式是這樣:

<bean id="mockService" class="..MockServiceImpl">
  ...
</bean>

而基於JavaConfig的配置形式是這樣的:

@Configuration
public class MockConfiguration{
  @Bean
  public MockService mockService(){
    return new MockServiceImpl();
  }
}

任何一個標注了@Bean的方法,其返回值將作為一個bean定義注冊到Spring的IoC容器,方法名將默認成該bean定義的id。

表達依賴注入關系層面

為了表達bean與bean之間的依賴關系,在XML形式中一般是這樣:

<bean id="mockService" class="..MockServiceImpl">
  <propery name ="dependencyService" ref="dependencyService" />
</bean>

<bean id="dependencyService" class="DependencyServiceImpl"></bean>

而基於JavaConfig的配置形式是這樣的:

@Configuration
public class MockConfiguration{
  @Bean
  public MockService mockService(){
    return new MockServiceImpl(dependencyService());
  }
  @Bean
  public DependencyService dependencyService(){
    return new DependencyServiceImpl();
  }
}

如果一個bean的定義依賴其他bean,則直接調用對應的JavaConfig類中依賴bean的創建方法就可以了。

圖片例子

等價於

3.案例

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

通過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對象了。

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

3、去掉@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處理過的,變成了一個代理對象

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

4、@Configuration加不加到底區別在哪

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

1.再來一個加@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,即單例的。

2.不加的案例

我們再來看看將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}

3.結果分析

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

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

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

參考:

https://mp.weixin.qq.com/s/1Ym5gIRMvhJ0nCJlNjqfOg

https://www.jb51.net/article/184822.htm

 


免責聲明!

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



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