ApplicationContext的額外功能

dust1發表於2019-04-02

org.springframework.context包增加了ApplicationContext介面,它繼承了BeanFactory介面,除了以面向應用框架的風格擴充套件介面來提供一些額外的功能。很多人以完全宣告的方式使用ApplicationContext,甚至沒有以程式設計的方式去建立它,而是依賴諸如ContextLoader等支援類來自動的例項化ApplicationContext,作為Java EE web應用程式正常啟動的一部分。 為了增強BeanFactory在面向框架風格的功能,上下文的包還提供了以下的功能:

  • 通過MessageSource介面訪問i18n風格的訊息
  • 通過ResourceLoader介面訪問類似URL和檔案資源
  • 通過ApplicationEventPublisher介面,即bean實現ApplicationListener介面來進行事件釋出
  • 通過HierarchicalBeanFactory介面實現載入多個(分層)上下文,允許每個上下文只關注特定的層,例如應用中的web層

通過MessageSource介面訪問i18n風格的訊息

國際化在於對特定地區的訪問提供屬於特定地區的語言反饋。

ApplicationContext介面繼承了一個叫做MessageSource的介面,因此它也提供了國際化(i18n)的功能。Spring也提供了HierarchicalMessageSource介面,它可以分層去解析資訊。

  • String getMessage(String code, Object[] args, String default, Locale loc): 這個基礎的方法用來從MessageSource檢索訊息。當指定的區域中沒有發現訊息時,將使用預設的。任何引數傳遞都將使用標準庫提供的MessageFormat變成替換值。
  • String getMessage(String code, Object[] args, Locale loc): 本質上和前面提供的方法相同,只有一個區別,就是當沒有指定訊息,又沒有發現訊息,將會丟擲NoSuchMessageException 異常。
  • String getMessage(MessageSourceResolvable resolvable, Locale locale): 所有的屬性處理方法都被包裝在一個名為MessageSourceResolvable的類中,你可以使用此方法。

Spring提供了ResourceBundleMessageSource和StaticMessageSource兩個MessageSource實現。它們兩個都實現了HierarchicalMessageSource以便處理巢狀訊息。StaticMessageSource很少使用,但是它提供了通過程式設計的方式增加訊息源。

    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>message.format</value>
            </list>
        </property>
        <property name="defaultEncoding" value="UTF-8"/>
    </bean>

    <bean id="messageUtils" class="com.example.springdemo.utils.MessageUtils">
        <property name="messageSource" ref="messageSource"/>
    </bean>
複製程式碼
public class MessageUtils {

    private MessageSource messageSource;

    public MessageSource getMessageSource() {
        return messageSource;
    }

    public void setMessageSource(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    public String getMessage(String temp, Object[] message, Locale locale) {
        return messageSource.getMessage(temp, message, "Required", locale);
    }

}
複製程式碼
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:/spring/message.xml");
        String temp = "argument.required";
        Object[] objects = new Object[] {
                "Java Coder!"
        };
        MessageUtils messageUtils = context.getBean(MessageUtils.class);
        System.out.println(messageUtils.getMessage(temp, objects, Locale.SIMPLIFIED_CHINESE));
    }
複製程式碼

資原始檔如下:

ApplicationContext的額外功能
通過_en_CN這種格式的命名,可以使得MessageSource通過Locale來解析獲取到對應的語言的資原始檔。最終可以根據傳入的語言資訊來返回對應的訊息。

標準和自定義事件

ApplicationEvent類和ApplicationListener介面提供了ApplicationContext中的事件處理。

如果一個bean實現了ApplicationListener介面,然後它被部署到上下問中,那麼每次ApplicationEvent釋出到ApplicationContext中時,bean都會收到通知。本質上,這是觀察者模型。

我們可以自定義自己的事件:

public class BlackListEvent extends ApplicationEvent {

    private String address;

    private String test;

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

    @Override
    public String toString() {
        return "{address:" + address +",text:" + test + "}";
    }

}
複製程式碼

