1. 程式人生 > >碼農小汪-spring框架學習之9-基於 Java 的配置元資料 @ImportResource

碼農小汪-spring框架學習之9-基於 Java 的配置元資料 @ImportResource

基於 Java 的配置元資料

Spring 新功能 Java-cofiguration 支援@Configuration 類註解和@Bean 方法註解@Bean 註解用於表明一個方法將會例項化、配置、初始化一個新物件,該物件由Spring IoC 容器管理。大家都熟悉 Spring 的< beans/>XML 配置, @Bean 註解方
法和它一樣。可以在任何 Spring @Component 中使用@Bean 註解方法,當然了,大多數情況下,@Bean 是配合@Configuration 使用的。@Configuration 註解的類表明該類的主要目的是作為 bean 定義的源。此外,@Configuration 類允許依賴本類中使用@Bean 定義的 bean。

@Configuration
public class AppConfig {

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

}

The AppConfig class above would be equivalent to the following Spring < beans/> XML:
我覺得直接使用XML挺好的,配合註解使用

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

使用 AnnotationConfigApplicationContext 例項化 Spring IoC 容器

Spring 的 AnnotationConfigApplicationContext 部分,是 Spring3.0 中新增的。這是一個強大的(譯註原文中是多才多藝的 versatile)ApplicationContext 實現,不僅能解析@Configuration 註解類,也能解析@Componnet 註解的類和使用 JSR-330
註解的類。使用@Configuration 註解的類作為配置元資料的時候, @Configuration 類本身也
會註冊為一個 bean 定義,類內所有的@Bean 註解的方法也會註冊為 bean 定義。使用@Component 和 JSR-330 註解類作為配置元資料時,他們本身被註冊為bean 定義,並假設 DI(依賴注入)元資料,像類內使用的@Autowired 或者@Inject都是

簡單結構

Spring 以 XML 作為配置元資料例項化一個 ClassPathXmlApplicationContext,以@Configuration 類作為配置元資料時, Spring 以差不多的方式,例項化一個AnnotationConfigApplicationContext。因此, Spring 容器可以實現零 XML 配
置。

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

AnnotationConfigApplicationContext 不是僅能與@Configuration 註解類配合使用。任何@Component 或者 JSR-330 註解的類都可以作為其建構函式的引數:

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…)程式設計式構造 Spring 容器

AnnotationConfigApplicationContext 也可以通過無參建構函式例項化,然後呼叫 registor()方法配置。此法應用於程式設計式構

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

這些好麻煩的感覺~xml挺好的為什麼要基於java配置呢?

開啟元件掃描

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

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

上面的栗子中,會掃描 com.acme package 包,檢索出所有@Component-annotated類, Spring 容器將會註冊這些類為 Spring bean 定義。
在上面的示例中,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);
}

Note:Remember that @Configuration classes are meta-annotated with @Component,(他的元註解是@Component) so they are candidates for component-scanning! In the example above, assuming that AppConfig is declared within the com.acme package (or any package underneath), it will be picked up during the call to scan(), and upon refresh() all its @Bean methods will be processed and registered as bean definitions within the container.

使用 AnnotationConfigWebApplicationContext 支援 WEB 應用

WebApplicationContext 介面一個實現 AnnotationConfigWebApplicationContext,是AnnotationConfigApplicationContext 的一個變體。在配置ContextLoaderListener、 Spring MVCDispatcherServlet 等等時,使用此實現類。下面這段 web.xml 片段,是典型 Spring MVC 的 Web 應用的配置。注意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>

這些和我們在學習servlet的時候差不多吧~比如監聽器,攔截器~進行處理!

使用@Bean 註解

@Bean 是方法註解,和 XML 中的元素十分相似。該註解支援的一些屬性,比如 init-method, destroy-method,autowiring 和 name

宣告Bean

要宣告 bean 非常簡單,只需要在方法上使用@Bean 註解。使用此方法,將會在ApplicationContext 內註冊一個 bean, bean 的型別是方法的返回值型別

@Configuration
public class AppConfig {

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

}

上面的配置和下面的 XML 配置等價:

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

上面兩種配置,都會在 ApplicationContext 內產生一個 bean 定義,名稱為transferService,該 Spring bean 繫結到一個型別為 TransferServiceImpl 的例項:
transferService -> com.acme.TransferServiceImpl

Receiving lifecycle callbacks 接受生命期的回撥

使用@Bean 註解的 bean 定義,都支援常規生命週期回撥,能使用 JSR-250 中的@PostConstruct 和@PreDestroy 註解
The regular Spring lifecycle callbacks are fully supported as well(常規的回撥週期,使用編碼的方式注入的也是支援的). If a bean implements InitializingBean, DisposableBean, or Lifecycle, their respective methods are called by the container.(這些都會被容器呼叫的哦)
The standard set of Aware interfaces such a**s BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware*, and so on are also fully supported.

下面這個是我剛剛用MyExclipse檢視的她可以使用的屬性!相信大家都是知道這些到底是啥子意思吧!

@Bean(initMethod=”dd”,destroyMethod=”XX”,name=”ddd”,autowire=Autowire.BY_NAME)

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

}

Of course, in the case of Foo above, it would be equally as valid to call the init() method directly during construction:
自己主動的去呼叫也是可以得!當你直接在Java中,你可以做任何你喜歡的,做你的物件 並不總是需要依靠容器生命週期!

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

    // ...

}

使用@Scope 註解

The default scope is singleton, but you can override this with the @Scope annotation:

@Configuration
public class MyConfiguration {

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

}

