1. 程式人生 > >IoC容器篇(十五)——ApplicationContext的額外功能

IoC容器篇(十五)——ApplicationContext的額外功能

目錄

ApplicationContext的額外功能

org.springframework.beans.factory包提供了管理與操作beans的基礎功能。

org.springframework.context包提供了ApplicationContext介面,此介面擴充套件了BeanFactory介面,並且擴充套件了其他介面,用以提供面向框架風格應用的額外功能。

開發者通常使用完全宣告風格使用ApplicationContext。

1.使用MessageSource國際化

ApplicationContext擴充套件了名為MessageSource的介面,因此提供了國際化功能。

Spring提供了HierarchicalMessageSource介面,因此可以層次解析資訊。

這些介面構成了Spring訊息處理的基礎。

這些介面提供了getMessage方法用來獲取檢索資訊。

當ApplicationContext被載入時,會自動搜尋定義在context中的MessaegSource bean。

bean的名字必須為messageSource。

當messageSource bean被找到,所有的getMessage方法都會被委派到資訊源。

ps:如果沒有找到資訊源,ApplicationContext會試圖尋找包含名為messageSource的bean的雙親,並將其作為MessageSource使用。

ps:如果ApplicationContext還是無法找到可使用的資訊源,會建立一個空DelegatingMessageSource。

Spring為MessageSource提供了兩種實現,ResourceBundleMessageSsource與StaticMessageSource。

這兩種實現又都實現了HierarchicalMessageSource,用於處理巢狀資訊。

StaticMessageSource很少使用,不過提供了程式設計新增資訊到源的方法。

<beans>
    <bean id="messageSource"
            class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>format</value>
                <value>exceptions</value>
                <value>windows</value>
            </list>
        </property>
    </bean>
</beans>

note:ResourceBundleMessageSource的使用。

note:這個例子假設,在classpath下定義了三個名為formate、exceptions、windows的資源包(屬性檔案)。

ps:resource bundle,即資源包,指編譯在應用程式中的屬性檔案。

ps:JDK通過將使用ResourceBundles作為標準方法,用於處理解析資訊的請求。

public static void main(String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message", null, "Default", null);
    System.out.println(message);
}

note:程式使用MessageSource功能。

note:獲取資訊源中鍵為message的屬性的值。

ps:ApplicationContext的實現也是MessageSource的實現。

<beans>

    <!-- this MessageSource is being used in a web application -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="exceptions"/>
    </bean>

    <!-- lets inject the above MessageSource into this POJO -->
    <bean id="example" class="com.foo.Example">
        <property name="messages" ref="messageSource"/>
    </bean>

</beans>
public class Example {

    private MessageSource messages;

    public void setMessages(MessageSource messages) {
        this.messages = messages;
    }

    public void execute() {
        String message = this.messages.getMessage("argument.required",
            new Object [] {"userDao"}, "Required", null);
        System.out.println(message);
    }
}
# in exceptions.properties
argument.required=The {0} argument is required.

note:傳入的引數轉化為字串,然後將之插入到檢索返回資訊的佔位符上。

ps:String getMessage(String code, Object[] args, String default, Locale loc):用於從MessageSource中檢索資訊的基礎方法。

ps:如果指定場所沒有資訊被檢索到,default將會被返回。

ps:提供的引數會使用標準庫提供的MessageFormat功能用於返回的資訊。

# in exceptions_en_GB.properties
argument.required=Ebagum lad, the {0} argument is required, I say, required.
public static void main(final String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("argument.required",
        new Object [] {"userDao"}, "Required", Locale.UK);
    System.out.println(message);
}

note:針對British(en-GB)地區解析資訊。

ps:Spring的MessageSource實現遵循與JDK的ResourceBundle相同的場所解析規則與回退操作規則。

ps:通常情況下,地區解析有應用執行環境管理。

可以使用MessageSourceAware介面,獲取任何定義的MessageSource。

任何定義在ApplicationContext中並實現了MessageSourceAware介面的bean,會被注入context中的MessageSource。

可以使用ReloadableResourceBundleMessageSource類代替ResourceBundleMessageSource。

ReloadableResourceBundleMessageSource可以從任何Spring的資源位置讀取屬性檔案(不只是classpath下),並且支援熱載入屬性檔案。

2.標準事件與定製事件

ApplicationContext的事件處理機制由ApplicationEvent類與ApplicationListener介面提供。

如果實現了ApplicationListener介面的bean配置到了context中,每一次ApplicationEvent被髮布,都會通知這個bean。

這是一個標準的觀察者模式。

public class BlackListEvent extends ApplicationEvent {

    private final String address;
    private final String test;

    public BlackListEvent(Object source, String address, String test) {
        super(source);
        this.address = address;
        this.test = test;
    }

    // accessor and other methods...
}

note:通過繼承ApplicationEvent構建新事件BlackListEvent。

public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blackList;
    private ApplicationEventPublisher publisher;

    public void setBlackList(List<String> blackList) {
        this.blackList = blackList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void sendEmail(String address, String text) {
        if (blackList.contains(address)) {
            BlackListEvent event = new BlackListEvent(this, address, text);
            publisher.publishEvent(event);
            return;
        }
        // send email...
    }
}

note:需要作為bean配置進容器。

ps:通過呼叫ApplicationEventPublisher的publishEvent()方法釋出定製的ApplicationEvent。

ps:為了完成釋出,通常需要通過實現ApplicationEventPublisherAware獲取實現類,然後作為bean註冊。

public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

note:需要作為bean配置進容器。

ps:預設情況下所有的監聽器同步接收事件。