為了釋出一個自定義的ApplicationEvent,在ApplicationEventPublisher中呼叫publishEvent()方法。通常在實現了ApplicationEventPublisherAware介面並把它註冊為一個Spring bean的時候它就完成了。下面的例子展示了這麼一個類:

public class EmailService implements ApplicationEventPublisherAware {

    //黑名單
    private List<String> blackLists;

    private ApplicationEventPublisher publisher;

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

    public void setBlackLists(List<String> blackLists) {
        this.blackLists = blackLists;
    }

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

}
複製程式碼

在配置時,Spring容器將檢測到EmailService實現了ApplicationEventPublisherAware,並將自動呼叫setApplicationEventPublisher()方法。實際上,傳入的引數將是Spring容器本身;您只需通過ApplicationEventPublisher介面與應用程式上下文進行互動。

在實際情況中可以直接使用@Service和@Autowired註解從Ioc容器中獲取到容器物件,而不需要實現ApplicationEventPublisherAware類。

這兩種我比較傾向於對ApplicationEventPublisherAware的實現,因為事件通常用於處理通用的業務,比如訊息推送或者郵件傳送,這種功能和系統的主要功能沒多大關係,因此可以將他們當成元件放置在別的packet下,降低耦合。

對事件的監聽有兩種實現方式:

  1. 實現ApplicationListener<T>介面
  2. 採用@EventListener註解

其中2方法更加快捷方便,是spring4.2所引入的新功能。相比起1。2方法可以將複數的監聽事件統一放置在一個類下面,方便管理。

實現ApplicationListener<T>介面

public class BlackListNotifier implements ApplicationListener<BlackListEvent> {
    private String notificationAddress;

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

    @Override
    public void onApplicationEvent(BlackListEvent blackListEvent) {
        System.out.println("黑名單:" + blackListEvent);
        System.out.println("通知目標使用者:" + notificationAddress);
    }
}
複製程式碼

採用@EventListener註解

public class BlackListNotifier {

    private String notificationAddress;

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

    @EventListener
    public void onApplicationEvent(BlackListEvent blackListEvent) {
        System.out.println("黑名單:" + blackListEvent);
        System.out.println("通知目標使用者:" + notificationAddress);
    }

}
複製程式碼

這兩種方法都可以實現監聽,但是我在測試的時候發現一個問題:

當我採用xml例項化bean的時候:

<beans>
    <bean id="blackListNotifier" class="com.example.springdemo.listener.BlackListNotifier">
        <property name="notificationAddress" value="blacklist@example.org" />
    </bean>

    <bean id="emailService" class="com.example.springdemo.service.impl.EmailService">
        <property name="blackLists">
            <list>
                <value>known.spammer@example.org</value>
                <value>known.hacker@example.org</value>
                <value>john.doe@example.org</value>
            </list>
        </property>
    </bean>

</beans>
複製程式碼

方法2無法獲取到監聽事件。

當我將它修改成Java類配置後就可以了:

@Configuration
public class EventConf {

    @Bean
    public BlackListNotifier blackListNotifier() {
        BlackListNotifier blackListNotifier = new BlackListNotifier();
        blackListNotifier.setNotificationAddress("unkonw@163.com");
        return blackListNotifier;
    }

}
複製程式碼

這裡文件上也沒有提,希望有知道的大佬能夠為我解答,感謝。

Spring ApplicationContext 作為Java EE RAR檔案部署

可以將Spring ApplicationContext部署為RAR檔案,將上下文和所有他所需的bean的類和JAR庫封裝在Java EE RAR部署單元中。這相當於獨立啟動一個ApplicationContext,它在Java EE環境中可以訪問Java EE服務資源。RAR部署在一些沒用頭資訊的war檔案中更自然的選擇,實際上,一個war檔案在沒有http入口的時候,那麼它就僅僅是用來在Java EE環境中啟動Spring ApplicationContext。

RAR部署在一些沒用頭資訊的war檔案中更自然的選擇,實際上,一個war檔案在沒有http入口的時候,那麼它就僅僅是用來在Java EE環境中啟動Spring ApplicationContext。

相關文章