1.13,Environment
Environment介面是集成在容器中的抽象存在,它表現為應用程式環境的兩個關鍵方面:profiles和properties,
1.13.1,Bean Definition Profiles
Bean Definition Profiles在核心容器中提供了一種機制,該機制允許在不同environment中注冊不同的Bean,
說白了其實就是判斷 spring.profiles.active 的值
這個值可以有多個中間用 , 隔開就可以
“environment”一詞對不同的用戶而言可能意味著不同的含義,并且此功能可以在許多用例中提供幫助,包括:
- 在開發中針對記憶體中的資料源進行作業,而不是在進行QA或生產時從JNDI查找相同的資料源,
- 僅在將應用程式部署到性能環境中時注冊監視基礎結構,
- 為客戶A和客戶B部署注冊bean的自定義實作,
考慮實際應用中需要使用的第一個用例DataSource,
在測驗環境中,配置可能類似于以下內容:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
現在,假設該應用程式的資料源已在生產應用程式服務器的JNDI目錄中注冊,請考慮如何將該應用程式部署到QA或生產環境中,
現在,我們的dataSource bean看起來像下面的清單:
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
問題是如何根據當前環境在使用這兩種變體之間進行切換,
隨著時間的流逝,Spring用戶已經設計出許多方法來完成此任務,通常依賴于系統環境變數和
Bean Definition Profiles是一項核心容器功能,可提供此問題的解決方案,
使用 @Profile
@Profile注解能做到只有在您指定的一個或多個指定的概要檔案處于活動狀態時才對該組件進行注冊,
使用前面的示例,我們可以重寫資料源配置,如下所示:
@Configuration
@Profile("development")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
如前所述,對于@Bean方法,您通常選擇使用編程式JNDI查找,方法是使用Spring的JNDIMplate/JNDilocatorDeleteGate幫助器,
或者使用前面顯示的直接JNDIInitialContext用法,
而不是JndiObjectFactoryBean變數,因為factoryBean方法回傳的是FactoryBean型別,而不是DataSource型別,
原理解釋:
1.@Profile注解中指定了@Conditional注解中的ProfileCondition.class
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
2.首先在加載bean的時候發現有方法判斷是否應該調過當前bean
if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
configClass.skippedBeanMethods.add(methodName);
return;
}
在shouldSkip中會查詢當前bean的所有的condition
并回圈執行每個condition的matches
而@Profile的condition的matches如下所示
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
//此處 呼叫了environment的propertySources
//判斷當前配置中的所有的propertySources是否含有spring.profiles.active屬性
//有值的話就將它設定到environment的activeProfiles屬性中
//再判斷當前類的@Profile注解中的值是否被包含在activeProfiles屬性內
//如果被包含則回傳true
if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
}
組態檔字串可以包含簡單的組態檔名稱(例如production)或組態檔運算式,
組態檔運算式允許表達更復雜的組態檔邏輯(例如production & us-east),
概要檔案運算式中支持以下運算子:
- !:組態檔的邏輯“非”
- &:組態檔的邏輯“與”
- |:組態檔的邏輯“或”
您不能在不使用括號的情況下混合使用 & 和 | 運算子,
例如, production & us-east | eu-central 不是有效的運算式,
它必須表示為 production & (us-east | eu-central),
您可以將其@Profile用作元注解,以創建自定義的組合注解,
以下示例定義了一個自定義 @Production批注,您可以將其用作@Profile("production")的替代品
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
如果一個@Configuration類被標記了一個@Profile,則除非一個或多個指定的組態檔處于活動狀態,
否則將忽略與該類關聯的所有@Bean方法和 @Import注解,
如果一個@Component或@Configuration類標記有@Profile({"p1", "p2"}),
則除非已激活組態檔“ p1”或“p2”,否則不會注冊或處理該類,
如果給定的組態檔以NOT運算子(!)為前綴,
則僅在該組態檔未激活時才注冊帶注解的元素,
例如,給定@Profile({"p1", "!p2"}),
如果組態檔“ p1”處于活動狀態或組態檔“p2”未處于活動狀態,
則會進行注冊,
@Profile 也可以在方法級別宣告,作用范圍僅僅是配置類的一個特定Bean,
如以下示例所示:
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development")
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean("dataSource")
@Profile("production")
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
- 該standaloneDataSource方法僅在development組態檔中可用,
- 該jndiDataSource方法僅在production組態檔中可用,
對于@Bean方法上的@Profile,可能會應用一個特殊的場景(在同一個配置類中):
-----對于具有相同Java方法名的多載@Bean方法(類似于建構式多載),需要在所有多載方法上宣告相同的@Profile條件,宣告相同的條件并不是因為可以自動選擇多載方法,是因為這一批多載方法都會因為第一個方法的校驗不合格就全部不通過,如果第一個合格才會往下繼續判斷是否可以用其他的多載方法進行bean的注冊,
-----如果條件不一致,則只有多載方法中第一個宣告的條件才生效,
因此,@Profile不能用于選擇具有特定引數簽名的多載方法,
同一bean的所有工廠方法之間的決議在創建時遵循Spring的建構式決議演算法,
如果您想定義具有不同組態檔條件的替代bean,請使用指向相同bean名稱的不同@Bean方法名,方法是使用@Bean的name屬性,如前面的示例所示,
如果引數簽名都相同(例如,所有變數都沒有arg工廠方法),那么這是在一個有效的Java類中首先表示這種安排的唯一方法(因為只能有一個特定名稱和引數簽名的方法),
分析:
// 判斷當前@Bean是否需要跳過
// 這里的判斷順序就是 Config類中的@Bean代碼的先后順序跟@Order無關
if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
configClass.skippedBeanMethods.add(methodName);
return;
}
//當同名方法出現并在之前被跳過之后 這里會判斷skippedBeanMethods屬性是否包含并直接跳過
//所以不管同一個配置類中后續的同名方法是否帶有注解都將不再處理
if (configClass.skippedBeanMethods.contains(methodName)) {
return;
}
XML Bean定義組態檔XML對應項是元素的profile屬性
我們前面的示例配置可以用兩個XML檔案重寫,如下所示:
<beans profile="development"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="...">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
也可以避免
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<!-- other bean definitions -->
<beans profile="development">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
spring-bean.xsd已經做了限制,只允許這樣的元素作為檔案中的最后一個元素,這將有助于提供靈活性,并且不會導致XML檔案的混亂,
XML對應項不支持前面描述的組態檔運算式
-----例如:(production & (us-east | eu-central)),
但是,可以通過使用!運算子來取消組態檔,
也可以通過嵌套組態檔來應用邏輯“與”,如以下示例所示:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<!---如果production和 us-east組態檔都處于活動狀態,則dataSource會被注冊-->
<beans profile="production">
<beans profile="us-east">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
</beans>
Default Profile
默認組態檔表示默認情況下啟用的組態檔,考慮以下示例:
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
如果沒有組態檔被激活,dataSource被創建,
您可以看到這是為一個或多個bean提供默認定義的一種方法,
如果啟用了任何組態檔,則默認組態檔不適用,
您可以通過setDefaultProfiles() 或者使用宣告性地使用spring.profiles.default屬性來更改默認組態檔的名稱,
1.13.2,PropertySource抽象化
Spring的Environment抽象提供了對屬性源可配置層次結構的搜索操作,
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
在前面的代碼片段中,我們看到了一種方式來詢問Spring是否為當前環境定義了該my-property屬性,
為了回答這個問題,Environment物件在一組PropertySource物件上執行搜索 ,
PropertySource是對任何鍵-值對源的簡單抽象,Spring的StandardEnvironment配置有兩個PropertySource物件
- 一個代表JVM系統屬性集(System.getProperties())
- 一個代表系統環境變數集(System.getenv()),
這些默認屬性源是為StandardEnvironment提供的,供獨立應用程式使用,
StandardServletEnvironment使用了附加的默認屬性源,包括servlet配置和servlet背景關系引數,
它可以選擇啟用JndiPropertySource,
所執行的搜索是分層的,
默認情況下,系統屬性優先于環境變數,
因此,如果在呼叫env.getProperty(“my-property”)期間,恰好在兩個位置都設定了my-property屬性,則系統屬性值“勝出”并被回傳,
注意,屬性值沒有被合并,而是被前面的條目完全覆寫,
對于common StandardServletEnvironment,完整的層次結構如下所示,最高優先級的條目位于頂部:
ServletConfig引數(如果適用——例如,在DispatcherServlet背景關系的情況下)
ServletContext引數(web.xml背景關系引數項)
JNDI環境變數(java:comp/env/ entries)
JVM系統屬性(-D命令列引數)
JVM系統環境(作業系統環境變數)
最重要的是,整個機制是可配置的,
也許您具有要集成到此搜索中的自定義屬性源,
為此,請實作并實體化自己的實體PropertySource并將其添加到PropertySourcescurrent的集合中Environment,
以下示例顯示了如何執行此操作:
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
//MyPropertySource被添加了最高優先級,
sources.addFirst(new MyPropertySource());
該MutablePropertySources API公開了許多方法,
這些方法允許對屬性源集進行精確操作,
1.13.3,使用@PropertySource
@PropertySource注解提供了一種方便的宣告機制,可以將PropertySource添加到Spring的環境中,
給定一個名為app.properties的檔案,
其中包含鍵值對testbean.name=myTestBean,
下面的@Configuration類使用@PropertySource,
呼叫env.getProperty("testbean.name")會回傳myTestBean:
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
任何出現在@PropertySource資源位置的${…}占位符都會根據已經在環境中注冊的屬性源進行決議,如下面的示例所示:
//假定my.placeholder存在于已注冊的屬性源之一(例如,系統屬性或環境變數)中,則占位符將決議為相應的值,
//如果不是,則default/path用作默認值,
//如果未指定默認值并且無法決議屬性, IllegalArgumentException則拋出,
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
根據Java 8的約定,@PropertySource注解是可重復的,
但是,所有這樣的@PropertySource注解都需要在同一級別宣告,要么直接在配置類上宣告,要么作為同一自定義注解中的元注解宣告,
不推薦混合使用直接注解和元注解,因為直接注解有效地覆寫了元注解,
1.13.4,宣告中的占位符決議
過去,元素中的占位符的值只能根據JVM系統屬性或環境變數決議,
現在情況已經不一樣了,
因為環境抽象集成在整個容器中,所以很容易通過它來決議占位符,
這意味著您可以以任何您喜歡的方式配置決議程序,
您可以更改搜索系統屬性和環境變數的優先級,或者完全洗掉它們,
您還可以在適當的情況下添加您自己的屬性源,
具體地說,無論客戶屬性定義在哪里,只要它在環境中可用,以下陳述句都適用:
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>
1.15,ApplicationContext的其他功能
正如在引言中所討論的,
org.springframework.beans.factory包提供了管理和操作bean的基本功能,包括以編程的方式,
org.springframework.context 包添加了ApplicationContext介面,
該介面擴展了BeanFactory介面,
此外還擴展了其他介面,以更面向應用程式框架的風格提供額外的功能,
許多人以一種完全宣告式的方式使用ApplicationContext,甚至不是通過編程來創建它,而是依賴于支持類(如ContextLoader)來自動實體化一個ApplicationContext,作為Java EE web應用程式的正常啟動程序的一部分,
為了以更面向框架的風格增強BeanFactory的功能,背景關系包還提供了以下功能:
- 通過MessageSource介面訪問i18n風格的訊息,
- 通過ResourceLoader介面訪問資源,例如url和檔案,
- 事件發布,即通過使用ApplicationEventPublisher介面發布到實作ApplicationListener介面的bean,
- 通過HierarchicalBeanFactory介面加載多個(分層的)背景關系,讓每個背景關系都關注于一個特定的層,比如應用程式的web層,
1.15.1,國際化使用MessageSource
ApplicationContext介面擴展了一個名為MessageSource的介面,因此提供了國際化(“i18n”)功能,
Spring還提供了HierarchicalMessageSource介面,該介面可以分層決議訊息,
這些介面一起提供了Spring實作訊息決議的基礎,
在這些介面上定義的方法包括:
- String getMessage(String code, Object[] args, String default, Locale loc):
用于從MessageSource檢索訊息的基本方法,
如果未找到指定語言環境的訊息,則使用默認訊息,
通過使用標準庫提供的MessageFormat功能,傳入的任何引數都將成為替換值, - String getMessage(String code, Object[] args, Locale loc):
本質上與前面的方法相同,但有一個區別:不能指定預設訊息,
如果找不到訊息,則拋出NoSuchMessageException, - String getMessage(MessageSourceResolvable, Locale Locale):前面方法中使用的所有屬性也包裝在一個名為MessageSourceResolvable類中,可與此方法一起使用,
加載ApplicationContext時,它會自動搜索背景關系中定義的MessageSource bean,
bean的名稱必須是messageSource,
- 如果找到這樣一個bean,對前面方法的所有呼叫都將委托給訊息源,
- 如果沒有找到訊息源,ApplicationContext將嘗試查找包含同名bean的父訊息源,如果是,則使用該bean作為訊息源,
- 如果ApplicationContext找不到任何訊息源,則實體化一個空的DelegatingMessageSource,以便能夠接受對上面定義的方法的呼叫,
Spring提供了兩個訊息源實作:
ResourceBundleMessageSource和StaticMessageSource,
兩者都實作了HierarchicalMessageSource以執行嵌套訊息傳遞,
很少使用StaticMessageSource,但它提供了將訊息添加到源的編程方法,
下面的示例展示ResourceBundleMessageSource:
<beans>
<bean id="messageSource"
>
<property name="basenames">
<list>
<value>format</value>
<value>exceptions</value>
<value>windows</value>
</list>
</property>
</bean>
</beans>
這個例子假設你有所謂的三個資源包format.properties,exceptions.properties,windows.properties 在類路徑中定義,
決議訊息的任何請求均通過JDK標準的通過ResourceBundle物件決議訊息的方式來處理,
就本示例而言,假定上述兩個資源束檔案的內容如下:
#在format.properties中
message=Alligators rock!
#在exceptions.properties中
argument.required=The {0} argument is required.
下一個示例顯示了運行該MessageSource功能的程式,
請記住,所有ApplicationContext實作也是MessageSource實作,因此可以強制轉換為MessageSource介面,
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
System.out.println(message);
}
以上程式的結果輸出如下:
Alligators rock!
總之,MessageSource是在一個名為beans.xml的檔案中定義的,它存在于classpath中,
MessageSource bean定義通過其basenames屬性參考大量資源包,
在串列中傳遞給basenames屬性的三個檔案作為類路徑的根檔案存在,它們被稱為format.properties,exceptions.properties,and windows.properties,
下一個示例顯示了傳遞給訊息查找的引數,
這些引數被轉換為字串物件,并插入到查找訊息中的占位符中,
<beans>
<bean id="messageSource" >
<property name="basename" value="https://www.cnblogs.com/dabaieyangzhijidi/p/exceptions"/>
</bean>
<bean id="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", Locale.ENGLISH);
System.out.println(message);
}
}
execute()方法呼叫的結果輸出如下:
The userDao argument is required.
關于國際化(“i18n”),Spring的各種MessageSource實作遵循與標準JDK ResourceBundle相同的語言環境決議和回退規則,
簡而言之,繼續前面定義的示例messageSource,如果您希望根據英國(en-GB)地區決議訊息,您將創建名為format_en_GB.properties, exceptions_en_GB.properties, and windows_en_GB.properties,
通常,語言環境決議由應用程式的周圍環境管理,
在下面的示例中,手動指定決議(英國)訊息所對應的語言環境:
# 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);
}
運行上述程式的結果輸出如下:
Ebagum lad, the 'userDao' argument is required, I say, required.
您還可以使用MessageSourceAware介面來獲取對已定義的任何訊息源的參考,
當創建和配置bean時,在ApplicationContext中定義的任何bean實作MessageSourceAware介面的都被注入應用背景關系的MessageSourceAware介面,
作為ResourceBundleMessageSource的替代方案,Spring提供了一個ReloadableResourceBundleMessageSource類,
這個變體支持相同的bundle檔案格式,但是比基于JDK的標準ResourceBundleMessageSource實作更加靈活,
特別是,它允許從任何Spring資源位置讀取檔案(不僅僅是從類路徑),并支持bundle屬性檔案的熱重新加載(同時有效地快取它們),
有關詳細資訊,請參見ReloadableResourceBundleMessageSource javadoc,
1.15.2,標準和自定義事件
ApplicationContext中的事件處理是通過ApplicationEvent類和ApplicationListener介面提供的,
如果實作ApplicationListener介面的bean被部署到背景關系中,那么每當一個ApplicationEvent被發布到ApplicationContext時,該bean就會得到通知,
本質上,這就是標準的觀察者設計模式,
從Spring 4.2開始,事件基礎設施已經得到了顯著改進,并提供了一個基于注解的模型,
以及發布任意事件的能力(也就是說,不需要從ApplicationEvent擴展的物件),
當發布這樣的物件時,我們為您將其包裝在事件中,
表7.內置事件
| 事件 | 說明 |
|---|---|
| ContextRefreshedEvent | 在初始化或重繪ApplicationContext時發布 例如,ConfigurableApplicationContext.refresh()方法 這里,初始化意味著加載了所有bean 檢測并激活了后處理器bean 預先實體化了singleton 并且ApplicationContext物件可以使用了, 只要背景關系尚未關閉 并且所選的ApplicationContext實際上支持這種“熱”重繪 就可以多次觸發重繪 例如,XmlWebApplicationContext支持熱重繪, 但GenericApplicationContext不支持, |
| ContextStartedEvent | 在ConfigurableApplicationContext.start()方法 啟動ApplicationContext時發布, 這里,啟動意味著所有生命周期bean都收到一個顯式的啟動信號, 通常,這個信號用于在顯式停止后重新啟動bean ,但是它也可以用于啟動尚未配置為自動啟動的組件 例如,尚未在初始化時啟動的組件, |
| ContextStoppedEvent | 在ConfigurableApplicationContext.stop()方法 停止ApplicationContext時發布, “停止”意味著所有生命周期bean都收到一個顯式的停止信號, 停止的背景關系可以通過start()呼叫重新啟動, |
| ContextClosedEvent | 通過使用ConfigurableApplicationContext.close()方法 或通過JVM shutdown hook關閉ApplicationContext時發布, ,“關閉”意味著所有的單例bean將被銷毀, 一旦背景關系關閉, 它就會到達生命的終點,無法重繪或重新啟動, |
| RequestHandledEvent | 一個特定于web的事件, 告訴所有bean一個HTTP請求已經得到服務, 此事件在請求完成后發布, 此事件僅適用于使用Spring的DispatcherServlet的web應用程式, |
| ServletRequestHandledEvent | 該類的子類RequestHandledEvent 添加了Servlet-specific的背景關系資訊, |
您還可以創建和發布自己的自定義事件,
下面的示例顯示了一個簡單的類,該類擴展了Spring的ApplicationEvent基類:
public class BlockedListEvent extends ApplicationEvent {
private final String address;
private final String content;
public BlockedListEvent(Object source, String address, String content) {
super(source);
this.address = address;
this.content = content;
}
// accessor and other methods...
}
要發布自定義ApplicationEvent,請在ApplicationEventPublisher上呼叫publishEvent()方法 ,
通常,這是通過創建一個實作ApplicationEventPublisherAware并注冊為Spring bean的類來完成的 ,
以下示例顯示了此類:
public class EmailService implements ApplicationEventPublisherAware {
private List<String> blockedList;
private ApplicationEventPublisher publisher;
public void setBlockedList(List<String> blockedList) {
this.blockedList = blockedList;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void sendEmail(String address, String content) {
if (blockedList.contains(address)) {
publisher.publishEvent(new BlockedListEvent(this, address, content));
return;
}
// send email...
}
}
在配置時,Spring容器檢測到該ApplicationEventPublisherAware實作EmailService 并自動呼叫 setApplicationEventPublisher(),
實際上,傳入的引數是Spring容器本身,
您正在通過其ApplicationEventPublisher界面與應用程式背景關系進行互動,
要接收自定義ApplicationEvent,您可以創建一個實作 ApplicationListener并注冊為Spring bean的類,
以下示例顯示了此類:
public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
注意,ApplicationListener通常是用自定義事件的型別引數化的(在前面的示例中是BlockedListEvent),
這意味著onApplicationEvent()方法可以保持型別安全,避免向下強制轉換,
您可以注冊任意數量的事件監聽器,但是請注意,默認情況下,事件監聽器同步接收事件,
這意味著publishEvent()方法會阻塞,直到所有監聽器都完成了事件的處理,
這種同步和單執行緒方法的一個優點是,當偵聽器接收到事件時,如果事務背景關系可用,它將在發布程式的事務背景關系內操作,
下面的例子顯示了用于注冊和配置上面每個類的bean定義:
<!--當呼叫emailService bean的sendEmail()方法時,
如果有任何需要阻止的電子郵件訊息,
則發布型別為BlockedListEvent的自定義事件, -->
<bean id="emailService" >
<property name="blockedList">
<list>
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
</list>
</property>
</bean>
<!--blockedListNotifier
bean注冊為一個ApplicationListener并接收BlockedListEvent,
此時它可以通知適當的方,-->
<bean id="blockedListNotifier" >
<property name="notificationAddress" value="https://www.cnblogs.com/dabaieyangzhijidi/p/[email protected]"/>
</bean>
Spring的事件機制是為相同背景關系中的Spring bean之間的簡單通信而設計的,
然而,對于更復雜的企業集成需求,
單獨維護的Spring integration專案提供了構建輕量級、面向模式、事件驅動架構的完整支持,
這些架構構建在眾所周知的Spring編程模型之上,
基于注解的事件偵聽器
從Spring 4.2開始,您可以使用@EventListener注解在托管Bean的任何公共方法上注冊事件偵聽器,
該BlockedListNotifier可改寫如下:
public class BlockedListNotifier {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@EventListener
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
方法簽名再次宣告它偵聽的事件型別,但是這次使用了靈活的名稱,并且沒有實作特定的偵聽器介面,
只要實際事件型別在其實作層次結構中決議泛型引數,就可以通過泛型縮小事件型別,
如果您的方法應該偵聽多個事件,或者您希望在不使用任何引數的情況下定義它,那么還可以在注解本身上指定事件型別,
下面的例子展示了如何做到這一點:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
// ...
}
還可以通過使用定義SpEL運算式的注解的條件屬性來添加額外的運行時過濾,該注解應該與針對特定事件實際呼叫方法相匹配,
下面的例子展示了我們的通知程式如何被重寫,只有在content 屬性等于my-event時才被呼叫:
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blockedListEvent) {
// notify appropriate parties via notificationAddress...
}
每個SpEL運算式針對專用背景關系進行評估,
下表列出了可用于背景關系的專案,以便您可以將它們用于條件事件處理:
表8. Event SpEL可用的元資料
| 名稱 | 例子 |
|---|---|
| Event | #root.event or event |
| 引數陣列 | #root.args or args; args[0]訪問第一個引數等, |
| 引數名稱 | #blEvent或#a0 (您還可以使用#p0或#p<#arg>引數表示法作為別名) |
請注意,root.event允許您訪問基礎事件,即使您的方法簽名實際上參考了已發布的任意物件,
如果你需要發布一個事件作為處理另一個事件的結果,你可以改變方法簽名來回傳應該發布的事件,如下面的例子所示:
@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
asynchronous listeners.不支持此功能 ,
此新方法為每處理一個BlockedListEvent事件都會發布一個新事件ListUpdateEvent,
如果您需要發布多個事件,則可以回傳一個Collection事件,
asynchronous listeners 異步偵聽器
如果需要一個特定的偵聽器異步處理事件,可以重用常規的@Async支持,
下面的例子展示了如何做到這一點:
@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
// BlockedListEvent is processed in a separate thread
}
使用異步事件時,請注意以下限制:
- 如果異步事件偵聽器拋出Exception,則不會傳播到呼叫者,
- 異步事件偵聽器方法無法通過回傳值來發布后續事件,如果您需要發布另一個事件作為處理的結果,請插入一個 ApplicationEventPublisher 以手動發布事件,
Ordering Listeners
如果需要先呼叫一個偵聽器,則可以將@Order注解添加到方法宣告中,
如以下示例所示:
@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
一般事件
還可以使用泛型來進一步定義事件的結構,
考慮使用EntityCreatedEvent
例如,您可以創建以下偵聽器定義來只為一個人接收EntityCreatedEvent:
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
// ...
}
由于型別擦除,只有在觸發的事件決議了事件偵聽器所基于的通用引數
(即class PersonCreatedEvent extends EntityCreatedEvent
在某些情況下,如果所有事件都遵循相同的結構(前面示例中的事件也應該如此),那么這可能會變得非常乏味,
在這種情況下,您可以實作ResolvableTypeProvider來指導運行時環境所提供的框架,
下面的事件展示了如何做到這一點:
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()));
}
}
這不僅適用于ApplicationEvent作為事件發送的任何物件,而且適用于該物件,
1.15.3,方便地訪問低級資源
為了優化使用和理解應用程式背景關系,您應該熟悉Spring的Resource類,如參考資料中所述,
應用程式背景關系是一個ResourceLoader,可用于加載資源物件,
Resource本質上是JDK java.net.URL類的功能豐富版本,
事實上,Resource 的實作在適當的時候包裝了一個java.net.URL的實體,
Resource可以以透明的方式從幾乎任何位置獲取底層資源,包括類路徑、檔案系統位置、可用標準URL描述的任何位置,以及其他一些變體,
如果Resource位置字串是沒有任何特殊前綴的簡單路徑,那么這些資源的來源是特定的,并且適合于實際的應用程式背景關系型別,
您可以配置部署到應用程式背景關系中的bean,以實作特殊的回呼介面ResourceLoaderAware,在初始化時自動回呼,而應用程式背景關系本身作為ResourceLoader傳入,
您還可以公開Resource型別的屬性,以便用于訪問靜態資源,Resource 像其他屬性一樣可以被注入,
您可以將這些資源屬性指定為簡單的字串路徑,并在部署bean時依賴于從這些文本字串到實際資源物件的自動轉換,
提供給ApplicationContext建構式的位置路徑或路徑實際上是資源字串,并且以簡單的形式,根據特定的背景關系實作進行適當的處理,
例如,ClassPathXmlApplicationContext將簡單的位置路徑視為類路徑位置,
您還可以使用帶有特殊前綴的位置路徑(資源字串)來強制從類路徑或URL加載定義,而不管實際背景關系型別是什么,
1.15.4,應用程式啟動跟蹤
ApplicationContext管理Spring應用程式的生命周期,并圍繞組件提供豐富的編程模型,
因此,復雜的應用程式可能具有同樣復雜的組件圖和啟動階段,
使用特定的度量來跟蹤應用程式的啟動步驟可以幫助理解啟動階段的時間花費在哪里,它也可以作為一種更好地理解整個背景關系生命周期的方法,
AbstractApplicationContext(及其子類)由ApplicationStartup檢測,它收集關于不同啟動階段的StartupStep資料:
- 應用程式背景關系生命周期(基本包掃描,配置類管理)
- bean生命周期(實體化、智能初始化、后處理)
- 應用程式事件處理
下面是AnnotationConfigApplicationContext中的插裝示例:
// 創建并啟動記錄
StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan");
// 向當前步驟添加標記資訊
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
// 執行我們正在測量的實際階段
this.scanner.scan(basePackages);
// 結束
scanPackages.end();
1.15.5,Web應用程式的便捷ApplicationContext實體化
例如,可以使用ContextLoader以宣告方式創建ApplicationContext實體,當然,也可以通過使用ApplicationContext實作之一以編程方式創建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>
監聽器檢查contextConfigLocation引數,
如果引數不存在,偵聽器將使用/WEB-INF/applicationContext.xml作為默認值,
當引數確實存在時,偵聽器使用預定義的分隔符(逗號、分號和空白)分隔字串,并將這些值用作搜索應用程式背景關系的位置,
也支持Ant風格的路徑模式,
例如:
/WEB-INF/*Context.xml(對于在WEB-INF目錄中名稱以Context結尾的所有檔案)
/WEB-INF/**/*Context.xml(對于WEB-INF任何子目錄中的所有此類檔案)
1.16.1,BeanFactory or ApplicationContext?
本節解釋BeanFactory和ApplicationContext容器級別之間的差異,以及引導的含義,
您應該使用ApplicationContext,除非您有很好的理由不這樣做,使用GenericApplicationContext和它的子類AnnotationConfigApplicationContext作為自定義引導的通用實作,
這些是用于所有常見目的的Spring核心容器的主要入口點:加載組態檔、觸發類路徑掃描、以編程方式注冊bean定義和帶注解的類,以及(從5.0開始)注冊功能性bean定義,
因為ApplicationContext包含了BeanFactory的所有功能,所以一般建議它優于普通的BeanFactory,除非需要對bean處理進行完全控制的場景除外,
對于許多擴展的容器特性,如注解處理和AOP代理,BeanPostProcessor擴展點是必不可少的,如果只使用普通的DefaultListableBeanFactory,默認情況下不會檢測到這種后處理器并激活它,
下表列出了BeanFactory和ApplicationContext介面和實作提供的特性,
表9.功能矩陣
| 特征 | BeanFactory | ApplicationContext |
|---|---|---|
| Bean實體化/布線 | Yes | Yes |
| 集成的生命周期管理 | No | Yes |
| 自動BeanPostProcessor登記 | No | Yes |
| 方便的訊息源訪問(用于內部化) | No | Yes |
| 內置ApplicationEvent發布機制 | No | Yes |
要使用顯式注冊Bean后處理器DefaultListableBeanFactory,您需要以編程方式呼叫addBeanPostProcessor,如以下示例所示:
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());
// now start using the factory
要將一個BeanFactoryPostProcessor應用到一個普通的DefaultListableBeanFactory中,你需要呼叫它的postProcessBeanFactory方法,如下面的例子所示:
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));
// bring in some property values from a Properties file
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
// now actually do the replacement
cfg.postProcessBeanFactory(factory);
如上所見手動登記是十分不方便的,尤其是依靠BeanFactoryPostProcessor和BeanPostProcessor擴展功能的時候,
一個AnnotationConfigApplicationContext注冊了所有公共注解后處理器,并可能通過配置注解(如@EnableTransactionManagement)在后臺引入額外的處理器,
在Spring的基于注解的配置模型的抽象層上,bean后處理器的概念僅僅成為容器內部的細節,
本文由博客一文多發平臺 OpenWrite 發布!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/203895.html
標籤:Java
上一篇:java基礎及Java作業總結
下一篇:Git 常用命令
