IoC容器12——基於Java的容器配置

1 基本概念:@Bean和@Configuration

Spring新的Java配置支持的核心組件是@Configuration註釋的類和@Bean註釋的方法。web

@Bean註解被用於代表一個方法實例化、配置和初始化一個被Spring IoC容器管理的新對象。相似於Spring的<beans/>XML配置,@Bean註解扮演與<bean/>相同的角色。可使用@Bean註解的方法在任何Spring組件中,然而,它們更經常使用於@Configuration註釋的bean。spring

使用@Configuration註解一個類代表它的主要目的是做爲bean定義的一個源。此外,@Configuration類容許經過簡單的在同一個類中調用其它@Bean方法來定義bean間的依賴關係。最簡單的@Configuration類以下所示:sql

@Configuration
public class AppConfig {

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

}

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

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

完整的@Configuration vs ‘精簡‘ @Bean模式數組

當@Bean方法在未註釋@Configuration的類中聲明,它們被‘精簡’模式進行處理。例如,在@Component類中或在普通舊類中聲明的bean方法被做爲精簡模式。緩存

不像完整的@Configuration,精簡的@Bean方法不能輕鬆的聲明bean間的依賴。在精簡模式操做中,一般一個@Bean方法不該該調用另外一個@Bean方法。服務器

只有在@Configuration類中使用@Bean方法是確保老是使用完整模式推薦的方法。這將防止相同的@Bean方法意外的被屢次調用而且有助於減小在「精簡」模式下操做時難以追逐的微妙錯誤。app

2 使用AnnotationConfigApplicationContext實例化Spring容器

下面的部分介紹了Spring的AnnotationConfigApplicationContext,在Spring 3.0中是新增的。這個多功能的ApplicationContext實現不只能夠接受@Configuration類做爲輸入,還能夠接受普通的@Component類和JSR-330元數據註釋的類。async

當@Configuration類被做爲輸入提供它自己被註冊爲一個bean定義,而且類中全部聲明的@Bean方法也被註冊爲bean定義。ide

當@Component和JSR-330類被提供,它們被註冊爲bean定義,而且假設在必要時這些類中使用注入@Autowired或@Inject之類的依賴注入元數據。

簡單的構造

與使用Spring XML文件做爲實例化ClassPathXmlApplicationContext時的輸入方式大體形同,@Configuration類能夠做爲實例化AnnotationConfigApplicationContext的輸入。這容許徹底無XML的使用Spring容器:

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註解類能夠做爲構造函數的輸入。例如:

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 mian(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  {
    ...
}

等價的XML聲明是使用Spring的context:命名空間:

<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 listener、Spring MVC DispathcerServlet等組件時使用這個實現。下面是配置典型的Spring MVC Web應用程序的web.xml片斷。注意contextClass的context-param和init-param的使用方法:

<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>

3 使用@Bean註解

@Bean是方法級的註解而且是XML <bean/>元素的直接模擬。註解支持<bean/>提供的一些屬性,例如init-method, destory-method, autowiring和name。

能夠在@Configuration和@Component註釋的類中使用@Bean註解。

聲明一個bean

爲了聲明一個bean,只要用@Bean註釋一個方法便可。使用這個方法在容器中註冊一個bean定義,由方法的返回值指定bean的類型。默認的,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,與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和@PreDestory註解。

常規的Spring生命週期回調也被徹底支持。若是一個bean實現了InitializingBean、DisposableBean或者Lifecycle,它們各自的方法會被容器調用。

標準的*Aware接口集合例如BeanFactoryAware、BeanNameAware、MessageSourceAware、ApplicationContextAware等等也被全面支持。

@Bean註解支持指定任意初始化和銷燬回調函數,與Spring XML <bean>元素的init-method和destory-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配置的bean定義擁有的公有的close或者shutdown方法自動被視做銷燬回調。若是類中有公有的close或shutdown方法而且不想讓其在容器關閉時被調用,將@Bean("destoryMethod="")加入bean定義便可關閉默認(inferred)的模式。

默認狀況下,可能在經過JNDI獲取資源時作上面的處理,由於其生命週期在應用程序以外管理。特別的請確保始終爲DataSource執行上面的操做,由於它已知在Java EE應用程序服務器上有問題。

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

另外,使用@Bean方法一般會選擇使用編程式的JNDI查找:或者使用Spring的JndiTemplate/IndiLocatorDelegate助手或者直接使用JNDI InitialContext,可是不要使用JndiObjectFactoryBean的變體,它強制你將返回類型聲明爲FactoryBean類型而不是實際的目標類型,使其在其它@Bean方法中更難於爲了提供資源進行交叉引用調用。

固然在上面Foo的例子中,等價於在構造時直接調用init()方法:

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

    // ...

}

當使用Java配置,可使用對象作任何想作的事情,不須要依賴於容器的生命週期。

指定bean做用域

使用@Scope註解

能夠爲使用@Bean註解的bean定義指定做用域。可使用任何標準的做用域。

默認的做用域是singleton,可是可使用@Scope註解覆蓋這個值:

@Configuration
public class MyConfiguration {

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

}

@Scope和做用域代理

Spring提供一個便捷的方式經過做用於代理來使用做用域依賴。使用XML配置建立這樣一個代理的最簡單方式是aop:scoped-proxy/元素。使用@Scope註解在Java中定義bean經過proxyMode屬性提供了一個等價的支持。默認的值是沒有代理(ScopedProxyMode.NO),可是能夠指定ScopedProxyMode.TARGET_CLASS或ScopedProxyMode.INTERFACES。

若是想做用域代理的例子從XML參考文檔移植到使用Java的@Bean配置中,應是以下的形式:

// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
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;
}

