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。
在测试环境中,配置可能相似于以下内容:
@Beanpublic 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用户曾经设计出许多办法来实现此工作,通常依赖于零碎环境变量和<import/>蕴含${placeholder}的XML语句的组合,这些${placeholder}依据环境变量的值解析为正确的配置文件门路。
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的时候发现有办法判断是否应该调过以后beanif (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,
如以下示例所示:
@Configurationpublic 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属性<beans>。
咱们后面的示例配置能够用两个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/>在同一文件中拆分和嵌套元素,如以下示例所示:
<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" class="org.springframework.context.support.ResourceBundleMessageSource"> <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" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="exceptions"/> </bean> <bean id="example" class="com.something.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.propertiesargument.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" class="example.EmailService"> <property name="blockedList"> <list> <value>known.spammer@example.org</value> <value>known.hacker@example.org</value> <value>john.doe@example.org</value> </list> </property></bean><!--blockedListNotifier bean注册为一个ApplicationListener并接管BlockedListEvent, 此时它能够告诉适当的方。--><bean id="blockedListNotifier" class="example.BlockedListNotifier"> <property name="notificationAddress" value="blockedlist@example.org"/></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容许您拜访根底事件,即便您的办法签名实际上援用了已公布的任意对象。
如果你须要公布一个事件作为解决另一个事件的后果,你能够扭转办法签名来返回应该公布的事件,如上面的例子所示:
@EventListenerpublic ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) { // notify appropriate parties via notificationAddress and // then publish a ListUpdateEvent...}
asynchronous listeners.不反对此性能 。
此新办法为每解决一个BlockedListEvent事件都会公布一个新事件ListUpdateEvent。
如果您须要公布多个事件,则能够返回一个Collection事件。
asynchronous listeners 异步侦听器
如果须要一个特定的侦听器异步处理事件,能够重用惯例的@Async反对。
上面的例子展现了如何做到这一点:
@EventListener@Asyncpublic 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<T>,其中T是所创立的理论实体的类型。
例如,您能够创立以下侦听器定义来只为一个人接管EntityCreatedEvent:
@EventListenerpublic void onPersonCreated(EntityCreatedEvent<Person> event) { // ...}
因为类型擦除,只有在触发的事件解析了事件侦听器所基于的通用参数
(即class PersonCreatedEvent extends EntityCreatedEvent<Person> { … })时,此办法才起作用 。
在某些状况下,如果所有事件都遵循雷同的构造(后面示例中的事件也应该如此),那么这可能会变得十分乏味。
在这种状况下,您能够实现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 filePropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();cfg.setLocation(new FileSystemResource("jdbc.properties"));// now actually do the replacementcfg.postProcessBeanFactory(factory);
如上所见手动注销是非常不不便的,尤其是依附BeanFactoryPostProcessor和BeanPostProcessor扩大性能的时候。
一个AnnotationConfigApplicationContext注册了所有公共注解后处理器,并可能通过配置注解(如@EnableTransactionManagement)在后盾引入额定的处理器。在Spring的基于注解的配置模型的形象层上,bean后处理器的概念仅仅成为容器外部的细节。