天天看點

007-Spring IoC 基于Java程式設計的配置@Bean和@ConfigurationAnnotationConfigApplicationContext使用自動元件掃描AnnotationConfigWebApplicationContext@Bean的工作機制

@Bean和@Configuration

@Configuration類似于@Component,它标明目前類時一個配置類,用于配置bean。

@Bean對應xml配置中的<bean/>标簽,擁有<bean/>标簽所有的屬性,如果init-method等屬性

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceOne();
    }
}
           

AppConfig類等價于如下配置

<beans>
    <bean id="myService" class="com.acme.services.MyServiceOne"/>
</beans>
           
MyServiceOne是我們上一節中定義的MyService接口的一個實作類

AnnotationConfigApplicationContext

此節之前我們都是使用的ClassPathXmlApplicationContext讀取的xml配置檔案進行的容器初始化。即便我們使用了基于注解的配置,也需要配置xml标簽自動掃描包等。有了AnnotationConfigApplicationContext之後,我們就可以不再使用xml配置了。

package com.yyoo.boot.config;

import com.yyoo.boot.annotation.MyService;
import com.yyoo.boot.annotation.MyServiceImplOne;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppCofig1 {

    @Bean
    public MyService getMyService(){
        return new MyServiceImplOne();
    }

}

           
import com.yyoo.boot.config.AppCofig1;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo9 {

    @Test
    public void test(){

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppCofig1.class);

        MyService myService = context.getBean(MyService.class);
        System.out.println(myService);
        myService.print();

    }

}
           

使用AnnotationConfigApplicationContext的register方法

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
           

使用自動元件掃描

package com.yyoo.boot.config;

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

@Configuration
@ComponentScan({"com.yyoo.boot"})
public class AppConfig2 {



}

           
我們在AppConfig2中沒有定義Bean,通過scan掃描了com.yyoo.boot包下的所有bean,我們之前章節的代碼中有MyService以及它的兩個實作類都使用了@Service注解标注,在scan掃描的時候會将他們掃描到容器,容器可以直接使用。
package com.yyoo.boot.annotation;

import com.yyoo.boot.config.AppConfig2;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo10 {

    @Test
    public void test(){

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig2.class);

        MyService myService = (MyService)context.getBean("myServiceImplOne");
        System.out.println(myService);
        myService.print();

    }

}

           
我們的示例getBean的時候直接通過bean的名稱擷取了MyServiceOne的執行個體,如果我們直接通過type擷取的話MyService有兩個滿足條件的實作類,容器會報錯。因為我們沒有使用@Primary等注解。
如果我們不用@ComponentScan注解,也可以通過AnnotationConfigApplicationContext的scan方法定義掃描包
public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.yyoo.boot");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}
           
實際一般建議使用@ComponentScan注解定義掃描包即可。

@ComponentScan排除某些類不掃描或包含某些類需要掃描

package com.yyoo.boot.config;

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

@Configuration
@ComponentScan(value = {"com.yyoo.boot"},
        includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @ComponentScan.Filter(Repository.class))
public class AppConfig2 {



}
           

其中FilterType枚舉類中包含如下幾類

過濾器類型 示例 說明
ANNOTATION(預設類型) @ComponentScan.Filter(Repository.class) 任意的注釋類
ASSIGNABLE_TYPE 任意類的class 任意的類
ASPECTJ org.example…*Service+ 目标比對的AspectJ類型表達式
REGEX org.example.Default.* 比對的正規表達式
CUSTOM org.example.MyTypeFilter 自定義實作的org.springframework.core.type.TypeFilter接口

AnnotationConfigWebApplicationContext

在web應用中,我們使用AnnotationConfigWebApplicationContext來時效性上述功能。