(@SessionScope的proxyMode()的默認值是TARGET_CLASS)

自定義bean命名

默認的,配置類使用@Bean方法的名字做爲結果bean的名字。這個功能可使用name屬性被覆蓋。

@Configuration
public class AppConfig {

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

}

Bean別名

有些時候須要賦給一個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...
    }

}

Bean描述

有時提供一個更詳細的文本描述是有用的。當bean被暴露(可能經過JMX)進行監視時,會頗有用。

使用@Description註解給@Bean體檢一個描述:

@Configuration
public class AppConfig {

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

}

4 使用@Configuration註解

@Configuration是一個類級別的註解表示對象是bean定義的源。@Configuration類經過@Bean註釋的公有方法聲明bean。調用@Configuration類的@Bean方法能夠用於定義bean之間的依賴關係。

注入bean之間的依賴

當@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之間的依賴關係。

查找方法注入

如前所述,查找方法注入是一種應該不多使用的高級功能。在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的配置如何工做的信息

下面的例子展現了一個@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中,默認將bean實例化爲singleton。這就是神奇的地方:全部@Configuration類在啓動時經過CGLIB被繼承。在這個子類中,方法在調用父類方法並建立新的實例以前首先檢查容器中的緩存bean。注意,從Spring3.2開始不須要將CGLIB添加到類路徑由於CGLIB類被從新打包到org.springframework.cglib而且直接包含域spring-core JAR包。

其它做用域的bean的行爲是不一樣的。這裏討論的是singleton做用域。

因爲CGLIB在啓動時動態添加功能,所以有一些限制,特別是配置類不能是final。然而,從4.3開始,在配置類中容許任何構造函數,包括使用@Autowired或者只有一個非默認的用於默認注入的構造函數。

若是想要避免任何CGLIB帶來的限制,能夠在非@Configuration類中聲明@Bean方法,例如在普通@Component類中。@Bean方法之間的交叉調用不會被攔截,因此不得不明確的依靠構造函數或者方法級別的依賴注入。

5 組合基於Java的配置

使用@Import註解

與在Spring XML中使用<import/>元素來幫助模塊化配置很是類似,@Import註解容許從另外一個配置類加載@Bean定義:

@Configuration
public class ConfigA {

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

}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }

}

如今,在初始化上下文時不須要同時指定ConfigA.class和ConfigB.class,只須要明確提供ConfigB:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

這種方法簡化了容器初始化,由於僅有一個類須要被處理,而不用開發者在構造時記住大量的@Configuration類。

從Spring Framework 4.2開始,@Import也能夠支持應用常規的組件類,相似於AnnotationConfigApplicationContext.register方法。若是想避免組件掃描,而是使用一些配置類做爲明確的定義全部組件的入口,是十分有用的。

在被導入的@Bean定義中注入依賴關係

上面的例子很簡單。在更實際的場景中,bean會有跨配置類的依賴關係。當使用XML,這自己不是問題,由於沒有發生編譯,而且能夠簡單的聲明ref="someBean"而後信任Spring會早初始化時注入。固然,在使用@Configuration類時,Java編譯器在配置模型中加入一些約束,對其它bean的引用必須是Java語法有效的。

幸運的事,解決這個問題很簡單。@Bean方法能夠任意數量的參數來描述bean的依賴關係。考慮一個更加實際的場景,幾個@Configuration類,每一個都有bean依賴於其它類中定義的bean。

@Configuration
public class ServiceConfig {

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

}

@Configuration
public class RepositoryConfig {

    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        return new JdbcAccountRepository(dataSource);
    }

}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

還有另外一種方法得到相同的接口。記住@Configuration類最終僅僅是容器中的另外一個bean:這意味着它們能夠向任何其它bean同樣使用@Autowired和@Value注入等。

要保證你只經過最簡單的方式注入依賴關係。@Configuration類在上下文初始化時很早的被處理而且經過這種方法強制注入的依賴關係可能會致使不指望的過早實例化。只要容許,任什麼時候候都應使用上面例子中的給予參數的注入。

同時,經過@Bean使用BeanPostProcessor和BeanFactoryPostProcessor時要十分當心。它們應該總被聲明爲靜態@Bean方法,不與容器配置類的實例化綁定。然而,@Autowired和@Value不會在配置類自己工做由於它會被過早的建立爲一個bean實例。

@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository;

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

}

@Configuration
public class RepositoryConfig {

    private final DataSource dataSource;

