7.12 Java-based container configuration (基於java的容器配置)

http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#beans-java-instantiating-container-web ##7.12 Java-based container configuration (基於java的容器配置) ###7.12.1 Basic concepts:@Bean and @Configuration spring對於新的java配置的支持的核心武器是@Configuration-annotated類以及@bean-annotated方法.html

@Bean註解是來標記一個能夠實例化,配置,初始化一個新的有spring-ioc容器管理的新的對象.這裏有些像spring的xml配置中的<beans/>標籤,@Bean則至關於裏面的<bean>標籤.你能夠講@Bean標記到任何@Components類的方法上,可是,它們通常只在@Configuration的beans裏使用.java

用@Configuration代表他的主要目標是做爲bean定義的資源.另外,@Configuration類容許經過調用同一個類裏面的@Bean標註的方法定義的內部bean依賴.最簡單的@Configuration類用法以下:web

@Configuration
public class AppConfig {

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

}

上面的AppConfig類等價於下面的<beans/> xml:spring

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

####Full @Configuration vs 'lite' @Beans mode? 當 @Bean方法出如今沒有@Configuration註解標誌的類中時,它們其實是在'lite'模式下處理的.例如,在@Component類裏或一個POJO類裏定義的bean方法都認爲是'lite';sql

不一樣於full @Confuguration,lite @Bean 方法不易申明內部bean依賴.通常在'lite'模式下,一個@Bean方法不能調用其餘的@Bean方法.數組

只有使用在@Configuration類裏的@Bean方法纔是保證'full'模式常常被使用的好方法.它能夠阻止相同的@Bean方法意外的被屢次調用,並有助於減小在'lite'模式下很難被追蹤的微妙的bugs.緩存

@Bean和@Configuration註解會在之後的部分深刻討論.可是,首先讓咱們來了解使用java-based configuration來建立spring容器的幾種方式吧. ###7.12.2 Instantiating the Spring container using AnnotationConfigApplicationContext(用AnnotationConfigApplicationContext來實例化spring上下文容器) 本節中的AnnotationConfigApplicationContext,是spring3.0的新加內容.這個ApplicationContext的變種實現,不只可以接受@Configuration的類,還能夠接受日常的@Component類和JSR-330元數據標註的類.app

當@Configuration類做爲輸入類時,@Configuration類自己會註冊爲一個bean,且全部聲明的@Bean方法也會註冊爲一個bean定義.async

當@component和JSR-330類被提供時,他們會被註冊爲bean定義.而且全部的依賴注入元數據例如@Autowired或@Inject將會被使用在這些類須要的地方.ide

###簡單的構造器 同實例化一個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類.任何@Componnet和用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 and Dependency2等類使用了spring依賴注入的註解如@Autowired.

####Building the container programmatically using 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();
}

####Enabling component scanning with scan(String ..) 可用組件掃描 要保證組件掃描,只以下標誌你的@Configuration類便可:

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

有經驗的spring用戶能夠發現其用法和xml使用context命名空間類似:

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

在上面的例子中,com.acme包將被掃描,查找任意被@Component標誌的類,這些類將註冊爲spring容器裏的bean定義.

AnnotationConfigApplicationContext 暴露了scan(String..)方法,使其有component-scanning的功能.

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

記住@Configuratin類的元註解也是@Component,因此它們是組件掃描的候選者.在上面的例子中,默認AppConfig是在com.acme包裏的,在調用scan()方法它會被掃描,在refrsh()方法以後全部的@Bean方法會被容器處理並註冊爲一個bean定義. ####Support for web applications with AnnotationConfigWebApplicationContext(對web應用的支持)

AnnotationConfigApplicationContext的WebApplicationContext的版本能夠經過AnnotationConfigWebApplicationContext實現.當你配置spring的ContextLoaderListener servlet Listener,spring MVC DispatcherServlet時會很是有用.下面是一個典型的spring MVC的web應用配置的web.xml.記住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>

###7.12.3 使用@Bean註解 @Bean是一個方法級別的註解,且是xml中<bean/>元素的替代物.這個註解支持<bean/>裏的一些屬性,例如:init-method,destroy-method,autowiring ,name.

你能夠在用@Configuration或@Component標誌的類裏使用@Bean註解. ####Declaring a bean 聲明一個bean 要申明一個bean,能夠簡單的用@Bean來標註一個方法.你能夠在ApplicationContext中用這個方法的特定返回值類型來註冊一個bean定義.默認,bean的名稱同方法名相同.下面是使用@Bean方法申明的簡單例子;

@Configuration
public class AppConfig {

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

}

上面的配置等價於下面的spring xmlns

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

這兩個申明均可以在ApplicationContext中註冊transferService,限制這個實例類型爲TransferServiceImpl:

