天天看點

Spring 5 中文解析核心篇-IoC容器之基于Java容器配置

1.12 基于Java容器配置

這個部分涵蓋了在你的Java代碼中怎樣去使用注解配置Spring容器。它包含下面的主題:

1.12.1 基本概念:@Bean和

@Configuration

Spring的新Java配置支援中的主要構件是

@Configuration

注解的類和

@Bean

注解的方法。

@Bean

注解使用表示一個方法執行個體、配置和執行個體化Spring IoC容器管理的新對象。這些類似Spring的 XML配置,

@Bean

注解扮演了元素相同的角色。你可以使用

@Bean

注解方法替換任何Spring中

@Component

元件。然而,它們最常與@Configuration一起使用。注解類

@Configuration

注解表示它的主要目的是bean定義的源。此外,

@Configuration

類允許通過調用同一類中的其他

@Bean

方法來定義Bean間的依賴關系。下面最簡單的

@Configuration

例子:

@Configuration
public class AppConfig {

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

前面的AppConfig類是與下面的Spring XML定義相等:

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>           
​ 完整的

@Configuration

與“精簡”

@Bean

模式?

@Bean

方法在類中被聲明時,這些類沒有被注解

@Configuration

,它們被稱為以“精簡”模式進行處理。在

@Component

或在簡單的舊類中聲明的Bean方法被認為是“精簡版”,其中包含類的主要目的不同,而

@Bean

方法在那裡具有某種優勢。例如,服務元件在每個可應用的元件類上通過增加一個附加的@Bean方法暴露管理視圖到容器。在這種場景下,

@Bean

方法是一種通用的工廠方法機制。

不像完整的

@Configuration

,精簡

@Bean

方法不能聲明bean之間的依賴關系。相反,它們對其包含的元件的内部狀态以及可能聲明的參數(可選)進行操作。是以,此類

@Bean

方法不應調用其他

@Bean

方法。每個這樣的方法實際上隻是一個特定bean引用的工廠方法,沒有任何特殊的運作時語義。這裡的積極副作用是在運作時不需要應用CGLIB子類,是以在類設計方面沒有限制(也就是,包含類可能是final)。

在常見的場景中,

@Bean

方法是在@Configuration類中聲明的,確定總是使用完整模式,是以交叉方法引用被重定向到容器的生命周期管理。這可以防止通過正常Java調用意外調用相同的

@Bean

方法,這有助于減少在lite模式下操作時難以跟蹤的細微錯誤。備注:在

@Configuration

類中調用其他

@Bean

方法會定向到容器的生命周期管理。

@Bean

@Configuration

在下面的部分深入讨論。首先,我們會覆寫基于Java注解建立Spring容器的各種方式。

1.12.2 通過使用

AnnotationConfigApplicationContext

初始化Spring容器

以下各節介紹了Spring 3.0中引入的Spring的

AnnotationConfigApplicationContext

。這個通用的ApplicationContext實作不僅能夠接收

@Configuration

類作為輸入,還能夠接收普通的

@Component

類和使用JSR-330中繼資料注解的類

@Configuration

類作為輸入被提供,

@Configuration

類自身作為一個bean定義被注冊并且所有在類中被

@Bean

聲明的方法也作為bean的定義被注冊到容器。

當提供

@Component

和JSR-330類時,它們被注冊為bean定義,并假設DI中繼資料(如

@Autowired

@Inject

)在這些類中使用。

簡單構造

與執行個體化

ClassPathXmlApplicationContext

時将Spring XML檔案用作輸入的方式幾乎相同,執行個體化

AnnotationConfigApplicationContext

時可以将

@Configuration

類用作輸入。如下面的示例所示,這允許完全不使用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 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") //1
public class AppConfig  {
    ...
}           
  1. 這個注解激活元件掃描
經驗豐富的Spring使用者可能熟悉Spring context 中等效的XML聲明:命名空間,類似下面例子:
<beans>
 <context:component-scan base-package="com.acme"/>
</beans>           

在前面的例子中,

com.acm

包被掃描去查找被注解

@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

包中(或任何

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類。也可以指定全限定包以進行元件掃描 -->
    <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>           

1.12.3 使用@Bean注解

@Bean

是一個方法級别注解并且是XML 元素的直接類比物。這個注解支援一些通過提供的屬性,例如:

init-method destroy-method autowiring

你可以在

@Configuration

@Component

注解的類上使用

@Bean

注解。

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

中可用,并綁定一個

TransferServiceImpl

類型的對象執行個體,類似下面文本圖檔顯示:

transferService -> com.acme.TransferServiceImpl           

你也可以聲明你的

@Bean

方法為一個接口(或者基類)傳回類型,類似下面例子展示:

@Configuration
public class AppConfig {

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

但是,這将進階類型預測的可見性限制為指定的接口類型(

TransferService

)。然而,全類型(

TransferServiceImpl

)在容器隻有一個,受影響的單例bean被初始化。非懶加載單例bean根據它們的聲明順序擷取已經初始化執行個體,當其他元件嘗試通過非聲明類型去比對時,你可能看到不同類型比對結果依賴(例如,

@Autowired

TransferServiceImpl

,僅在執行個體化

transferService

bean之後才解析)。

如果你始終通過聲明的服務接口引用你的類型,那麼你的

@Bean

傳回類型可以安全地加入該設計決策。但是,對于實作多個接口的元件或由其實作類型潛在引用的元件,聲明最具體的傳回類型(至少與引用你的bean的注入點所要求的具體類型一樣)更為安全。 備注:意思是如果通過實作類型引用元件時,在定義

@Bean

方法時傳回類型要是具體的實作類型。

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

,它們各自的方法被容器回調。

标準的set

*Aware

接口(例如,

BeanFactoryAware

,、

BeanNameAware MessageSourceAware ApplicationContextAware

等等)也是完全的被支援。

@Bean

注解支援任意的初始化和銷毀回調方法,非常類似Spring 中XML元素的init-method和

destroy-method

屬性,類似下面例子顯示:

public class BeanOne {

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

public class BeanTwo {

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

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}           
預設情況下,這些bean通過Java配置定義它們有公共的

close

shutdown

方法自定地加入到銷毀回調中。如果你有一個公共的

close

shutdown

方法并且當容器被關閉時不想被回調,你應該增加

@Bean(destroyMethod="")

到你的bean定義中去禁止預設的(

inferred

)模式。

預設情況下,你可能要對通過JNDI擷取的資源執行此操作,因為其生命周期在應用程式外部進行管理的。特别是,要確定始終為資料源執行此操作,因為在Java EE應用伺服器上這是有問題的。

下面的例子展示怎樣去阻止DataSource自動銷毀回調。

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

@Bean

方法,通常使用程式設計式地JNDI查找,方法是使用Spring的

JndiTemplate

JndiLocatorDelegate

幫助器,或者直接使用JNDI

InitialContext

用法,而不使用

JndiObjectFactoryBean

變體(這将迫使你将傳回類型聲明為

FactoryBean

類型,而不是實際的類型。目标類型,是以很難在打算引用此處提供的資源的其他@Bean方法中用于交叉引用調用。

對于前面示例中的BeanOne,它等效于在構造期間直接調用

init()

方法,類似下面例子:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }

    // ...
}           
當你直接地工作在Java中,你可以對你的對象做任何事而不需要依賴容器生命周期。

指定Bean作用域

Spring包括

@Scope

注解,是以你可以使用bean的作用域。

@Scope

你可以指定你的bean定義通過

@Bean

注解同時也可以指定一個作用域。你可以使用

在Bean作用域部分

中任何标準的作用域指定。

預設作用域是

singleton

,但是你可以覆寫這個通過

@Scope

注解,類似下面例子顯示:

@Configuration
public class MyConfiguration {

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

@Scope

scoped-proxy

Spring提供通過

作用域代理

處理作用域依賴的便捷方式。當使用XML配置

< aop:scoped-proxy/>

是最簡單建立一個代理方式。使用

@Scope

注解在Java中配置bean,可以通過

proxyMode

屬性提供同等的支援。預設是沒有代理(

ScopedProxyMode.NO

),但是你可以指定

ScopedProxyMode.TARGET_CLASS

ScopedProxyMode.INTERFACES

如果你使用Java,将XML參考文檔中的作用域代理示例(請參閱作用域代理)移植到我們的

@Bean

,它類似于以下内容:

// Http session作用域bean 暴露為代理
@Bean
@SessionScope
public UserPreferences userPreferences() {
    return new UserPreferences();
}

@Bean
public Service userService() {
    UserService service = new SimpleUserService();
    // 引用UserPreferences
    service.setUserPreferences(userPreferences());
    return service;
}           

自定義Bean名稱

預設情況下,配置類使用

@Bean

方法的名稱作為bean名稱。這個功能可以被覆寫,通過

@Bean

的name屬性,類似下面例子:

@Configuration
public class AppConfig {

   @Bean(name = "myThing")
   public Thing thing() {
       return new Thing();
   }
}           

Bean别名

類似在

bean的命名

中讨論,有時候給一個簡單bean多個名稱,也稱為Bean别名。

@Bean

注解的name屬性接受一個字元串數組為這個别名。下面例子展示怎樣去設定bean的别名:

@Configuration
public class AppConfig {

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

@Bean描述

有時候,有助于提供有關bean的更詳細的文本描述。當這些bean被暴露為監控目的時,是非常有用的。

去增加一個描述到

@Bean

,你可以使用

@Description

注解,類似下面例子展示:

@Configuration
public class AppConfig {

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

1.12.4 使用

@Configuration

@Configuration

是一個類級别注解,它表示對象是bean定義的源。

@Configuration

類聲明bean通過公共

@Bean

注解方法。在

@Configuration

類上調用

@Bean

方法能被使用去定義Bean之間依賴關系。

檢視基礎概念:@Bean和@Configuration

bean間注入的依賴關系

當Bean彼此依賴時,表達這種依賴就像讓一個bean方法調用另一個一樣簡單,類似下面例子展示:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());
    }

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}           

beanOne

通過構造函數注入引用

beanTwo

僅僅當

@Bean

方法被聲明在

@Configuration

類中時,這方法聲明bean間的依賴關系有效。你不能通過使用普通的

@Component

聲明bean間的依賴關系。

查找方法注入

如前所述,查找方法注入是一個進階特性,它很少地使用。在一些單例作用域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()

方法将被覆寫,以使其查找新的(原型)command對象。通過使用Java配置。下面例子展示怎樣去做:

@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 createCommand()
    // 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

執行個體并且傳回它,你通常希望有兩個執行個體(每個服務一個)。那肯定是有問題的:在Spring中,預設情況執行個體化bean作用域是

singleton

。這就是神奇之處:所有

@Configuration

類在運作時是CGLIB的子類。在子類中,在調用父方法建立執行個體之前,子類方法首先檢查容器緩存bean。

這種行為根據你的bean作用域可能不同。我們在這裡讨論的單例bean。

在Spring3.2以後,不在需要添加CGLIB到你的類路徑下,因為CGLIB類已經被重新打包在

org.springframework.cglib

下并且直接地包含在

spring-core

jar中。

由于CGLIB在啟動時會動态添加功能,是以存在一些限制。特别地,配置類不能是

final

。然而,在Spring4.3以後,任何構造函數在配置類上是被允許的,包括

@Autowired

或沒有預設參數的構造函數申明預設注入的使用。

如果你想避免CGLIB的限制,考慮在你非

@Configuration

類(例如,一個普通的

@Component

替換)上聲明你的

@Bean

方法。在

@Bean

方法之間跨方法調用不會被攔截,是以你必在須構造函數或方法級别隻依賴需要注入的。

代碼示例:

com.liyong.ioccontainer.starter.BaseJavaConfigIocContainer

1.12.5 編寫基于Java的配置

Spring的基于Java配置特性允許你編寫注解,這樣可以降低你的配置複雜性。

使用@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

類。

在Spring4.2以後,

@Import

也支援引用正常的元件類,類似

AnnotationConfigApplicationContext.register

方法。如果你想避免元件掃描,這是非常有用地,通過使用一些配置類作為入口點顯示定義所有元件。

在被導入的

@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:這意味着它們可以利用

@Autowired

@Value

注入以及其他與其他bean相同的特性。

確定以這種方式注入的依賴項隻是最簡單的一種。

@Configuration

類是在上下文初始化期間非常早地處理的,并且強制以這種方式注入依賴項可能導緻意外的早期初始化。如上例所示,盡可能使用基于參數的注入。

另外,通過

@Bean

定義

BeanPostProcessor

和BeanFactoryPostProcessor時要特别小心。這些應該通常地被聲明為

static

@Bean

方法,不要觸發它們包含的配置類初始化。除此之外,

@Autowired

@Value

可能在配置類上不能工作,因為它作為bean執行個體被建立早于

AutowiredAnnotationBeanPostProcessor

下面例子展示一個bean怎樣被裝配到其他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;

    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

類中構造方法注入僅支援在Spring4.3以後。注意:如果目标bean定義僅有一個構造函數,那麼不需要去指定

@Autowired

com.liyong.ioccontainer.starter.BeanAndConfigurationImportContainer

全限定導入bean以更容易導航

在前面的場景中,使用

@Autowired

工作很好并且提供期望的子產品化,但是确定自動裝配 bean的定義是在哪裡聲明的仍然有些模棱兩可。例如,作為一個開發者看到

ServiceConfig

,怎樣确切的知道

@Autowired

AccountRepository

bean在哪裡定義的?它在代碼中不是明确的,這可能很好。記住,

Spring Tools

為Eclipse提供可以渲染圖形的工具,這些圖形顯示了所有的對象是怎樣連接配接的,這可能是你需要的。你的Java IDE能更容易地找到所有聲明和使用

AccountRepository

類型并且快速地顯示

@Bean

方法的路徑以及傳回類型。

如果這種歧義是不可接受的,并且你希望從IDE内部直接從一個

@Configuration

類導航到另一個

@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

類及其依賴項與導航基于接口的代碼的通常過程沒有什麼不同。

如果你想流暢啟動建立這些bean的順序,考慮聲明它們為

@Lazy

(用于首次通路而不是啟動時建立)或像

@DependsOn

其他bean(確定其他指定bean在目前bean被建立之前 ,而不受後者直接依賴關系的影響)。

條件地包含

@Configuration

類或

@Bean

方法

根據某些系統狀态,有條件地啟用或禁用完整的

@Configuration

類甚至單個

@Bean

方法,通常很有用。說明:可以啟用/禁止

@Configuration

類或者

@Configuration

類中的

@Bean

方法。一個常用的例子是去使用

@Profile

注解去激活bean,僅僅當在Spring中的

Environment

指定的

profile

被激活時(檢視Bean定義

Profiles

詳情)。

@Profile

注解通過使用一個非常靈活的注解叫做

@Conditional

實作的。

@Conditional

注解訓示在注冊

@Bean

之前應咨詢特定

org.springframework.context.annotation.Condition

實作。

Condition

接口的實作接口提供一個

matches(…)

方法,它會傳回true或false。例如,下面的清單顯示

Condition

@Profile

真實實作:

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    // 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

文檔更多詳細。

結合Java和XML配置

Spring的

@Configuration

類不能

100%

的完成替換Spring XML。一些工具(如Spring XML namespaces)仍然是配置容器的理想方法。在使用XML友善或有必要的情況下,你可以選擇:使用“

ClassPathXmlApplicationContext

以“ XML中心”方式執行個體化容器,或通過使用

AnnotationConfigApplicationContext

和“ Java配置中心”方式執行個體化容器。

@ImportResource

注解可根據需要導入XML。

以XML為中心的

@Configuration

類的使用

從XML引導Spring容器并以特别的方式包含

@Configuration

類可能更好。例如,在使用Spring XML的大型現有代碼庫中,根據需要建立

@Configuration

類并且現有XML檔案中将它們包含在内變得更加容易。在本節的後面,我們将介紹在這種“以XML為中心”的情況下使用

@Configuration

類的選項。

  • 聲明

    @Configuration

    類似普通的Spring元素

@Configuration

類最終在容器中是bean定義。在這一系列例子中,我們建立

@Configuration

類命名為

AppConfig

并且包含它在system-test-config.xml中類似定義。因為

<context:annotation-config/>

已經打開,容器在

AppConfig

中識别

@Configuration

注解和處理@Bean方法聲明。

下面例子顯示在Java中普通的配置:

@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 沒有聲明id元素。雖然這樣做是可以接受的,但這是不必要的,因為沒有其他bean引用它,而且不太可能通過名稱顯式地從容器中擷取它。同樣,

DataSource

bean隻能按類型自動裝配,是以也不嚴格要求顯式bean

id

  • <context:component-scan/>

    選擇

    @Configuration

因為

@Configuration

元注解是

@Component

,被

@Configuration

注解的類是元件自動地掃描的候選者。使用相同的場景在前面例子已經描述,我們可以重定義

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

以類為中心的XML與

@ImportResource

的結合使用

在應用中,

@Configuration

類是配置容器的主要機制,但是仍然可能需要至少使用一些XML。在這個場景中,你可以使用

@ImportResource

和定義你需要的XML。這樣做實作了“以Java為中心”的方法來配置容器,并使XML保持在最低限度。下面例子展示了怎樣去使用

@ImportResource

注解去實作“以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

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}           

com.liyong.ioccontainer.starter.BaseJavaConfigAndXmlIocContainer

作者

個人從事金融行業,就職過易極付、思建科技、某網約車平台等重慶一流技術團隊,目前就職于某銀行負責統一支付系統建設。自身對金融行業有強烈的愛好。同時也實踐大資料、資料存儲、自動化內建和部署、分布式微服務、響應式程式設計、人工智能等領域。同時也熱衷于技術分享創立公衆号和部落格站點對知識體系進行分享。

部落格位址:

http://youngitman.tech

CSDN:

https://blog.csdn.net/liyong1028826685

微信公衆号:

Spring 5 中文解析核心篇-IoC容器之基于Java容器配置

技術交流群:

Spring 5 中文解析核心篇-IoC容器之基于Java容器配置