ps:同步單執行緒處理處理監聽器接收事件的一個優點為可以在事務上下文內部進行操作。

Spring的事件機制被設計為位於同一個ApplicationContext下的beans之間的簡單通訊。

Spring Integration專案提供了用於構建well-known Spring程式模型的輕量級、面向模式、事件驅動的架構的完整支援。

基於註解的事件監聽器

使用@EventListener監聽事件

public class BlackListNotifier {

    @EventListener
    public void processBlackListEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

ps:通過使用@EventListener註解,監聽器可以不再實現ApplicationListener介面,而是直接使用public方法。

使用@EventListener同時監聽多個事件

@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    ...
}

通過SpEL設定監聽過濾

@EventListener(condition = "#blEvent.test == 'foo'")
public void processBlackListEvent(BlackListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}

note:只有當事件的test屬性等於foo時,此事件才會被監聽。

ps:每一個SpEL表示式都針對專用的上下文求值

非同步監聽器

@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
    // BlackListEvent is processed in a separate thread
}

ps:非同步監聽器如果丟擲異常,異常不會傳給呼叫者。

ps:非同步監聽器不能傳送回覆。

監聽器排序

@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress...
}

泛型事件

@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    ...
}

note:只監聽型別引數為Person的EntityCreatedEvent事件。

ps:由於型別擦除,所以只有當被觸發的Event解析被監聽器過濾的泛型引數,泛型事件機制才會運作。

使用ResolvableTypeProvider引導Spring框架

public class EntityCreatedEvent<T>
        extends ApplicationEvent implements ResolvableTypeProvider {

    public EntityCreatedEvent(T entity) {
        super(entity);
    }

    @Override
    public ResolvableType getResolvableType() {
        return ResolvableType.forClassWithGenerics(getClass(),
                ResolvableType.forInstance(getSource()));
    }
}

3.對低層次資源的便捷訪問

為了更好地理解與使用ApplicationContext,使用者通常需要熟悉Spring的Resource抽象。

一個ApplicationContext就是一個ResourceLoader,用於載入Resource。

Resource基本上就是一個有著更多特性的java.net.URL,事實上,Resource的實現包裝了java.net.URL的例項。 Resource幾乎可以以透明樣式從任何位置(classpath、檔案系統以及任何可以以標準URL或其變體描述的位置)獲取低層次資源。

如果資源位置字串是一個沒有字首的簡單路徑,這些資源位置字串所表達的含義將會適應實際ApplicationContext型別。

實現了ResourceLoaderAware的bean,在ApplicationContext的初始化階段,ResourceLoader會被注入ApplicationContext依賴。

可以選擇將Resource設定為依賴注入點,用於訪問靜態資源,這些Resource會被作為普通的屬性注入。

可以將這些Resource屬性指定為普通的字串路徑。

這些Resource屬性使用一個自動在context中註冊的特定的JavaBean ProoertyEditor,將這些文字字串轉化為真實的Resource物件。

提供給ApplicationContext構造器的路徑是實際資源字串,對於特定的context實現,簡單形式的路徑會被進行適當的處理。

ClassPathXmlApplicationContext將簡單位置路徑作為classpath位置處理。

可以通過給位置路徑新增特定的字首,強迫從classpath或URL中載入定義,無論實際的ApplicationContext的型別是什麼。

4.初始化web應用的ApplicationContext

使用ContextLoaderListener註冊一個ApplicationContext

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

ps:ContextLoaderListener會檢查contextConfigLocation引數。

ps:如果此引數不存在,listener預設使用/WEB-INF/applicationContext.xml。

ps:引數可以通過預定義的分隔符(逗號,分號,空格)輸入多個xml檔案路徑。

5.將Spring ApplicationContext作為Java EE RAR檔案部署

可以將ApplicationContext作為RAR檔案部署,即將context以及所有所需的bean類與jar庫全部封裝為一個Java EE配置單元。

RAR等價於一個單機的ApplicationContext,只是執行在Java EE環境下,可以訪問Java EE服務設施。

在部署WAR檔案的場景下,部署RAR是一個更為自然的選擇。

一個沒有任何HTTP進入點的WAR檔案只能用於將Spring的ApplicationContext匯入Java EE環境中。

RAR部署對於不需要HTTP進入點並且只由資訊終端與定時任務組成的ApplicationContext是一個理想的選擇。

RAR部署的context下的beans,可以使用應用伺服器資源,eg:JTA事務管理、JNDI繫結JDBC資料來源、JMS ConnectionFactory例項、在JMX伺服器註冊,所有這些通過Spring的標準事務管理、JNDI、JMX支援裝置實現。

Application元件還可以通過Spring的TaskExecutor抽象與應用伺服器的 JCA WorkManage進行互動。

簡單地將Spring的ApplicationContext部署為Java EE RAR檔案:

  1. 將所有的應用類全部打包進RAR檔案,即擁有不同的副檔名的JAR檔案
  2. 將所有必須的Jar庫全部新增進RAR檔案的根下
  3. 新增"META-INF/ra.xml"部署描述符以及相關的bean定義的xml檔案
  4. 將RAR檔案放入應用伺服器的部署目錄下

RAR部署單元通常是自足的,即不會將自己的元件暴露給外界,甚至是相同應用的模組。

與基於RAR的ApplicationContext的互動通常通過與其他模組共享的JMS destination。

基於RAR的ApplicationContext(eg:定時任務)可以對檔案系統中的檔案做出反應。

如果需要同步訪問外界時,可以通過匯出被其他應用模組在相同機制下使用的RMI端點的方式實現。