国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Spring詳解4.容器內幕

dantezhao / 2311人閱讀

摘要:在這一步里,將配置文件的信息裝入到容器的定義注冊表中,但此時還未初始化。注冊后處理器根據反射機制從中找出所有類型的,并將它們注冊到容器后處理器的注冊表中。是屬性編輯器的注冊表,主要作用就是注冊和保存屬性編輯器。

點擊進入我的博客 1 Spring容器整體流程 1.1 ApplicationContext內部原理

AbstractApplicationContext是ApplicationContext的抽象實現類,其中最重要的是refresh()方法,它定義了容器在加載配置文件以后的各項處理過程。

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();
            // (1)初始化BeanFactory
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);
            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);
                // (2)調用工廠后處理器
                invokeBeanFactoryPostProcessors(beanFactory);
                // (3)注冊Bean后處理器
                registerBeanPostProcessors(beanFactory);
                // (4)初始化消息源
                initMessageSource();
                // (5)初始化應用上下文事件廣播器
                initApplicationEventMulticaster();
                // (6)初始化其他特殊Bean,由具體子類實現
                onRefresh();
                // (7)注冊事件監聽器
                registerListeners();
                // (8)初始化所有單實例的Bean(Lazy加載的除外)
                finishBeanFactoryInitialization(beanFactory);
                // (9)完成刷新并發布容器刷新事件
                finishRefresh();
            }

            catch (BeansException ex) {
                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();
                // Reset "active" flag.
                cancelRefresh(ex);
                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring"s core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

初始化BeanFactory:根據配置文件實例化BeanFactory,在obtainFreshBeanFactory()方法中,首先調用refreshBeanFactory()刷新BeanFactory,然后調用getBeanFactory()方法獲取BeanFactory,這兩個方法都是需要子類實現的抽象方法。在這一步里,Spring將配置文件的信息裝入到容器的Bean定義注冊表(BeanDefinitionRegistry)中,但此時Bean還未初始化。

調用工廠后處理器:根據反射機制從BeanDefinitionRegistry中找出所有BeanFactoryPostProcessor類型的Bean,并調用其postProcessBeanFactory()接口方法。

注冊Bean后處理器:根據反射機制從BeanDefinitionRegistry中找出所有BeanPostProcessor類型的Bean,并將它們注冊到容器Bean后處理器的注冊表中。

初始化消息源:初始化容器的國際化信息資源。

初始化應用上下文事件廣播器。

初始化其他特殊的Bean:這是一個鉤子方法,子類可以借助這個鉤子方法執行一些特殊的操作——如AbstractRefreshableWebApplicationContext就使用該鉤子方法執行初始化ThemeSource的操作。

注冊事件監聽器。

初始化singleton的Bean:實例化所有singleton的Bean(使用懶加載的吹),并將它們放入Spring容器的緩存中。

發布上下文刷新事件:創建上下文刷新事件,事件廣播器負責將些事件廣播到每個注冊的事件監聽器中。

1.2 Spring創建Bean流程

下圖描述了Spring容器從加載配置文件到創建一個Bean的完整流程:

ResourceLoader從存儲介質中加載Spring配置文件,并使用Resource表示這個配置文件的資源。

BeanDefinitionReader讀取Resource所指向的配置文件資源,然后解析配置文件。配置文件中每一個解析成一個BeanDefinition對象,并保存到BeanDefinitionRegistry中;

容器掃描BeanDefinitionRegistry中的BeanDefinition,使用Java的反射機制自動識別出Bean工廠后處理器(實現BeanFactoryPostProcessor接口)的Bean,然后調用這些Bean工廠后處理器對BeanDefinitionRegistry中的BeanDefinition進行加工處理。主要完成以下兩項工作:
3.1 對使用到占位符的元素標簽進行解析,得到最終的配置值,這意味對一些半成品式的BeanDefinition對象進行加工處理并得到成品的BeanDefinition對象。
3.2 對BeanDefinitionRegistry中的BeanDefinition進行掃描,通過Java反射機制找出所有屬性編輯器的Bean(實現java.beans.PropertyEditor接口的Bean),并自動將它們注冊到Spring容器的屬性編輯器注冊表中(PropertyEditorRegistry)。

Spring容器從BeanDefinitionRegistry中取出加工后的BeanDefinition,并調用InstantiationStrategy著手進行Bean實例化的工作;

在實例化Bean時,Spring容器使用BeanWrapper對Bean進行封裝,BeanWrapper提供了很多以Java反射機制操作Bean的方法,它將結合該Bean的BeanDefinition以及容器中屬性編輯器,完成Bean屬性的設置工作。

利用容器中注冊的Bean后處理器(實現BeanPostProcessor接口的Bean)對已經完成屬性設置工作的Bean進行后續加工,直接裝配出一個準備就緒的Bean。

1.3 Spring中的組件

Spring中的組件按照所承擔的角色可以劃分為兩類:

在Bean創建過程中被處理的元素:Resource、BeanDefinition、PropertyEditor以及最終的Bean。

處理上述元素的工具類:ResourceLoader、BeanDefinitionReader、BeanFactoryPostProcessor、InstantiationStrategy、BeanWrapper等。

1.4 BeanDefinition

org.springframework.beans.factory.config.BeanDefinition是配置文件元素標簽在容器中的內部表示,是與一一對應的。

一般的和父用RootBeanDefinition表示,而子用ChildBeanDefinition表示。

一般情況下,BeanDefinition只在容器啟動時加載并解析,除非容器重啟或刷新。當然用戶也可以在運行時通過編程調整BeanDefinition的定義。

創建BeanDefinition主要包括兩個步驟:

利用BeanDefinitionReader讀取承載配置信息的Resource,通過XML解析器解析配置信息的DOM對象,簡單地每個生成對應地BeanDefinition對象。但是這里生成的BeanDefinition可能是半成品,因為在配置文件中,可能通過占位符變量引用外部屬性文件的屬性,這些占位符變量在這一步里還沒有被解析出來。

利用容器中注冊的BeanFactoryPostProcessor對半成品的BeanDefinition進行加工處理,將以占位符表示的配置解析為最終的實際值,這樣半成品的BeanDefinition就成為成品的BeanDefinition。

1.5 InstantiationStrategy

org.springframework.beans.factory.support.InstantiationStrategy負責根據BeanDefinition對象創建一個Bean實例。

InstantiationStrategy僅負責實例化Bean(相當于new的操作),不會設置的Bean屬性,所以InstantiationStrategy返回的并不是最終的Bean實例,還需要通過BeanWrapper進行屬性的設置。

SimpleInstantiationStrategy是最常用的實例化策略,通過使用Bean的默認構造方法、帶參數的構造方法或工廠方法創建Bean的實例。

CglibSubclassingInstantiationStrategy利用CGLib類庫為Bean動態生成子類,在子類中生成方法注入的邏輯,然后使用這個動態生成的子類創建Bean的實例。

1.6 BeanWrapper

BeanWrapper相當于一個代理器,Spring委托BeanWrapper完成Bean屬性填充工作。

PropertyAccessor:屬性訪問接口定義了各種訪問Bean屬性的方法,如getPropertyValue、setPropertyValue等。

PropertyEditorRegistry:是屬性編輯器的注冊表,主要作用就是注冊和保存屬性編輯器。

BeanWrapperImpl:一個BeanWrapperImpl實例內部封裝了兩類組件——被封裝的待處理的Bean和一套用于設置Bean屬性的屬性編輯器。BeanWrapperImpl的三重身份——Bean的包裹器、屬性訪問器和屬性編輯器注冊表。

Spring首先從容器的BeanDefinitionRegistry中獲取對應的BeanDefinition,然后從BeanDefinition中獲取Bean屬性的配置信息PropertyValue,然后使用屬性編輯器對PropertyValue進行轉換以得到Bean的屬性值。

2 屬性編輯器

我們在配置文件中配置的都是字面值,如果把它們轉換成對應數據類型(如double、int)的值或對象呢?

2.1 JavaBean的屬性編輯器

任何實現了java.beans.PropertyEditor接口的類都是屬性編輯器,其主要功能就是將外部的設置值轉換成JVM內部的對應類型。

PropertyEditor

PropertyEditor是屬性編輯器的接口,它規定了將外部設置值轉換為內部JavaBean屬性值的接口方法,是內部屬性值和外部設置值的橋梁。

Object getValue():返回屬性的當前值。基本類型被封裝成對應的封裝類實例。

void setValue(Object newValue):設置屬性的值,基本類型以封裝類傳入。

String getAsText():將屬性對象用一個字符串表示,以便外部的屬性編輯器能以可視化的方式顯示。缺省返回null,表示該屬性不能以字符串表示。

void setAsText(String text):用一個字符串去更新屬性的內部值,這個字符串一般從外部屬性編輯器傳入。

String[] getTags():返回表示有效屬性值的字符串數組(如boolean屬性對應的有效Tag為true和false),以便屬性編輯器能以下拉框的方式顯示出來。缺省返回null,表示屬性沒有匹配的字符值有限集合。

String getJavaInitializationString():為屬性提供一個表示初始值的字符串,屬性編輯器以此值作為屬性的默認值。

我們一般不去直接實現PropertyEditor,而是擴展PropertyEditorSupport來實現自己類。

BeanInfo

BeanInfo主要描述了JavaBean的哪些屬性可以編輯及對應的屬性編輯器。BeanInfo和JavaBean的對應關系通過二者命名規范確定:對應JavaBean的BeanInfo的命名規范應該是BeanInfo,如Car對應的BeanInfo為CarBeanInfo。

JavaBean的每個屬性對應一個屬性描述器PropertyDescriptor。

BeanInfo最重要的方法就是PropertyDescriptor[] getPropertyDescriptors(),該方法返回JavaBean的屬性描述數組。

BeanInfo接口常用其實現類SimpleBeanInfo,可以擴展此類實現功能。

PropertyEditorManager

JavaBean規范提供了一個默認的屬性編輯器PropertyEditorManager,保存一些常見類型的屬性編輯器。

2.2 Spring屬性編輯器

Spring為常見的屬性類型提供了默認的屬性編輯器PropertyEditorRegistrySupport,里邊有多個用于保存屬性編輯器的Map類型變量,鍵為屬性類型,值為對應的屬性編輯器實例。常見的類型如下所示。

類 別 說 明
基本數據類型 如:boolean、byte、short、int等;
基本數據類型封裝類 如:Long、Character、Integer等;
兩個基本數據類型的數組 char[]和byte[];
大數類 BigDecimal和BigInteger
集合類 為5種類型的集合類Collection、Set、SortedSet、List和SortedMap提供了編輯器
資源類 用于訪問外部資源的8個常見類Class、Class[]、File、InputStream、Locale、Properties、Resource[]和URL
2.3 自定義屬性編輯器

Step1:我們可以通過擴展java.beans.PropertyEditorSupport類,并覆蓋其中的setAsText()方法即可自定義屬性編輯器。

class KFCWaitress {
    private KFCCombo kfcCombo;
    // getters & setters
}

class KFCCombo {
    private String burger;
    private String drink;
    // getters & setters
}

/**
 * KFCCombo的Editor
 */
class KFCComboEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        // 將字面值轉換為屬性類型對象
        String[] textArr = text.split(",");
        KFCCombo kfcCombo = new KFCCombo();
        kfcCombo.setBurger(textArr[0]);
        kfcCombo.setDrink(textArr[1]);

        // 調用父類的setValue()方法設置轉換后的屬性對象
        setValue(kfcCombo);
    }
}