@Scope and scoped-proxy 兩個作用域不一樣的之間的依賴關係

作用域代理完成作用域bean 依賴。若使用 XML 配置,最簡單的方式是使用元素建立一個代理。若是在 Java 程式碼中配置 bean,有一種等價的做法,使用@Scope註解並配置其 proxyMOde 屬性.預設配置是沒有代理 ScopedProxyMode.NO,但是你可以設定 ScopedProxyMode.TARGET_CLASS 或者 ScopedProxyMode.INTERFACES。 如
果將 XML 格式的作用域代理示例轉換成 Java 中使用@Bean

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

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

預設情況下,配置類中,使用@Bean 的方法名作為返回 bean 的名字。通過配置可以覆蓋此設定,使用 name 屬性 即可。

@Configuration
public class AppConfig {

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

}

預設為foo

bean 別名在之前討論過的 bean 別名“Naming beans”,有時候需要給一個bean 指定多個 name。 @Bean 註解的 name 屬性就是幹這個用,該屬性接收一個字串陣列。

Bean的描述,我們這個就可以使用到很多地方,File,Method,Type

挺有用的,檢視使用的時候,非常的方便!

Configuration
public class AppConfig {

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

}

Lookup method injection 之前我們提到過吧,使用XML方式處理

兩個不同生命週期之間的依賴關係!
我們之前有兩種不同的處理方式
每次,我們呼叫process方法的內部,自己去主動的呼叫新的createCommand()

// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

第二種方法:使用CGLIB的動態呼叫(我也不是很瞭解這個玩意)

和上面的思路是一樣的,每次改變的時候我們自己動態的呼叫新的東西!

package fiona.apple;

// no more Spring imports!

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

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="command"/>
</bean>

Using Java-configuration support , you can create a subclass of CommandManager where the abstract createCommand() method is overridden in such a way that it looks up a new (prototype) command object:

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();
}
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的配置是如何工作的內部資訊

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 個例項(分別返回給各個 service)。這個定義會有問題:在 Spring 中,例項化bean 預設的作用域是單例。這就是它的神奇之處:所有的@Configuration 類在啟動時,都是通過 CGLIB 建立一個子類。在呼叫父類的方法並建立一個新的例項之前,子類中的方法首先檢查是否快取過。 僅僅會有一個例項!這裡沒有采用New而是呼叫的方法。被限制為單例

組裝 java 配置元資料 Composing Java-based configurations

很多個java的配置檔案,我們要把他們何在一起的塞!讓後掃描的時候比較的方便,加入到容器中。這個和我們不同的XML檔案的時候我們也需要把他們合併在一起一個意思的!
在 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();
    }

}

現在,例項化 context 時,不需要同時指定 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。能讓每個開發者自己做自己的事情!

Injecting dependencies on imported @Bean definitions

在大部分實際場景中, bean 都會跨配置依賴。若使用 XML,這不是問題,因為不包含編譯器,開發者簡單的宣告ref=somBean 並相信 Spring 在容器例項化期間會正常執行。但是,使用@Configuration 類,配置模型替換為 java 編譯器,為了引用另一個 bean, Java編譯器會校驗該引用必須是有效的合法 Java 語法。非常幸運,解決這個這個問題非常簡單。還記得不, @Configuration 類在容器中本身就是一個 bean,這意味著他們能使用高階@Autowired 注入元資料,就像其他 bean 一樣。
構造的時候,依賴別的哦~

@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(accountRepository);//建構函式的時候注入依賴關係!
    }

}

@Configuration
public class RepositoryConfig {

    @Autowired
    private 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");
}

@Autowired 可以很好的工作,使設計更具模組化,但是自動注入的是哪個 bean 依然有些模糊不清。Spring Tool Suite 提供了視覺化工具,用來展示 bean 之間是如何裝配的,也許這就是你需要的。(這個視覺化工具呢?)

混合 java 和 xml 配置

Spring 的@Configuration 類並非是為了完全替換 Spring XML。有些工具,比如XML 名稱空間就是一種理想的配置方式。如果 XML 更方便或是必須的,你就得選擇:或者選擇基於 XML 的配置方式例項化容器,比如使用ClassPathXmlApplicationContext,或者選擇基於 Java 配置風格使用AnnotationConfigApplcationContext 加上@ImportResource 註解匯入必須的XML。

基於 XML 混合使用@Configuration 類

已存在大量的使用了 SPringXML 的程式碼,有需求需要使用@Configuration 類,這些配置類需要引入到現存的 XML 檔案中,此種做法也許更容易。接下來看看此場景。
@Configuration 類本身在容器內就是一個 bean。下面的樣例中,建立了一個@Configuration 類,類名是 AppConfig,引入一個配置檔案 system-testconfig.xml。由於< 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,識別裡面的註解!加入到容器中去~

    <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 引用,也不會根據 name 從容器獲取,所以 id 不是必須指定的,同樣,DataSourcebean,它只會根據型別自動裝配,所以明確的 id 也不是必須的。因為@Configuration 是@Component 的元資料註解,@Configuration 註解類也會自動作為掃描元件的候選者。

我們能重新定義 system-testconfig.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 配置 誰是誰的主導呢

在應用中, @Configuration 類是主要的容器配置機制,但是仍然可能會需要一些 XML。在這些場景中, 使用@ImportResource,即可引用 XML 配置。這樣配置可是實現此效果,基於 java 配置,儘可能少的使用 XML。
XML的唯一問題是你要等到執行時的時候來發現Bean裡面的錯誤或者其他愚蠢的問題。

@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>
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

`