天天看點

SpringFramework核心技術一(IOC:基于Java的容器配置)

@Bean和@Configuration

Spring新的Java配置支援中的中心構件是 - @Configuration注釋類和@Bean注釋方法。

一、基本概念:@Bean和@Configuration

該@Bean注釋被用于訓示一個方法執行個體,配置和初始化為通過Spring IoC容器進行管理的新對象。對于那些熟悉Spring的

<beans/>

XML配置的人來說,@Bean注釋和

<bean/>

元素具有相同的作用。您可以在任何Spring中帶@Component方法中使用@Bean,但是,它們通常與@Configurationbean一起使用。

使用注釋類@Configuration表示它的主要目的是作為bean定義的來源。此外,@Configuration類允許通過簡單地調用@Bean同一類中的其他方法來定義bean間依賴關系。最簡單的@Configuration類可以讀作如下:

@Configuration
public class AppConfig {

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

AppConfig上面的類将等同于下面的Spring

<beans/>

XML:

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

二、Full @Configuration vs ‘lite’ @Bean mode?

@Bean

方法在沒有

@Configuration

注釋的類中被聲明時,它們被稱為在’精簡’模式下處理。

在一個@Component或者一個普通的舊類中聲明的Bean方法将被認為是’精簡’的,其中包含類的一個不同的主要目的和一個@Bean方法僅僅是那裡的一種獎勵。

例如,服務元件可能會通過@Bean每個适用元件類的附加方法向集裝箱公開管理視圖。在這種情況下,@Bean方法是一個簡單的通用工廠方法機制。

與完整不同@Configuration,lite @Bean方法不能聲明bean間依賴關系。

相反,他們對包含元件的内部狀态和可選的參數進行操作,它們可能會聲明。

@Bean是以這種方法不應該引用其他 @Bean方法; 每個這樣的方法實際上隻是一個特定的bean引用的工廠方法,沒有任何特殊的運作時語義。

這裡的積極副作用是,在運作時不需要應用CGLIB子類,是以在類設計方面沒有限制(即,包含的類可能是final等等)。

在常見的場景中,

@Bean

方法将在

@Configuration

類中聲明,確定始終使用“完整”模式,并且跨方法引用将重定向到容器的生命周期管理。這樣可以防止

@Bean

通過正常的Java調用意外調用相同的 方法,這有助于減少在“精簡”模式下操作時難以追蹤的細微錯誤。

三、使用AnnotationConfigApplicationContext執行個體化Spring容器

下面的部分介紹了Spring的AnnotationConfigApplicationContextSpring 3.0中的新功能。這種多功能的ApplicationContext實作不僅能夠接受@Configuration類作為輸入,還能夠接受 @Component用JSR-330中繼資料注釋的普通類和類。

當@Configuration提供類作為輸入時,@Configuration類本身被注冊為一個bean定義,并且在該類中所有@Bean聲明的方法也被注冊為bean定義。

當@Component被提供和JSR-330類,它們被登記為bean定義,并且假定DI中繼資料,例如@Autowired或者@Inject是這些類中使用的必要。

1.結構簡單

與執行個體化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 or 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

2.以程式設計的方式使用注冊方法構造容器

一個

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

3.使用

@ComponentScan

啟用元件掃描

要啟用元件掃描,隻需@Configuration按照以下步驟注釋您的班級:

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

有經驗的Spring使用者将熟悉Spring的context:命名空間中的XML聲明

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

在上面的例子中,com.acme軟體包将被掃描,尋找任何 @Component-annotated的類,并且這些類将被注冊為容器中的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定義。

4.在Web項目中使用AnnotationConfigWebApplicationContext

WebApplicationContext

變體

AnnotationConfigApplicationContext

可用

AnnotationConfigWebApplicationContext

。當配置

Spring ContextLoaderListenerservlet

偵聽器,

Spring MVC DispatcherServlet

等時,可以使用此實作。接下來是

web.xml

配置典型Spring MVC Web應用程式的片段。請注意使用

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

四、使用

@Bean

注釋

@Bean是一種方法級别的注釋和XML 元素的直接模拟。注釋支援一些由其提供的屬性

<bean/>

,例如: init-method, destroy-method, autowiring和name。

您可以在

@Configuration

@Component

注釋類中使用

@Bean

注解。

1.聲明一個bean

要聲明一個bean,隻需使用注釋對一個方法進行@Bean注釋。您可以使用此方法在ApplicationContext指定為方法傳回值的類型中注冊一個bean定義。預設情況下,bean名稱将與方法名稱相同。以下是@Bean方法聲明的一個簡單示例:

@Configuration
public class AppConfig {

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

上述配置完全等同于以下Spring XML:

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

這兩個聲明都将一個名為transferService可用 的bean ApplicationContext,綁定到一個類型為object的對象執行個體TransferServiceImpl:

transferService  - > com.acme.TransferServiceImpl           

你也可以@Bean用接口(或基類)傳回類型聲明你的方法:

@Configuration
public class AppConfig {

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

但是,這會将提前類型預測的可見性限制為指定的接口類型(TransferService),然後,TransferServiceImpl一旦受影響的單例bean被執行個體化,容器就會知道完整類型()。非懶惰的singleton bean根據它們的聲明順序得到執行個體化,是以你可能會看到不同的類型比對結果,這取決于另一個元件試圖通過非聲明類型進行比對的時間(比如@Autowired TransferServiceImpl 隻有在“transferService”bean已經被解析執行個體化)。

如果您始終通過聲明的服務接口來引用您的類型,那麼您的 @Bean傳回類型可以安全地加入該設計決策。但是,對于實作多個接口的元件或可能由其實作類型引用的元件,聲明最具體的傳回類型是可能的(至少按照注入點對bean引用的要求)是比較安全的。

2.Bean依賴關系

@Bean注解的方法可以具有描述建構豆所需要的依賴關系的參數的任意數量。例如,如果我們TransferService 需要一個,AccountRepository我們可以通過一個方法參數來實作這個依賴關系:

@Configuration
public class AppConfig {

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

解析機制與基于構造函數的依賴注入非常相似。

3.接收生命周期回調

任何使用@Bean注釋定義的類都支援正常生命周期回調,并且可以使用JSR-250中的注釋@PostConstruct和@PreDestroy注釋,請參閱 JSR-250注釋以擷取更多詳細資訊。

正常的Spring 生命周期回調也被完全支援。如果一個bean實作了InitializingBean,DisposableBean或者Lifecycle,它們各自的方法被容器調用。

*Aware諸如BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware等的标準接口也完全受支援。

該@Bean注釋支援指定任意初始化和銷毀​​回調方法,就像Spring XML init-method和元素destroy-method上的屬性一樣bean:

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

預設情況下,使用具有公共close或shutdown 方法的Java配置定義的bean 将自動列入銷毀回調。如果你有一個public close或shutdownmethod,并且你不希望在容器關閉時調用它,隻需添加@Bean(destroyMethod=”“)到你的bean定義來禁用預設(inferred)模式。

您可能希望為通過JNDI擷取的資源預設執行此操作,因為其生命周期在應用程式外部進行管理。特别是,確定始終DataSource以Java EE應用程式伺服器上的問題着稱。

@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
    return (DataSource) jndiTemplate.lookup("MyDS");
}           
此外,通過@Bean方法,您通常會選擇使用程式設計式JNDI查找:使用Spring的JndiTemplate/ JndiLocatorDelegatehelper或直接InitialContext使用JNDI ,但不會JndiObjectFactoryBean強制您将傳回類型聲明為FactoryBean類型而不是實際目标類型,很難在其他@Bean方法中用于參照所提供的資源的交叉引用調用。

當然,就上述情況而言, 在施工期間直接Foo調用該init()方法同樣有效:

@Configuration
public class AppConfig {

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