<web-app>
    <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
        instead of the default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- Configuration locations must consist of one or more comma- or space-delimited
        fully-qualified @Configuration classes. Fully-qualified packages may also be
        specified for component-scanning -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Declare a Spring MVC DispatcherServlet as usual -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
            instead of the default XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- Again, config locations must consist of one or more comma- or space-delimited
            and fully-qualified @Configuration classes -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- map all requests for /app/* to the dispatcher servlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>
           
以上是官網示例,如果使用spring boot此處可以跳過。

@Bean的工作機制

我們再用之前的A、B兩個類來做示例

package com.yyoo.boot.bean;

public class A {

    public A(){
        System.out.println("A無參構造");
    }
}

           
package com.yyoo.boot.bean;

public class B {

    private A a;

    public B(){
        System.out.println("B無參構造");
    }

    public B(A a){
        this.a = a;
        System.out.println("B帶參構造");
    }

    public A getA() {
        return a;
    }

    public void setA(A a) {
        this.a = a;
        System.out.println("B的setA方法");
    }
}

           

@Bean示例程式

package com.yyoo.boot.config;

import com.yyoo.boot.bean.A;
import com.yyoo.boot.bean.B;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig2 {


    @Bean
    public A getA(){
        return new A();
    }

    @Bean
    public B getB(){
        return new B(getA());
    }

    @Bean
    public B getB1(){
        return new B(getA());
    }


}

           
package com.yyoo.boot.annotation;

import com.yyoo.boot.bean.B;
import com.yyoo.boot.config.AppConfig2;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo10 {

    @Test
    public void test(){

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig2.class);
        B b = (B)context.getBean("getB");
        B b1 = (B)context.getBean("getB1");
        System.out.println(b.getA());
        System.out.println(b1.getA());

    }

}

           

執行結果

14:53:05.809 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.spring[email protected]7a0ac6e3
14:53:05.899 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
14:53:06.243 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
14:53:06.248 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
14:53:06.252 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
14:53:06.255 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
14:53:06.275 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig2'
14:53:06.288 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getA'
A無參構造
14:53:06.312 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getB'
B帶參構造
14:53:06.313 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getB1'
B帶參構造
[email protected]
[email protected]
           
1、最直覺的結果,A的無參構造隻執行了一次。雖然我們在getB或getB1方法中都調用了方法getA,而且getA也是@Bean注解的方法,但是getA隻執行了一次。因為getA也是@Bean注解的,在執行個體化後會被緩存起來,在getB執行的時候會先查詢緩存的getA。而且兩個B類的示例中a屬性是一個執行個體。
2、我們的示例中@Bean沒有定義name屬性,那麼其定義的bean預設名稱是對應的方法名稱(注意跟@Component的預設名稱差別),也就是我們的示例中定義的bean名稱分别為:getA、getB、getB1
所有@Configuration類在啟動時都使用CGLIB. 在子類中,子方法在調用父方法并建立新執行個體之前,首先檢查容器中是否有任何緩存的(作用域)bean。從 Spring 3.2 開始,CGLIB 類已被重新打包org.springframework.cglib并直接包含在 spring-core JAR 中。是以我們不再需要單獨引入CGLIB的包。

讓我們把getA方法的@Bean注解去掉試試

14:52:07.176 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.spring[email protected]7a0ac6e3
14:52:07.228 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
14:52:07.474 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
14:52:07.478 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
14:52:07.481 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
14:52:07.484 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
14:52:07.498 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig2'
14:52:07.507 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getB'
A無參構造
B帶參構造
14:52:07.527 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getB1'
A無參構造
B帶參構造
[email protected]
[email protected]
           
A的無參構造調用了兩次。因為我們沒有@Bean注解getA方法,那麼在getB和getB1方法中調用該方法就成了普通的方法調用,是以A被調用了兩次而且對應的getB和getB1的a屬性不是同一個執行個體。

以上示例的另一種寫法

package com.yyoo.boot.config;

import com.yyoo.boot.bean.A;
import com.yyoo.boot.bean.B;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig2 {


    @Bean
    public A getA(){
        return new A();
    }

    @Bean
    public B getB(A a){
        return new B(a);
    }

    @Bean
    public B getB1(A a){
        return new B(a);
    }


}

           
這樣Spring會為兩個getB方法注入getA執行個體

如果我們有多個A執行個體怎麼辦?

package com.yyoo.boot.config;

import com.yyoo.boot.bean.A;
import com.yyoo.boot.bean.B;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig2 {


    @Bean
    public A getA(){
        return new A();
    }

    @Bean
    public A getA1(){
        return new A();
    }

    @Bean
    public B getB(A a){
        return new B(a);
    }

    @Bean
    public B getB1(A a){
        return new B(a);
    }


}

           
以上寫法會報錯,因為在getB方法上對應的A參數執行個體,Spring在注入時不知道該注入getA還是getA1,此時我們需要借助@Qualifier注解
package com.yyoo.boot.config;

import com.yyoo.boot.bean.A;
import com.yyoo.boot.bean.B;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig2 {


    @Bean
    public A getA(){
        return new A();
    }

    @Bean
    public A getA1(){
        return new A();
    }

    @Bean
    public B getB(@Qualifier("getA")A a){
        return new B(a);
    }

    @Bean
    public B getB1(@Qualifier("getA1")A a){
        return new B(a);
    }


}

           

如果我們的getA是prototype而不是單例的會是什麼結果

package com.yyoo.boot.config;

import com.yyoo.boot.bean.A;
import com.yyoo.boot.bean.B;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class AppConfig2 {


    @Bean
    @Scope("prototype")
    public A getA(){
        return new A();
    }


    @Bean
    public B getB(A a){
        return new B(a);
    }

    @Bean
    public B getB1(A a){
        return new B(a);
    }


}

           
package com.yyoo.boot.annotation;

import com.yyoo.boot.bean.B;
import com.yyoo.boot.config.AppConfig2;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo10 {

    @Test
    public void test(){

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig2.class);
        B b = (B)context.getBean("getB");
        B b1 = (B)context.getBean("getB1");
        System.out.println(b.getA());
        System.out.println(b1.getA());

    }

}

           

結果如下

15:17:57.051 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.spring[email protected]7a0ac6e3
15:17:57.092 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
15:17:57.364 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
15:17:57.371 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
15:17:57.374 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
15:17:57.377 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
15:17:57.394 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig2'
15:17:57.406 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getB'
A無參構造
15:17:57.438 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'getB' via factory method to bean named 'getA'
B帶參構造
15:17:57.441 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getB1'
A無參構造
15:17:57.441 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'getB1' via factory method to bean named 'getA'
B帶參構造
[email protected]
[email protected]
           

上一篇:006-Spring IoC 基于注解配置

下一篇:008-Spring Ioc 環境與配置