Spring Java-based容器配置

多年以來,Spring大量的XML配置及複雜的依賴管理飽受非議。html

爲了實現免XML的開發體驗。Spring加入了新的配置註解以支持Java Config開發模式,當中最重要的註解就是@Configuration和@Bean。java

基本概念:@Bean和@Configuration

在Spring新的Java-configuration支持中,最核心的部分就是使用@Configuration註解的類和使用@Bean註解的類。git

@Bean註解用於指示一個方法實例化。配置,初始化一個新的被Spring容器管理的對象。github

對於熟悉Spring <beans/> XML配置的人來講,@Bean註解跟<bean/>元素做用一樣。你可以在不論什麼Spring @Component中使用@Bean註解的方法。只是。它們一般和@Configuration註解的beans一塊使用。web

使用@Configuration註解一個類意味着它的主要目的是做爲bean定義的來源。spring

此外。@Configuration類贊成bean之間(inter-bean)的依賴,你僅僅需簡單地調用該類中其它的@Bean方法。
演示樣例:編程

@Configuration
public class AppConfig {

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

}

上面的AppConfig類等價於如下的Spring <bean/> XML:api

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
  • Full @Configuration VS ‘lite’ @Beans模式

當@Bean方法聲明在沒有被@Conguration註解的類裏,這就是所謂的以’精簡’模式處理。數組

好比,在一個@Component中,甚至在一個普通的類中聲明的bean方法都會以’精簡’處理。緩存


跟完整@Configuration不一樣的是,精簡@Bean方法難以聲明bean之間的依賴。一般,在精簡模式中操做時,不該該在一個@Bean方法中調用還有一個@Bean方法。

一種推薦的方式是僅僅在@Configuration類中使用@Bean方法,這樣可以確保老是使用’完整’模式,避免@Bean方法意外地被調用屢次,下降那些在精簡模式下產生的很是難跟蹤的微妙bugs。

使用AnnotationConfigApplicationContext實例化Spring容器

AnnotationConfigApplicationContext是在Spring 3.0中新增的。

這個多功能的ApplicationContext實現就能夠接收@Configuration類做爲輸入。也可接收普通的@Component類。及使用JSR-330元數據註解的類。

當將@Configuration類做爲輸入時,@Configuration類自己被註冊爲一個bean定義,並且該類中所有聲明的@Bean方法也被註冊爲bean定義。

當將@Component和JSR-330類做爲輸入時。它們被註冊爲bean定義,並且在需要的地方使用DI元數據。比方@Autowired或@Inject。

  • 構造器實例化

跟實例化一個ClassPathXmlApplicationContext時將Spring XML文件用做輸入類似,在實例化一個AnnotationConfigApplicationContext時可以使用@Configuration類做爲輸入。這就贊成Spring容器全然零XML配置:

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

如上所述,AnnotationConfigApplicationContext不侷限於僅僅使用@Configuration類。不論什麼@Component或JSR-330註解的類都可以做爲AnnotationConfigApplicationContext構造器的輸入。好比:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

上面假設MyServiceImpl,Dependency1和Dependency2使用Spring依賴注入註解。比方@Autowired。

*register(Class<?

>…​)實例化

可以使用無參的構造器實例化AnnotationConfigApplicationContext,而後使用register()方法對容器進行配置。這樣的方式在以編程方式構造一個AnnotationConfigApplicationContext時很是實用。

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();
}
  • 啓用scan(String…​)的組件掃描

想要啓用組件掃描,僅僅需按例如如下方式註解你的@Configuration類:

@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig {
    ...
}

注: 有經驗的Spring用戶會比較熟悉來自Springcontext:命名空間的等效XML聲明:

<beans>
    <context:component-scan base-package="com.acme"/>
</beans>

在上面的演示樣例中。將會掃描com.acme包。查找不論什麼被@Component註解的類,並且這些類將被註冊爲容器裏的Spring bean定義。AnnotationConfigApplicationContext暴露scan(String…​)方法來實現一樣的容器掃描功能:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}

注: 記着@Configuration類是被@Component元註解的,因此它們也是組件掃描的候選者。在上面的演示樣例中。假設AppConfig定義在com.acme包(或不論什麼下層包)中,在調用scan()期間它也會被掃描。當refresh()時它的所有@Bean方法將被處理,並註冊爲容器裏的bean定義。

  • 使用AnnotationConfigWebApplicationContext支持web應用