transferService->com.acme.TransferServiceImpl

####Bean dependencies (Bean的依賴) 一個@bean標記的方法能夠有任意數量的參數來描述構建這個bean所必需的依賴.例如若是咱們的TransferService須要一個AccountRepository,咱們能夠經過方法參數來實現這個依賴.

@Configuration public class AppConfig {

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

}

這個解決機制是基於構造器依賴注入的替代物,查看相關片斷已查看更多細節.

Recieving lifecycle callbacks (接受生命週期回調)

任何被@Bean註解標記的類都支持常規的生命週期回調,並可使用@PostConstruct和@PreDestroy註解.

常規的spring生命週期回調一樣支持.若是一個bean實現了InitializingBean,DisposableBean或者Lifecycle,他們的表現方法會被容器調用.

也徹底支持*Aware接口的標準的設置,如BeanFactoryAware,BeanNameAware,MessageSourceAware,ApplicationContextAware等.

這個@Bean註解支持指定的任意初始化和銷燬回調方法,同spring xml元素裏的init-mehod,destroy-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配置定義的beans都有一個公共的能夠被銷燬回調調用的close或shutdown方法.若是你有一個公共的close或shutdown方法且你不但願在容器關閉時它們被調用,簡單的在你的bean定義上加上@Bean(destroyMethos="")來屏蔽這個默認模式.你可能想這麼作,經過默認的JNDI來獲取資源但它的生命週期管理須要在應用以外.特別,DataSource須要這麼處理,由於在Java EE應用服務中它是個衆所周知的問題.

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

另外,用@Bean標註的方法,你通常會選擇使用動態JNDI查找策略:spring的JndiTemplate或JndiLocateDelegate helpers或者直接的JNDI InitialContext用法,而不是JndiObjectFactoryBean的變種.JndiObjectFactoryBean會強迫你將返回類型定義爲FactoryBean而不是實際的目標類型,結果在其餘@Bean方法中須要指定該bean提供資源時會很難交叉引用調用.

固然,在上個例子的Foo中,等同於下面在構造器中調用init()方法.

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

    // ...

}

當你直接使用java時,你能夠直接同你的對象來作任何事,且不須要依賴容器的生命週期.

###Specifying bean scope 指定bean的做用域 ####Using the @Scope annotation使用@Scope註解 你能夠用指定你的用@Bean標記的bean定義有一個特定的做用域.你可使用在Bean Scope節裏的任意標準的做用域.

默認做用域是單例(singleton),但你能夠用@Scope註解重寫該做用域

@Configuration
public class MyConfiguration {

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

}

###@Scope and scoped-proxy 做用域和做用域代理 spring經過scopes proxies提供一個合適的方式來同做用域依賴工做.最簡單建立代理這種代理的方式是使用XML配置時的aop:scoped-proxy/元素.用@Scope配置你的beans用proxyMode屬性提供了相同的支持.默認是沒有代理的(ScopedProxyMode.NO),你能夠指定爲ScopedProxyMode.TRAGET_CLASS或ScopedPrxoyMode.INTERFACES. 若是你要將scoped proxy從xml引用文檔轉爲使用java配置的Bean,你能夠以下使用:

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

####自定義bean命名 通常,配置類使用@bean方法的名字做爲該bean的名字.不過,這個功能可使用name屬性來重寫.

@Configuration
public class AppConfig {

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

}

###Bean aliasing (Bean的別名) 同7.3.1'Naming beans'討論的那樣,有時要給單個的bean多個名字,這就是Bean aliasing.@Bean註解的name屬性接受一個String數組,這樣能夠了.

@Configuration
public class AppConfig {

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

}

####Bean description bean描述 有時給bean提供詳細的文本描述很是有幫助.特別是當這些bean暴露(經過JMX)出來以用於監控時. 你能夠這樣給一個Bean添加描述

@Configuration
public class AppConfig {

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

}

###7.12.4 Using the @Configuration annotation(使用@Configuration 註解) @Configuration是一個類級別的註解來標註該對象是一個bean定義的資源.@Configuration類經過@Bean註解方法來聲明beans定義.調用@Configuration類的@Bean方法也能夠用於定義內部bean依賴.

####Injecting inner-bean dependencies(注入內置的依賴) 當一個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的引用;;

注:只有在@Configuration類使用@Bean註解申明的方法時,這種申明內部bean依賴的方法纔有效.你不能在日常的@Component類裏使用內部bean依賴.

####Lookup method injection(查找方法注入) 和之前提過的同樣,查找方法注入是一個咱們不多使用的高級功能.當一個單例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()方法,這樣它就能查找一個新的command對象.

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