Step2:如果使用BeanFactory需要手動調用registerCustomEditor(class requiredType, PropertyEditor propertyEditor)方法注冊自定義的屬性編輯器;如果使用ApplicationContext,只需要在配置文件中通過CustomEditorConfigurer注冊即可。



    
        
            
            
        
    



    
    

在(3)處,直接通過一個字符串配置一個Bean。BeanWrapper在設置KFCCombo類型的屬性時,將會檢索自定義屬性編輯器的注冊表,如果發現有KFCCombo屬性類型有對應的屬性編輯器時,就會使用該方法的setAsText()轉換該對象。

3 使用外部屬性文件

Spring提供了一個PropertyPlaceholderConfigurer來引用外部屬性文件,它實現了BeanFactoryPostProcessor接口,因此也是一個Bean工廠后處理器。

3.1 使用PropertyPlaceholderConfigurer
簡單的例子

通過PropertyPlaceholderConfigurer并引入屬性文件,實現使用屬性名來引用屬性值。

    
    
        
        
    
    
    
    

    
    
        
    
PropertyPlaceholderConfigurer的其他屬性

location:如果只有一個屬性文件,則直接使用location屬性指定就可以了;如果有多個屬性文件,則可以通過locations屬性進行設置。可以像配置List一樣配置locations屬性。