AnnotationConfigWebApplicationContext是AnnotationConfigApplicationContext的WebApplicationContext變種。當配置Spring ContextLoaderListener servlet監聽器。Spring MVC DispatcherServlet等會用到。如下的web.xml片斷配置了一個典型的Spring MVC web應用。

注意contextClass的context-param和init-param的使用:

<web-app>
    <!-- 配置ContextLoaderListener使用 AnnotationConfigWebApplicationContext取代默認的XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
        org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- Configuration位置必須包括一個或多個逗號或空格分隔的全限定 @Configuration類.組件掃描中要指定該全限定包.-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <!-- 像尋常那樣使用ContextLoaderListener啓動根應用上下文 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!--像尋常那樣聲明一個Spring MVC DispatcherServlet -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 配置DispatcherServlet使用AnnotationConfigWebApplicationContext取代默認的XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- 再次。配置路徑必須包括一個或多個逗號,或空格分隔的全限定@Configuration類 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- 將/app/*的所有請求映射到該dispatcher servlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

@Bean註解使用方法

@Bean是一個方法級別的註解。XML<bean/>元素的等價物。該註解提供一些<bean/>提供的元素,好比init-method, destroy-method, autowiringname
你可以在一個@Configuration註解或@Component註解的類中使用@Bean註解。

  • 聲明一個bean

想要聲明一個bean,僅僅需簡單的使用@Bean註解一個方法。你可以使用該方法註冊一個bean定義到ApplicationContext中。類型經過方法的返回值指定。默認狀況下,bean的名稱和方法名同樣。如下是一個簡單的@Bean方法聲明演示樣例:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }

}

上述的配置等價於如下的Spring XML:

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

兩個聲明都在ApplicationContext中建立了一個名稱爲transferService 的bean,該bean都綁定到一個TransferServiceImpl類型的對象實例:transferService -> com.acme.TransferServiceImpl

  • Bean依賴

一個@Bean註解的方法可以有隨意數量的參數來描寫敘述構建該bean所需的依賴。

舉例來講,假設咱們的TransferService需要一個AccountRepository,咱們可以經過一個方法參數提供該依賴:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }

}

這樣的處理機制對於基於構造器的依賴注入很是重要,詳細參考相關章節

  • 接收生命週期回調

每個使用@Bean註解定義的類都支持常規的生命週期回調,並可以使用來自JSR-250的@PostConstruct和@PreDestroy註解,詳細參考JSR-250註解,常規的Spring生命週期回調也全支持。

假設一個bean實現InitializingBean,DisposableBean或Lifecycle。它們對應的方法會被容器調用。

標準的*Aware接口集合,比方BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware等也都所有支持。

@Bean註解支持指定隨意數量的初始化和銷燬回調方法,類似於Spring XML bean元素的init-methoddestroy-method屬性:

public class Foo {
    public void init() {
        // initialization logic
    }
}

public class Bar {
    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public Foo foo() {
        return new Foo();
    }

    @Bean(destroyMethod = "cleanup")
    public Bar bar() {
        return new Bar();
    }

}

注: 默認狀況下,使用Java config定義的beans有一個public的closeshutdown方法被本身主動註冊爲銷燬回調。假設你有一個public的closeshutdown方法。並且不想在容器關閉時調用它。你僅僅需簡單地將@Bean(destroyMethod="")加入到bean定義以此禁用默認的判斷(inferred)模式。

對於一個經過JNDI獲取的資源來講,由於它的生命週期是由server管理,而不是應用。因此你可能想默認就這樣作,特別是針對DataSource這樣的資源:

@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
    return (DataSource) jndiTemplate.lookup("MyDS");
}

固然,在上面的Foo演示樣例中。直接在構造器中調用init()方法也是有效的:

@Configuration
public class AppConfig {
    @Bean
    public Foo foo() {
        Foo foo = new Foo();
        foo.init();
        return foo;
    }

    // ...

}

注: 當直接使用Java時。你可以對你的對象作不論什麼想作的事,而不老是需要依賴於容器的生命週期。

  • 指定bean做用域

1.使用@Scope註解

你可以爲經過@Bean註解定義的bean指定一個特定的做用域。你可以使用Bean Scopes做用域中定義的不論什麼標準做用域。默認做用域爲singleton,但你可以使用@Scope做用域覆蓋它:

@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }

}

2.@Scope和scoped-proxy

Spring提供一個方便的方式來經過scoped proxies處理做用域的依賴。最簡單的方式是建立一個這樣的proxy,在使用XML配置時對應配置爲<aop:scoped-proxy/>元素。在Java中經過@Scope註解和proxyMode屬性可以達到一樣的功能。默認沒有proxy(ScopedProxyMode.NO),但你可以指定爲ScopedProxyMode.TARGET_CLASS或ScopedProxyMode.INTERFACES。
假設你想從XML參考文檔的scoped proxy演示樣例過渡到使用Java的@Bean,它看起來可能例如如下所看到的:

// an HTTP Session-scoped bean exposed as a proxy
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserPreferences userPreferences() {
    return new UserPreferences();
}

@Bean
public Service userService() {
    UserService service = new SimpleUserService();
    // a reference to the proxied userPreferences bean
    service.setUserPreferences(userPreferences());
    return service;
}

3.本身定義bean名稱

默認狀況下,配置類使用@Bean方法名做爲結果bean的名稱。

該功能可以被name屬性覆蓋。

@Configuration
public class AppConfig {

    @Bean(name = "myFoo")
    public Foo foo() {
        return new Foo();
    }

}

4.Bean別名

有時候爲單個bean起多個名稱是有必要的,@Bean註解的name屬性可以接收一個String數組來達到這個目的。

@Configuration
public class AppConfig {

    @Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }

}

5.Bean描寫敘述

有時候爲一個bean提供詳細的文本描寫敘述是很是有幫助的。特別是當beans被暴露(可能經過JMX)用於監控目的時。

可以使用@Description註解爲一個@Bean加入描寫敘述:

@Configuration
public class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    public Foo foo() {
        return new Foo();
    }

}

@Configuration註解使用方法

@Configuration註解是一個類級別的註解,它意味着該對象是一個bean定義的來源。@Configuration類經過public的@Bean註解的方法來聲明beans。調用@Configuration類上的@Bean方法可用於定義bean之間的依賴。

  • bean的依賴注入

當@Beans依賴其它bean時。僅僅需要在一個bean方法中調用還有一個bean就能夠表達這樣的依賴關係:

@Configuration
public class AppConfig {

    @Bean
    public Foo foo() {
        return new Foo(bar());
    }

    @Bean
    public Bar bar() {
        return new Bar();
    }

}

在上面的演示樣例中,foo bean經過構造器注入獲取到一個指向bar的引用。

注: 聲明bean之間依賴的方法僅僅在@Configuration類內部的@Bean方法有效,你不能使用普通的@Component類聲明bean之間的引用。

  • Lookup方法注入

正如前面提到的,lookup method injection是一個高級的特性。你應該少用。當一個singleton做用域的bean依賴一個prototype做用域的bean時該方法很是實用。

使用Java配置方式對這樣的類型的配置提供了一種天然的手段來實現該模式。

public abstract class CommandManager {
    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();

        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
    return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

使用Java配置支持,你可以建立一個CommandManager的子類,在這裏抽象的createCommand()方法以查找一個新的(prototype)命令對象來覆蓋:

@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    // inject dependencies here as required
    return command;
}

@Bean
public CommandManager commandManager() {
    // return new anonymous implementation of CommandManager with command() overridden
    // to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}
  • Java-based配置內容怎樣工做

如下的演示樣例展現了一個@Bean註解的方法被調用了兩次:

@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }

}

clientDao()clientService1()中調用了一次。而後在clientService2()中調用了一次。

由於這種方法建立了一個新的ClientDaoImpl實例,而後返回它,你可以指望有兩個實例(每個service都有一個實例)。這絕對會出問題:在Spring中。實例化的beans默認狀況下做用域爲singleton。

這就是魔法產生的地方:所有的@Configuration類在啓動期間都是被CGLIB子類化過的(代理)。在子類中。子類的方法首先檢查容器是否緩存(scoped)對應的beans。假設沒有緩存纔會調用父類的方法,建立一個新的實例。

注意在Spring3.2以後,已經不需要加入CGLIB的依賴,由於CGLIB被又一次打包到org.springframework下。並直接包括在spring-core JAR中。

注: 該行爲依賴於bean的做用域,此處咱們討論的是單例。由於CGLIB動態代理的特性這裏有一些限制:配置類不能爲final的,它們應該有一個無參構造器。

《Spring Boot參考指南》翻譯完成,歡迎各位拍磚。

相關文章
相關標籤/搜索