貌似也要在@Configuration類中.一個單例bean有一個原生bean的引用. ####Further information about how java-based configuration works internally 基於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默認是單例做用域.這個關鍵點在於,全部的@Configuration類在啓動時都是用CGLIB代理的子類啓動的.在子類中,子方法在父方法調用和建立新實例以前首先查找容器中緩存的bean.記住在spring3.2中,你不須要額外引用CGLIB的jar包了,由於它已經在org.springframework.cglib的包下了,並收到了spring核心jar包裏了.

這個行爲會根據做用域的不一樣而差別.咱們只討論單例狀況.

由於CGLIB動態代理在啓動時須要添加功能因此這裏有些限制,特別是配置類必須不是final.可是,4.3以後,配置類中容許全部的構造器,包括使用了@Autowired的,或無參構造器來申明默認注入.

若是你更傾向於避免CGLIB強加的限制,能夠考慮在非@Configuration類裏申明@Bean方法,例如,使用@Component類替代.@Bean方法之間的交叉調用不會被攔截,全部你必須依賴構造器或方法級別的依賴注入.

###7.12.5 Composing java-based configurations 集成java配置 ####Using the @Import annotaion 就像<import/>元素使用在spring的xml文件裏的模塊化配置中使用的同樣,這個@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 4.2中,@Import還支持常規組件類,與AnnotationConfigContext.register()方法類似.當你要避免組件掃描時,使用少許的配置類做爲定義大家全部組件的關鍵點. ####Injecting dependencies on imported @Bean definitions 經過@Import來引入bean定義 上面的例子雖然很簡潔,但仍能夠其做用.在大多數實際場景裏,bean會用其餘配置文件裏的依賴.當使用xml時,這不是問題.由於這裏沒有編譯器,且能夠簡單的宣佈ref="someBean",並相信spring會在容器初始化時解決好.固然,當使用@Configuration類時,在這種配置模式下java編譯器會提出一些限制,就是這些引用的bean必須通過java語法的檢驗;

可是,解決這個問題很簡單,就想咱們以前討論的,@Bean方法能夠有任意數量的參數來描述bean的依賴.讓咱們討論一個使用@Configuration類的真實場景,每一個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");
}

spring4.3支持@COnfiguration類裏的構造器注入.記住若是目標bean只有一個構造器,那就不須要指定@Autowired的值.上面的例子,Repository的構造器無需添加@Autowired註解.

在上面的例子中,使用@Autowired做用得當,並提供了理想的模塊,但在表現注入的bean定義須要申明這方面仍有些模糊.例如,一個開發者查找ServiceConfig,你怎麼能準確的知道@Autowired AccounRepository注入的bean是哪個呢.它可能在代碼中不明確,但倒是最好的那個.Spring Tool Suite提供了一個工具,它能夠畫出全部須要注入bean的層次圖表,多是全部你須要的bean.固然,你的其餘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類和它們的依賴和通常的導航到易於接口的代碼沒有太大的差別.

Conditionally include @Configuration classes or @Bean methods

根據任意系統狀態,用條件控制使@Configuration類或單個的@bean方法有效或無效頗有用.一個日常的例子是當spring環境中只有一個特定的profile可用時,使用@Profile註解來啓動beans.

@Profile註解其實是使用更靈活的@Conditional註解實現的.這個@Conditional註解是特定的org.springframework.context.annotation.Condition的實現,代表在@Bean註冊以前應該先詢問它.

實現Condition接口提供了一個返回true或false的matches()方法.例如,這裏有一個使用@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;
}

查看@Conditional的javadoc來獲取更多內容.

####Combining java and Xml configuration 基礎java和XML配置

spring的@Configuration類的支持並不打算100%的替代spring的xml配置.一些基礎設施如spring的xml命名空間還是解決容器配置的理想方式.當xml適使或必須時,你能夠如此:能夠用XML-centric的方式實例化你的容器,例如,ClassPathXmlApplicationContext,或者用"java-centric"方式使用AnnotationConfigApplicationContext或@ImportResource註解來引入須要的XML.

####XML-centric use of @Configuration classes 以xml爲中心使用@Configuration

當你傾向於從xml裏使用spring容器並以ad-hoc的方式引入@Configuration類.例如,若是已存在大量的使用spring XML的代碼,那麼在需求上建立@Configuration類和在xml裏引入它們會更加容易.下面你會找到在"XML-centric"情景下使用@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.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屬性不是必須的.

針對以上場景,你能夠設置路徑掃描;component-scan包含annotation-scan的功能,因此不須要annotation-scan,經過設置component-scan的base-package屬性來定義;如今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 class-centric use of XML with @ImportResource 經過使用@ImportResource來引入xml文件,這個是java-centric的作法,該註解需指明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);
    // ...
}

而後咱們能夠像在xml裏同樣使用property-placeholder裏的屬性了.

相關文章
相關標籤/搜索