fileEncoding:屬性文件的編碼格式。Spring使用操作系統默認編碼讀取屬性文件。如果屬性文件采用了特殊編碼,則需要通過該屬性顯示指定。

order:如果配置文件中定義了多個PropertyPlaceholderConfigurer,則通過該屬性指定優先順序。

placeholderPrefix:在上面的例子中,通過${屬性名}引用屬性文件中的屬性項,其中${為默認的占位符前綴,可以根據需要改為其他的前綴符。

placeholderSuffix:占位符后綴,默認為}

@Value引用屬性

在使用基于注解配置Bean時,可以通過@Value注解為Bean的成員變量或方法入參自動注入容器中已有的屬性,也可以使用@Value注入字面值。

public class Main {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(“com.ankeetc.spring);

        System.out.println(applicationContext.getBean(Cat.class).getName());
    }
}

@Configuration
class Config {
    @Bean
    public PropertyPlaceholderConfigurer configurer() {
        PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer();
        configurer.setLocation(new PathMatchingResourcePatternResolver().getResource("classpath:application.properties"));
        configurer.setFileEncoding("utf-8");
        return configurer;
    }
}

@Component
class Cat {
    @Value("${name}")
    private String name;

    public String getName() {
        return name;
    }
}
3.2 使用加密的屬性文件