    // ...
}           

當您直接使用Java進行工作時,您可以對您的對象執行任何您喜歡的操作,并且不總是需要依賴容器生命周期!

4.指定bean作用域

使用@Scope注釋

您可以指定使用@Bean注釋定義的bean 應該具有特定的作用域。您可以使用Bean Scopes部分中指定的任何标準範圍 。

預設範圍是singleton,但您可以使用@Scope注釋覆寫它:

@Configuration
public class MyConfiguration {

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

@Scope和scoped-proxy

Spring提供了一種通過作用域代理來處理作用域依賴關系的便捷方式 。在使用XML配置時建立此類代理的最簡單方法就是元素。使用@Scope注釋在Java中配置bean提供了與proxyMode屬性等效的支援。預設值是no proxy(ScopedProxyMode.NO),但可以指定ScopedProxyMode.TARGET_CLASS或ScopedProxyMode.INTERFACES。

如果您将範圍代理示例從XML參考文檔(請參閱前面的連結)移植到我們@Bean使用的Java中,它将如下所示:

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

5.自定義bean命名

預設情況下,配置類使用@Bean方法的名稱作為結果bean的名稱。但是,可以使用該name屬性覆寫此功能。

@Configuration
public class AppConfig {

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

5.Bean别名

正如在命名bean中所讨論的,有時需要為單個bean提供多個名稱,否則稱為bean别名。 注解的name屬性@Bean為此接受一個String數組。

@Configuration
public class AppConfig {

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

6.Bean的描述

有時候提供一個更詳細的bean的文本描述是有幫助的。當bean暴露(可能通過JMX)用于監視目的時,這可能特别有用。

為了說明添加到@Bean的 @Description 注釋,可以使用:

@Configuration
public class AppConfig {

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

五、使用@Configuration注釋

@Configuration

是一個類級注釋,訓示對象是bean定義的來源。

@Configuration

類通過公共

@Bean

注釋方法聲明bean 。調用

@Bean

的方法

@Configuration

類也可以用于定義bean間的依賴關系。有關一般介紹,請參閱基本概念:

@Bean

@Configuration

1.注入bean間依賴關系

@Beans

彼此依賴時,表達該依賴性就像一個bean方法調用另一個一樣簡單:

@Configuration
public class AppConfig {

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

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

在上面的例子中,foobean接收到bar通過構造函數注入的引用。

這種聲明bean間依賴關系的@Bean方法隻有在方法在@Configuration類中聲明時才有效。您不能使用普通@Component類聲明bean間依賴關系。

2.查找方法注入

如前所述,查找方法注入是一種您很少使用的進階功能。在單例範圍的bean對原型範圍的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()方法被重寫的子類,以便查找新的(原型)指令對象:

@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并傳回它的新執行個體,是以通常期望擁有2個執行個體(每個服務一個執行個體)。這肯定會有問題:在Spring中,執行個體化的bean singleton預設具有一個範圍。這就是魔法來臨的地方:所有@Configuration類都在啟動時被分類CGLIB。在子類中,child方法在調用父方法并建立新執行個體之前首先檢查容器是否有緩存的(範圍)Bean。請注意,從Spring 3.2開始,不再需要将CGLIB添加到類路徑中,因為CGLIB類已被重新打包org.springframework.cglib并直接包含在Spring-core JAR中。

根據您的bean的範圍,行為可能會有所不同。我們在這裡讨論單身人士。

由于CGLIB在啟動時動态添加功能,是以存在一些限制,特别是配置類不能是最終的。但是,從4.3開始,任何構造函數都可以在配置類上使用,包括@Autowired對預設注入使用 或使用單個非預設構造函數聲明。

如果您更喜歡避免任何CGLIB強加的限制,請考慮@Bean 在非@Configuration類上聲明您的方法,例如在普通@Component類上。方法之間的跨方法調用@Bean不會被攔截,是以您必須在構造方法或方法級别專門依賴依賴注入。

七、撰寫基于Java的配置

1.使用@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 Framework 4.2開始,它@Import也支援對正常元件類的引用,類似于該AnnotationConfigApplicationContext.register方法。如果您想要避免元件掃描,使用幾個配置類作為明确定義所有元件的入口點,這特别有用。

2.注入對導入的@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 類在上下文初始化期間處理得相當早,并且強制依賴性以這種方式注入可能會導緻意外的早期初始化。在可能的情況下,采用基于參數的注入,如上例所示。

另外,要特别小心,BeanPostProcessor并BeanFactoryPostProcessor通過定義@Bean。這些通常應該聲明為static @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中受支援。還要注意,不需要指定@Autowired目标bean是否隻定義了一個構造函數; 在上面的例子中,@Autowired在RepositoryConfig構造函數中是沒有必要的。

1.完全限定導入的豆類以便導航

在上面的場景中,使用得@Autowired很好,并提供了所需的子產品化,但是确切地确定自動布線bean定義的聲明位置仍然有些模糊。例如,作為一名開發人員ServiceConfig,您如何知道@Autowired AccountRepositorybean的确切位置?它在代碼中并不明确,這可能會很好。請記住, Spring Tool Suite提供的工具可以呈現圖表,顯示如何連接配接所有東西 - 這可能就是您所需要的一切。此外,您的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類及其依賴關系與導航基于接口的代碼的常用過程無異。

如果你想影響某些豆類的啟動建立順序,考慮宣布一些為@Lazy(對于第一次通路,而不是在啟動時建立)或@DependsOn某些其它豆類(確定特定的其他豆将在目前之前建立bean,超越了後者的直接依賴意味)。

二、有條件地包含@Configuration類或@Bean方法

根據某些任意系統狀态,有條件地啟用或禁用完整的@Configuration類,甚至個别的@Bean方法通常很有用。一個常見的例子是,@Profile隻有在Spring中啟用了特定的配置檔案時才使用注釋來激活bean Environment(請參閱Bean定義配置檔案 以了解詳細資訊)。

該@Profile注釋是使用所謂的更靈活的注釋實際執行@Conditional。該@Conditional注釋訓示特定 org.springframework.context.annotation.Condition前應谘詢的實施@Bean是注冊。

Condition接口的實作隻是提供了一個matches(…​) 傳回true或傳回的方法false。例如,以下是Condition用于以下内容的實際 實作@Profile:

@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并使用@ImportResource注釋根據需要導入XML 。

1.以XML為中心使用@Configuration類

最好從XML引導Spring容器,并@Configuration以臨時方式包含 類。例如,在使用Spring XML的大型現有代碼庫中,根據需要建立@Configuration類并從現有XML檔案中包含它們會更容易。下面你會發現@Configuration在這種“以XML為中心”的情況下使用類的選項。

将@Configuration類聲明為普通的Spring

<bean/>

元素

請記住,@Configuration類最終隻是容器中的bean定義。在這個例子中,我們建立一個@Configuration名為AppConfig并包含它的類system-test-config.xml作為

<bean/>

定義。因為

<context:annotation-config/>

已打開,容器将識别 @Configuration注釋并處理 正确@Bean聲明的方法AppConfig。

@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://本地主機/ 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将不會引用它,并且它不太可能通過名稱明确從容器中提取。與DataSourcebean 同樣- 它隻能通過類型自動裝配,是以id不需要顯式bean 。

使用

<context:component-scan />

來拾取@Configuration類

因為@Configuration與間注解@Component,@Configuration-annotated類自動對于元件掃描的候選者。使用與上述相同的場景,我們可以重新定義system-test-config.xml以利用元件掃描。請注意,在這種情況下,我們不需要顯式聲明

<context:annotation-config/>

,因為

<context:component-scan/>

啟用了相同的功能。

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

2.@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://本地主機/ XDB
jdbc.username = SA
jdbc.password =           
  • main
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}           

好啦,基于Java的容器配置講了這麼多,總算是完啦。

加油啦,越努力越幸運