AbstractApplicationContext是ApplicationContext的抽象實現類,其中最重要的是refresh()方法,它定義了容器在加載配置文件之後的各項處理過程。java
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(); } } }
下圖描述了Spring容器從加載配置文件到建立一個Bean的完整流程:
mysql
Spring中的組件按照所承擔的角色能夠劃分爲兩類:web
org.springframework.beans.factory.config.BeanDefinition
是配置文件<bean>元素標籤在容器中的內部表示,是與<bean>一一對應的。
org.springframework.beans.factory.support.InstantiationStrategy
負責根據BeanDefinition對象建立一個Bean實例。
咱們在配置文件中配置的都是字面值,若是把它們轉換成對應數據類型(如double、int)的值或對象呢?spring
任何實現了java.beans.PropertyEditor
接口的類都是屬性編輯器,其主要功能就是將外部的設置值轉換成JVM內部的對應類型。sql
PropertyEditor是屬性編輯器的接口,它規定了將外部設置值轉換爲內部JavaBean屬性值的接口方法,是內部屬性值和外部設置值的橋樑。編程
BeanInfo主要描述了JavaBean的哪些屬性能夠編輯及對應的屬性編輯器。BeanInfo和JavaBean的對應關係經過兩者命名規範肯定:對應JavaBean的BeanInfo的命名規範應該是<Bean>BeanInfo
,如Car對應的BeanInfo爲CarBeanInfo。數組
JavaBean規範提供了一個默認的屬性編輯器PropertyEditorManager,保存一些常見類型的屬性編輯器。緩存
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 |
Step1:咱們能夠經過擴展java.beans.PropertyEditorSupport類,並覆蓋其中的setAsText()方法便可自定義屬性編輯器。app
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註冊便可。
<!-- (1)配置自動註冊屬性編輯器的CustomEditorConfigurer --> <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="customEditors"> <map> <!-- (2)屬性編輯器對應的屬性類型 --> <entry key="com.ankeetc.spring.KFCCombo" value="com.ankeetc.spring.KFCComboEditor"/> </map> </property> </bean> <bean id="myWaitress" class="com.ankeetc.spring.KFCWaitress"> <!-- (3)該屬性將使用(2)處的屬性編輯器完成屬性填充操做 --> <property name="kfcCombo" value="Zinger Burger,Mirinda"/> </bean>
在(3)處,直接經過一個字符串配置一個Bean。BeanWrapper在設置KFCCombo類型的屬性時,將會檢索自定義屬性編輯器的註冊表,若是發現有KFCCombo屬性類型有對應的屬性編輯器時,就會使用該方法的setAsText()
轉換該對象。
Spring提供了一個PropertyPlaceholderConfigurer來引用外部屬性文件,它實現了BeanFactoryPostProcessor接口,所以也是一個Bean工廠後處理器。
經過PropertyPlaceholderConfigurer並引入屬性文件,實現使用屬性名來引用屬性值。
<!-- 建立PropertyPlaceholderConfigurer的Bean並引入屬性文件 --> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:application.properties"/> <property name="fileEncoding" value="utf-8"/> </bean> <!-- 也可使用這種方式引用屬性文件 --> <context:property-placeholder location="classpath:application.properties" file-encoding="utf-8"/> <!-- 經過屬性名引用屬性值 --> <bean id="myCat" class="com.ankeetc.spring.Cat"> <property name="name" value="${name}"/> </bean>
${屬性名}
引用屬性文件中的屬性項,其中${
爲默認的佔位符前綴,能夠根據須要改成其餘的前綴符。}
。在使用基於註解配置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; } }
若是屬性是敏感的,通常不容許使用明文形式保存,此時須要對屬性進行加密.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; } } }
${}
來實現屬性之間的相互引用\
將屬性分爲多行dbName=myDatabase url=jdbc:mysql://localhost:3306/${dbName}
在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 }
<bean id="databaseConfig" class="com.ankeetc.spring.DatabaseConfig"> <property name="userName" value="lucas"/> <property name="password" value="123456"/> </bean> <!--經過#{databaseConfig.propName}的方式引用databaseConfig的屬性值--> <bean id="applicationConfig" class="com.ankeetc.spring.Application"> <property name="databaseName" value="#{databaseConfig.userName}"/> <property name="databasePassword" value="#{databaseConfig.password}"/> </bean>
使用@Value("#{beanName.propName}")
的形式也能夠引用其餘類的屬性值。
@Component class Application { @Value("#{databaseConfig.userName}") private String databaseName; @Value("#{databaseConfig.password}") private String databasePassword; }
國際化信息的含義是根據不一樣的地區語言類型返回不一樣的信息,簡單來講就是爲每種語言配置一套對應的資源文件。
國際化信息也稱爲本地化信息,由java.util.Locale
類表示一個本地化對象。它由語言參數和國家/地區參數構成。
zh
、en
CN
、TW
、HK
、EN
、US
java.util包下的NumberFormat、DateFormat、MessageFormat都支持本地化的格式化操做,並且MessageFormat還支持佔位符的格式化操做。
使用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")); } }
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)); } }
<!--可使用basename指定資源文件的路徑--> <bean id="myMessageSource1" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="application"/> </bean> <!--可使用basenames指定多組資源文件的路徑--> <bean id="myMessageSource2" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basenames"> <list> <value>application</value> </list> </property> </bean> <!--當使用ReloadableResourceBundleMessageSource可使用cacheSeconds指定刷新週期--> <!--刷新週期默認爲-1即不刷新,單位是秒--> <bean id="myMessageSource3" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="application"/> <property name="cacheSeconds" value="100"/> </bean>
因爲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)); } }
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="application"/> </bean>
事件體系是觀察者模式的一種具體實現方式,一共有以下幾個角色:
java.util.EventObject
是Java中的事件。java.util.EventListener
是用於描述事件的接口,是一個沒有任何方法的標記接口。
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<Chef> 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())); } }
onApplicationEvent(E event)
,該方法接受ApplicationEvent事件對象,在該方法中編寫事件的響應處理邏輯。boolean supportsEventType(Class<? extends ApplicationEvent> eventType)
:指定監聽器支持哪一種類型的容器事件,即它只會對該類型的事件作出響應;boolean supportsSourceType(Class<?> sourceType)
:指定監聽器僅對何種事件源對象作出響應。
當發生容器事件時,容器主控程序將調用事件廣播器將事件通知給事件監聽器註冊表中的事件監聽器。Spring爲事件廣播器提供了接口和實現類。
Spring在ApplicationContext接口的抽象實現類AbstractApplicationContext中完成了事件體系的搭建。AbstractApplicationContext擁有一個applicationEventMulticaster(應用上下文事件廣播器)成員變量,它提供了容器監聽器的註冊表。AbstractApplicationContext在refresh()這個容器啓動啓動方法中經過如下3個步驟搭建了事件的基礎設施:
public void refresh() throws BeansException, IllegalStateException { // (5)初始化應用上下文事件廣播器 initApplicationEventMulticaster(); // (7)註冊事件監聽器 registerListeners(); // (9)完成刷新併發布容器刷新事件 finishRefresh(); }
假如咱們但願容器刷新時打印一行文字,能夠繼承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; } }