如果屬性是敏感的,一般不允許使用明文形式保存,此時需要對屬性進行加密.PropertyPlaceHolderConfigurer繼承自PropertyResourceConfigurer類,后者有幾個有用的protected方法(方法默認是空的即不會轉換),用于在屬性使用之前對屬性列表中的屬性進行轉換。

void convertProperties(Properties props):屬性文件的所有屬性值都封裝在props中,覆蓋該方法,可以對所有的屬性值進行轉換處理。

String convertProperty(String propertyName, String propertyValue):在加載屬性文件并讀取文件中的每個屬性時,都會調用此方法進行轉換處理。

String convertPropertyValue(String originalValue):和上一個方法類似,只不過沒有傳入屬性名。

簡單例子
public class Main {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.ankeetc.spring");
        // userName沒有被改變
        System.out.println(applicationContext.getBean(DataSource.class).getUserName());
        // password值被改變
        System.out.println(applicationContext.getBean(DataSource.class).getPassword());
    }
}

@Component
class DataSource {
    @Value("${userName}")
    private String userName;
    @Value("${password}")
    private String password;

    public String getUserName() {
        return userName;
    }

    public String getPassword() {
        return password;
    }
}

@Configuration
class Config {
    @Bean
    public EncryptPropertyPlaceholderConfigurer encryptPropertyPlaceholderConfigurer() {
        EncryptPropertyPlaceholderConfigurer configurer = new EncryptPropertyPlaceholderConfigurer();
        configurer.setLocation(new PathMatchingResourcePatternResolver().getResource("classpath:application.properties"));
        configurer.setFileEncoding("utf-8");
        return configurer;
    }
}

class EncryptPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
    @Override
    protected String convertProperty(String propertyName, String propertyValue) {
        if ("password".equals(propertyName)) {
            // 在此過濾并實現相關的揭秘邏輯
            return "Decrypt" + propertyValue;
        } else {
            return propertyValue;
        }
    }
}
3.3 屬性文件

可以在屬性文件中使用${}來實現屬性之間的相互引用

如果一個屬性值太長,可以在行后添加將屬性分為多行

dbName=myDatabase
url=jdbc:mysql://localhost:3306/${dbName}
4 應用Bean的屬性值
基于XML的配置

在XML配置文件中,可以使用#{beanName.propName}的方式引用其他Bean的屬性值。

public class Main {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:beans.xml");
        System.out.println(applicationContext.getBean(Application.class).getDatabaseName());
        System.out.println(applicationContext.getBean(Application.class).getDatabasePassword());
    }
}

class DatabaseConfig {
    private String userName;
    private String password;
    // getters & setters
}