    @Autowired
    public RepositoryConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

在@Configuration類中的構造函數注入,僅從Spring Framework 4.3開始支持。請注意,若是目標bean僅定義了一個構造函數,不須要指定@Autowired;在上面的例子中,@Autowired不須要在RepositoryConfig的構造函數上註釋。

在上面的場景中,使用@Autowired工做良好而且提供了所需的模塊性,可是明確的肯定被自動裝配的bean定義在何處仍有必定的不肯定性。例如,若是一個開發者查看ServiceCOnfig,你如何明確的知道@Autowired AccountRepository bean在何處聲明?這在代碼中不明確,這可能很好。由於Spring工具套件提供繪製圖的工具,展現了被裝配的每一個組件。同時,Java IDE也能輕鬆的找到全部依賴和AccountRepository類型的使用之處,同時會很快的展現返回那個類型的@Bean方法的位置。

在這種歧義不被接受的狀況下而且但願在IDE中直接從一個@Configuration類導航到另外一個,考慮自動裝配配置類自己:

@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        // navigate 'through' the config class to the @Bean method!
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }

}

在上述狀況中,明確指出了AccountRepository定義的地方。可是,ServiceConfig如今與RepositoryConfig耦合在一塊兒了;這是交易。緊耦合能夠在必定程度上獲得緩解,經過使用基於接口或者基於抽象類的@Configuration類。考慮下面的例子:

@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

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

@Configuration
public interface RepositoryConfig {

    @Bean
    AccountRepository accountRepository();

}

@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(...);
    }

}

@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config!
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

如今ServiceConfig與具體的DefaultRepositoryConfig送耦合,而且在IDE工具中構建仍然有用:開發者能夠輕鬆的獲取RepositoryConfig實現的類型結構。使用這種方法,導航@Configuration類和它們的依賴關係變得與通常基於接口的代碼導航沒有區別。

有條件的包含@Configuration類或@Bean方法

有條件的開啓或關閉一個完整的@Configuration類或單獨的@Bean方法時有用的,基於某種任意系統狀態。一個常見的例子是盡在Spring環境啓用特定配置時使用@Profile註解來激活bean。

@Profile註解實際上時使用更加令過的註解@Conditional來實現的。@Conditional註解指定在@Bean註冊以前應該參考的特定的org.springframework.context.annotation.Condition實現。

實現Condition接口僅須要提供一個matches()方法,它返回true或false。例如,這裏有一個用於@Profile的Condition實現:

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    if (context.getEnvironment() != null) {
        // Read the @Profile annotation attributes
        MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
        if (attrs != null) {
            for (Object value : attrs.get("value")) {
                if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
                    return true;
                }
            }
            return false;
        }
    }
    return true;
}

組合Java和XML配置

Spring的@Configuration類支持並不旨在成爲Spring XML的100%替代品。一些工具如Spring XML命名空間還是配置容器的理想方式。在一些狀況下,XML更方便或必須,須要作一個選擇:或者使用XML爲中心的方法初始化容器,例如ClassPathXmlApplicationContext,或者使用Java爲中心的AnnotationConfigApplicationContext初始化方法和使用@ImportResoure註解來導入須要的XML。

XML爲中心的配置使用@Configuration類

也許從XML引導Spring容器,並以專用的方式包含@Configuration類時最好的方式。例如,在一個使用Spring XML的大型現有代碼庫中,根據須要建立@Configuration類並將它們包含到現有的XML文件會更容易。下面會展現在這種XML爲中心的狀況下使用@Configuration類的方法。

記住@Configuration類最終僅僅是容器中的bean定義。在這個例子中,咱們建立名爲AppConfig的@Configuration類而且在system-test-config.xml做爲一個bean定義包含它。由於開啓了context:annotation-config/,容器會識別@Configuration註解而且正確處理在AppConfig中聲明的@Bean方法。

@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

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

}

system-test-config.xml:

<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="com.acme.AppConfig"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

jdbc.properties:

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

在上面的system-test-config.xml中,AppConfig <bean/>元素沒有聲明id屬性。這是能夠接受的,應爲沒有其它bean會引用它,而且它不也不會經過名字從容器中獲取。一樣的是DataSource bean——它僅僅會經過類型裝配,因此一個明確的bean id不嚴格須要。

由於@Configuration使用了元註解@Component,被@Configuration註釋的類自動成爲組件掃描的候選者。與上面場景類似的,能夠從新定義syste-test-config.xml來使用組件掃描。主要這種狀況下,不須要明確的聲明context:annotation-config/,由於context:component-scan/開啓了相同的功能。

system-test-config.xml:

<beans>
    <!-- picks up and registers AppConfig as a bean definition -->
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

@Configuration爲中心的配置中使用@ImportResource導入XML

在@Configuration類爲主要配置機制的應用中,仍然須要使用一些XML。在這種場景下,使用@ImportResource而且定義須要的XML便可。這樣作實現了以「以Java爲中心」的方式來配置容器,並將XML保持在最低限度。

@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }

}
properties-config.xml
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}
相關文章
相關標籤/搜索