class Application {
    private String databaseName;
    private String databasePassword;
    // getters & setters
}
    
        
        
    

    
    
        
        
    
基于注解和Java類的配置

使用@Value("#{beanName.propName}")的形式也可以引用其他類的屬性值。

@Component
class Application {
    @Value("#{databaseConfig.userName}")
    private String databaseName;
    @Value("#{databaseConfig.password}")
    private String databasePassword;
}
5 國際化信息

國際化信息的含義是根據不同的地區語言類型返回不同的信息,簡單來說就是為每種語言配置一套對應的資源文件。

5.1 基礎知識
本地化類java.util.Locale

國際化信息也稱為本地化信息,由java.util.Locale類表示一個本地化對象。它由語言參數和國家/地區參數構成。

語言參數:每種語言由兩位小寫字母表示,如zhen

國家/地區參數:用兩個大寫字母表示,如CNTWHKENUS

本地化工具類

java.util包下的NumberFormat、DateFormat、MessageFormat都支持本地化的格式化操作,而且MessageFormat還支持占位符的格式化操作。

ResourceBundle

使用ResourceBundle可以訪問不同本地化資源文件,文件名必須按照如下格式來命名:資源名_語言代碼_國家地區代碼.properties,其中語言代碼和國家/地區代碼是可選的。假如默認資源文件的文件名為application.properties,則中文中國大陸的資源文件名為application_zh_CN.properties

public class Main {
    public static void main(String[] args) {
        // 如果找不到對應的資源文件,將會使用默認的資源文件
        // getBundle是類路徑的文件名,而且不帶.properties后綴
        ResourceBundle zhCN = ResourceBundle.getBundle("application", Locale.SIMPLIFIED_CHINESE);
        ResourceBundle enUs = ResourceBundle.getBundle("application", Locale.US);
        System.out.println(zhCN.getString("name"));
        System.out.println(enUs.getString("name"));
    }
}
5.2 MessageSource

Spring定義了訪問國際化信息的MessageSource接口,主要方法如下:

String getMessage(String code, Object[] args, String defaultMessage, Locale locale):code表示國際化資源中的屬性名;args用于傳遞格式化串占位符所用的運行期參數;當在資源找不到對應屬性名時,返回defaultMessage參數所指定的默認信息;locale表示本地化對象;

String getMessage(String code, Object[] args, Locale locale)?throws NoSuchMessageException:與上面的方法類似,只不過在找不到資源中對應的屬性名時,直接拋出NoSuchMessageException異常;

String getMessage(MessageSourceResolvable resolvable, Locale locale)?throws NoSuchMessageException:MessageSourceResolvable將屬性名、參數數組以及默認信息封裝起來,它的功能和第一個接口方法相同。

類結構

HierarchicalMessageSource接口的作用是建立父子層級的MessageSource結構。

StaticMessageSource主要用于程序測試,它允許通過編程的方式提供國際化信息。

ResourceBundleMessageSource實現類允許通過beanName指定一個資源名(包括類路徑的全限定資源名),或通過beanNames指定一組資源名。

ReloadableResourceBundleMessageSource提供了定時刷新功能,允許在不重啟系統的情況下,更新資源的信息。

public class Main {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        System.out.println(applicationContext.getBean("myMessageSource1", MessageSource.class).getMessage("name", null, Locale.US));
        System.out.println(applicationContext.getBean("myMessageSource1", MessageSource.class).getMessage("name", null, Locale.SIMPLIFIED_CHINESE));
    }
}
    
    
        
    

    
    
        
            
                application
            
        
    

    
    
    
        
        
    
5.3 容器級的國際化信息

由于ApplicationContext本身也繼承了MessageSource接口,所以ApplicationContext的所有實現類本身也是一個MessageSource對象,國際化信息是整個容器的公共設施。
在本章(1.1 ApplicationContext內部原理)我們提到,在ApplicationContext會在initMessageSource()方法中,Spring通過反射機制找出bean名為messageSource(bean名必須是messageSource)且類型為MessageSource子類的Bean,將這個Bean定義的信息資源加載為容器級的國際化信息資源。

public class Main {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        System.out.println(applicationContext.getMessage("name", null, Locale.US));
    }
}
    
        
    
6 容器事件 6.1 Java的事件體系

事件體系是觀察者模式的一種具體實現方式,一共有如下幾個角色:

事件:java.util.EventObject是Java中的事件。

監聽器:java.util.EventListener是用于描述事件的接口,是一個沒有任何方法的標記接口。

事件源:事件的生產者,任何一個EventObject都有一個事件源。

事件監聽器注冊表:框架必須有一個地方來保存事件監聽器,當事件源產生事件時,就會通知這些位于注冊表中的監聽器。

事件廣播器:是事件和事件監聽器之間的橋梁,負責把事件通知給事件監聽器。

public class Main {
    public static void main(String[] args) {
        Waitress waitress = new Waitress("田二妞");
        waitress.addEventListener(new Chef("王二狗"));
        waitress.order("宮保雞丁");
        // 廚師[王二狗]收到服務員[田二妞]的訂單,開始做[宮保雞丁]
    }
}

// 一個餐廳的點單事件,繼承了EventObject
class OrderEventObject extends EventObject {
    private String order;

    public String getOrder() {
        return order;
    }

    public OrderEventObject(Object source, String order) {
        super(source);
        this.order = order;
    }
}

// 服務員是事件源,由她產生點單事件
class Waitress {
    private String name;
    // 服務員維護了所有在餐廳的廚師,即監聽器注冊表
    private List eventListenerList = new ArrayList<>();

    public Waitress(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void addEventListener(Chef chef) {
        eventListenerList.add(chef);
    }

    // 該方法是廣播器,即把點單事件通知給注冊表中的全部廚師
    public void order(String order) {
        OrderEventObject orderEventObject = new OrderEventObject(this, order);
        eventListenerList.forEach(var -> var.cook(orderEventObject));
    }
}

// 廚師是事件監聽器
class Chef implements EventListener {
    private String name;

    public Chef(String name) {
        this.name = name;
    }

    // 監聽到點單事件并作出相關反應
    public void cook(EventObject o) {
        System.out.println(String.format("廚師[%s]收到服務員[%s]的訂單,開始做[%s]", name, ((Waitress)o.getSource()).getName(), ((OrderEventObject)o).getOrder()));
    }
}
6.2 Spring事件類結構
事件類

ApplicationEvent:Spring的事件類的基類,其類結構如下所示。

ApplicationContextEvent:容器事件,它擁有4個子類分別表示容器的啟動、刷新、停止、關閉事件。

RequestHandleEvent:與Web應用有關的事件,當一個HTTP請求被處理后產生該事件。只有在web.xml中定義了DispatcherServlet時才會產生該事件。它有兩個子類,分別代表Servlet和Portlet的請求事件。

事件監聽器接口

ApplicationListener:該接口只定義了一個方法onApplicationEvent(E event),該方法接受ApplicationEvent事件對象,在該方法中編寫事件的響應處理邏輯。

SmartApplicationListener:定義了兩個方法boolean supportsEventType(Class eventType):指定監聽器支持哪種類型的容器事件,即它只會對該類型的事件做出響應;boolean supportsSourceType(Class sourceType):指定監聽器僅對何種事件源對象做出響應。

GenericApplicationListener:Spring 4.2新增的類,使用可解析類型ResolvableType增強了對范型的支持。

事件廣播器

當發生容器事件時,容器主控程序將調用事件廣播器將事件通知給事件監聽器注冊表中的事件監聽器。Spring為事件廣播器提供了接口和實現類。

6.3 Spring事件體系具體實現

Spring在ApplicationContext接口的抽象實現類AbstractApplicationContext中完成了事件體系的搭建。AbstractApplicationContext擁有一個applicationEventMulticaster(應用上下文事件廣播器)成員變量,它提供了容器監聽器的注冊表。AbstractApplicationContext在refresh()這個容器啟動啟動方法中通過以下3個步驟搭建了事件的基礎設施:

public void refresh() throws BeansException, IllegalStateException {
    // (5)初始化應用上下文事件廣播器
    initApplicationEventMulticaster();
    // (7)注冊事件監聽器
    registerListeners();
    // (9)完成刷新并發布容器刷新事件
    finishRefresh();
}

在(5)處,Spring初始化事件的廣播器,可以在配置文件中為容器定義一個自定義的事件廣播器,只要實現ApplicationEventMulticaster即可,Spring會通過反射機制將其注冊容器的事件廣播器。如果沒有找到配置的外部事件廣播器,則Spring自動使用SimpleApplicationEventMulticaster作為事件廣播器。

在(7)處,Spring根據反射機制,從BeanDefinitionRegistry中找出所有實現ApplicationListener的Bean,將它們注冊為容器的事件監聽器,即將其添加到事件廣播器所提供的事件監聽器注冊表中

在(9)處,容器啟動完成,調用事件發布接口向容器中所有的監聽器發布事件

6.4 一個例子

假如我們希望容器刷新時打印一行文字,可以繼承GenericApplicationListener并實現相關方法。

public class Main {
    public static void main(String[] args) {
        // new AnnotationConfigApplicationContext()會調用refresh方法,MyListener會監聽到并處理
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.ankeetc.spring");
        // stop事件不會被監聽到
        ((AnnotationConfigApplicationContext) applicationContext).stop();
    }
}

@Component
class MyListener implements GenericApplicationListener {
    // 判斷是否是刷新事件
    @Override
    public boolean supportsEventType(ResolvableType eventType) {
        return ResolvableType.forClass(ContextRefreshedEvent.class).equals(eventType);
    }

    @Override
    public boolean supportsSourceType(Class sourceType) {
        return true;
    }

    // 在此實現監聽到相關事件的處理邏輯
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("Hello world");
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72900.html

相關文章

  • 從小白程序員一路晉升為大廠高級技術專家我看過哪些書籍?(建議收藏)

    摘要:大家好,我是冰河有句話叫做投資啥都不如投資自己的回報率高。馬上就十一國慶假期了,給小伙伴們分享下,從小白程序員到大廠高級技術專家我看過哪些技術類書籍。 大家好,我是...

    sf_wangchong 評論0 收藏0
  • Spring技術內幕筆記(2):Spring MVC 與 Web

    摘要:與容器與容器的關系為容器提供了宿主環境。容器通過初始化建立,是注冊在容器中的監聽器,當容器初始化時,監聽器會收到該事件從而發起容器的初始化。是處理請求的轉發器,從而響應的請求。接著將數據進行合并,然后將數據放入中進行暴露。 Spring MVC 與 Web IoC容器與Web容器的關系 ServletContext為IoC容器提供了宿主環境。IoC容器通過ContexLoaderLis...

    YancyYe 評論0 收藏0
  • 那些年,我的前端/Java后端書單

    摘要:全文為這些年,我曾閱讀深入理解過或正在閱讀學習即將閱讀的一些優秀經典前端后端書籍。當然,如果您喜歡這篇文章,可以動手點點贊或者收藏。 全文為這些年,我曾閱讀、深入理解過(或正在閱讀學習、即將閱讀)的一些優秀經典前端/Java后端書籍。全文為純原創,且將持續更新,未經許可,不得進行轉載。當然,如果您喜歡這篇文章,可以動手點點贊或者收藏。 基礎 基礎書籍 進階 進階階段,深入學習的書...

    fxp 評論0 收藏0
  • 那些年,我的前端/Java后端書單

    摘要:全文為這些年,我曾閱讀深入理解過或正在閱讀學習即將閱讀的一些優秀經典前端后端書籍。當然,如果您喜歡這篇文章,可以動手點點贊或者收藏。 全文為這些年,我曾閱讀、深入理解過(或正在閱讀學習、即將閱讀)的一些優秀經典前端/Java后端書籍。全文為純原創,且將持續更新,未經許可,不得進行轉載。當然,如果您喜歡這篇文章,可以動手點點贊或者收藏。 基礎 基礎書籍 進階 進階階段,深入學習的書...

    Tecode 評論0 收藏0
  • 那些年,我的前端/Java后端書單

    摘要:全文為這些年,我曾閱讀深入理解過或正在閱讀學習即將閱讀的一些優秀經典前端后端書籍。當然,如果您喜歡這篇文章,可以動手點點贊或者收藏。 全文為這些年,我曾閱讀、深入理解過(或正在閱讀學習、即將閱讀)的一些優秀經典前端/Java后端書籍。全文為純原創,且將持續更新,未經許可,不得進行轉載。當然,如果您喜歡這篇文章,可以動手點點贊或者收藏。 基礎 基礎書籍 進階 進階階段,深入學習的書...

    VPointer 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<