Additional Capabilities of the ApplicationContext
As discussed in the chapter introduction, the org.springframework.beans.factory
package provides basic functionality for managing and manipulating beans, including in a
programmatic way. The org.springframework.context
package adds the
ApplicationContext
interface, which extends the BeanFactory
interface, in addition to extending other
interfaces to provide additional functionality in a more application
framework-oriented style. Many people use the ApplicationContext
in a completely
declarative fashion, not even creating it programmatically, but instead relying on
support classes such as ContextLoader
to automatically instantiate an
ApplicationContext
as part of the normal startup process of a Java EE web application.
To enhance BeanFactory
functionality in a more framework-oriented style, the context
package also provides the following functionality:
-
Access to messages in i18n-style, through the
MessageSource
interface. -
Access to resources, such as URLs and files, through the
ResourceLoader
interface. -
Event publication, namely to beans that implement the
ApplicationListener
interface, through the use of theApplicationEventPublisher
interface. -
Loading of multiple (hierarchical) contexts, letting each be focused on one particular layer, such as the web layer of an application, through the
HierarchicalBeanFactory
interface.
Internationalization using MessageSource
The ApplicationContext
interface extends an interface called MessageSource
and,
therefore, provides internationalization (“i18n”) functionality. Spring also provides the
HierarchicalMessageSource
interface, which can resolve messages hierarchically.
Together, these interfaces provide the foundation upon which Spring effects message
resolution. The methods defined on these interfaces include:
-
String getMessage(String code, Object[] args, String default, Locale loc)
: The basic method used to retrieve a message from theMessageSource
. When no message is found for the specified locale, the default message is used. Any arguments passed in become replacement values, using theMessageFormat
functionality provided by the standard library. -
String getMessage(String code, Object[] args, Locale loc)
: Essentially the same as the previous method but with one difference: No default message can be specified. If the message cannot be found, aNoSuchMessageException
is thrown. -
String getMessage(MessageSourceResolvable resolvable, Locale locale)
: All properties used in the preceding methods are also wrapped in a class namedMessageSourceResolvable
, which you can use with this method.
When an ApplicationContext
is loaded, it automatically searches for a MessageSource
bean defined in the context. The bean must have the name messageSource
. If such a bean
is found, all calls to the preceding methods are delegated to the message source. If no
message source is found, the ApplicationContext
attempts to find a parent containing a
bean with the same name. If it does, it uses that bean as the MessageSource
. If the
ApplicationContext
cannot find any source for messages, an empty
DelegatingMessageSource
is instantiated in order to be able to accept calls to the
methods defined above.
Spring provides two MessageSource
implementations, ResourceBundleMessageSource
and
StaticMessageSource
. Both implement HierarchicalMessageSource
in order to do nested
messaging. The StaticMessageSource
is rarely used but provides programmatic ways to
add messages to the source. The following example shows 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>
The example assumes that you have three resource bundles called format
, exceptions
and windows
defined in your classpath. Any request to resolve a message is
handled in the JDK-standard way of resolving messages through ResourceBundle
objects. For the
purposes of the example, assume the contents of two of the above resource bundle files
are as follows:
# in format.properties message=Alligators rock!
# in exceptions.properties argument.required=The {0} argument is required.
The next example shows a program to execute the MessageSource
functionality.
Remember that all ApplicationContext
implementations are also MessageSource
implementations and so can be cast to the MessageSource
interface.
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);
}
fun main() {
val resources = ClassPathXmlApplicationContext("beans.xml")
val message = resources.getMessage("message", null, "Default", Locale.ENGLISH)
println(message)
}
The resulting output from the above program is as follows:
Alligators rock!
To summarize, the MessageSource
is defined in a file called beans.xml
, which
exists at the root of your classpath. The messageSource
bean definition refers to a
number of resource bundles through its basenames
property. The three files that are
passed in the list to the basenames
property exist as files at the root of your
classpath and are called format.properties
, exceptions.properties
, and
windows.properties
, respectively.
The next example shows arguments passed to the message lookup. These arguments are
converted into String
objects and inserted into placeholders in the lookup message.
<beans>
<!-- this MessageSource is being used in a web application -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="exceptions"/>
</bean>
<!-- lets inject the above MessageSource into this POJO -->
<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);
}
}
class Example {
lateinit var messages: MessageSource
fun execute() {
val message = messages.getMessage("argument.required",
arrayOf("userDao"), "Required", Locale.ENGLISH)
println(message)
}
}
The resulting output from the invocation of the execute()
method is as follows:
The userDao argument is required.
With regard to internationalization (“i18n”), Spring’s various MessageSource
implementations follow the same locale resolution and fallback rules as the standard JDK
ResourceBundle
. In short, and continuing with the example messageSource
defined
previously, if you want to resolve messages against the British (en-GB
) locale, you
would create files called format_en_GB.properties
, exceptions_en_GB.properties
, and
windows_en_GB.properties
, respectively.
Typically, locale resolution is managed by the surrounding environment of the application. In the following example, the locale against which (British) messages are resolved is specified manually:
# 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);
}
fun main() {
val resources = ClassPathXmlApplicationContext("beans.xml")
val message = resources.getMessage("argument.required",
arrayOf("userDao"), "Required", Locale.UK)
println(message)
}
The resulting output from the running of the above program is as follows:
Ebagum lad, the 'userDao' argument is required, I say, required.
You can also use the MessageSourceAware
interface to acquire a reference to any
MessageSource
that has been defined. Any bean that is defined in an
ApplicationContext
that implements the MessageSourceAware
interface is injected with
the application context’s MessageSource
when the bean is created and configured.
As an alternative to ResourceBundleMessageSource , Spring provides a
ReloadableResourceBundleMessageSource class. This variant supports the same bundle
file format but is more flexible than the standard JDK based
ResourceBundleMessageSource implementation. In particular, it allows for reading
files from any Spring resource location (not only from the classpath) and supports hot
reloading of bundle property files (while efficiently caching them in between).
See the ReloadableResourceBundleMessageSource
javadoc for details.
|
Standard and Custom Events
Event handling in the ApplicationContext
is provided through the ApplicationEvent
class and the ApplicationListener
interface. If a bean that implements the
ApplicationListener
interface is deployed into the context, every time an
ApplicationEvent
gets published to the ApplicationContext
, that bean is notified.
Essentially, this is the standard Observer design pattern.
As of Spring 4.2, the event infrastructure has been significantly improved and offers
an annotation-based model as well as the
ability to publish any arbitrary event (that is, an object that does not necessarily
extend from ApplicationEvent ). When such an object is published, we wrap it in an
event for you.
|
The following table describes the standard events that Spring provides:
Event | Explanation |
---|---|
|
Published when the |
|
Published when the |
|
Published when the |
|
Published when the |
|
A web-specific event telling all beans that an HTTP request has been serviced. This
event is published after the request is complete. This event is only applicable to
web applications that use Spring’s |
|
A subclass of |
You can also create and publish your own custom events. The following example shows a
simple class that extends Spring’s ApplicationEvent
base class:
public class BlackListEvent extends ApplicationEvent {
private final String address;
private final String content;
public BlackListEvent(Object source, String address, String content) {
super(source);
this.address = address;
this.content = content;
}
// accessor and other methods...
}
class BlackListEvent(source: Any,
val address: String,
val content: String) : ApplicationEvent(source)
To publish a custom ApplicationEvent
, call the publishEvent()
method on an
ApplicationEventPublisher
. Typically, this is done by creating a class that implements
ApplicationEventPublisherAware
and registering it as a Spring bean. The following
example shows such a class:
public class EmailService implements ApplicationEventPublisherAware {
private List<String> blackList;
private ApplicationEventPublisher publisher;
public void setBlackList(List<String> blackList) {
this.blackList = blackList;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void sendEmail(String address, String content) {
if (blackList.contains(address)) {
publisher.publishEvent(new BlackListEvent(this, address, content));
return;
}
// send email...
}
}
class EmailService : ApplicationEventPublisherAware {
private lateinit var blackList: List<String>
private lateinit var publisher: ApplicationEventPublisher
fun setBlackList(blackList: List<String>) {
this.blackList = blackList
}
override fun setApplicationEventPublisher(publisher: ApplicationEventPublisher) {
this.publisher = publisher
}
fun sendEmail(address: String, content: String) {
if (blackList!!.contains(address)) {
publisher!!.publishEvent(BlackListEvent(this, address, content))
return
}
// send email...
}
}
At configuration time, the Spring container detects that EmailService
implements
ApplicationEventPublisherAware
and automatically calls
setApplicationEventPublisher()
. In reality, the parameter passed in is the Spring
container itself. You are interacting with the application context through its
ApplicationEventPublisher
interface.
To receive the custom ApplicationEvent
, you can create a class that implements
ApplicationListener
and register it as a Spring bean. The following example
shows such a class:
public class BlackListNotifier implements ApplicationListener<BlackListEvent> {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
class BlackListNotifier : ApplicationListener<BlackListEvent> {
lateinit var notificationAddres: String
override fun onApplicationEvent(event: BlackListEvent) {
// notify appropriate parties via notificationAddress...
}
}
Notice that ApplicationListener
is generically parameterized with the type of your
custom event (BlackListEvent
in the preceding example). This means that the onApplicationEvent()
method can
remain type-safe, avoiding any need for downcasting. You can register as many event
listeners as you wish, but note that, by default, event listeners receive events
synchronously. This means that the publishEvent()
method blocks until all listeners have
finished processing the event. One advantage of this synchronous and single-threaded
approach is that, when a listener receives an event, it operates inside the transaction
context of the publisher if a transaction context is available. If another strategy for
event publication becomes necessary, see the javadoc for Spring’s
ApplicationEventMulticaster
interface
and SimpleApplicationEventMulticaster
implementation for configuration options.
The following example shows the bean definitions used to register and configure each of the classes above:
<bean id="emailService" class="example.EmailService">
<property name="blackList">
<list>
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
</list>
</property>
</bean>
<bean id="blackListNotifier" class="example.BlackListNotifier">
<property name="notificationAddress" value="[email protected]"/>
</bean>
Putting it all together, when the sendEmail()
method of the emailService
bean is
called, if there are any email messages that should be blacklisted, a custom event of type
BlackListEvent
is published. The blackListNotifier
bean is registered as an
ApplicationListener
and receives the BlackListEvent
, at which point it can
notify appropriate parties.
Spring’s eventing mechanism is designed for simple communication between Spring beans within the same application context. However, for more sophisticated enterprise integration needs, the separately maintained Spring Integration project provides complete support for building lightweight, pattern-oriented, event-driven architectures that build upon the well-known Spring programming model. |
Annotation-based Event Listeners
As of Spring 4.2, you can register an event listener on any public method of a managed
bean by using the @EventListener
annotation. The BlackListNotifier
can be rewritten as
follows:
public class BlackListNotifier {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@EventListener
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
class BlackListNotifier {
lateinit var notificationAddress: String
@EventListener
fun processBlackListEvent(event: BlackListEvent) {
// notify appropriate parties via notificationAddress...
}
}
The method signature once again declares the event type to which it listens, but, this time, with a flexible name and without implementing a specific listener interface. The event type can also be narrowed through generics as long as the actual event type resolves your generic parameter in its implementation hierarchy.
If your method should listen to several events or if you want to define it with no parameter at all, the event types can also be specified on the annotation itself. The following example shows how to do so:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
// ...
}
@EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class)
fun handleContextStart() {
// ...
}
It is also possible to add additional runtime filtering by using the condition
attribute
of the annotation that defines a SpEL
expression , which should match
to actually invoke the method for a particular event.
The following example shows how our notifier can be rewritten to be invoked only if the
content
attribute of the event is equal to my-event
:
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlackListEvent(BlackListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
@EventListener(condition = "#blEvent.content == 'my-event'")
fun processBlackListEvent(blEvent: BlackListEvent) {
// notify appropriate parties via notificationAddress...
}
Each SpEL
expression evaluates against a dedicated context. The following table lists the
items made available to the context so that you can use them for conditional event processing:
Name | Location | Description | Example |
---|---|---|---|
Event |
root object |
The actual |
|
Arguments array |
root object |
The arguments (as an object array) used to invoke the method. |
|
Argument name |
evaluation context |
The name of any of the method arguments. If, for some reason, the names are not available
(for example, because there is no debug information in the compiled byte code), individual
arguments are also available using the |
|
Note that #root.event
gives you access to the underlying event, even if your method
signature actually refers to an arbitrary object that was published.
If you need to publish an event as the result of processing another event, you can change the method signature to return the event that should be published, as the following example shows:
@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
@EventListener
fun handleBlackListEvent(event: BlackListEvent): ListUpdateEvent {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
This feature is not supported for asynchronous listeners. |
This new method publishes a new ListUpdateEvent
for every BlackListEvent
handled by the
method above. If you need to publish several events, you can return a Collection
of events
instead.
Asynchronous Listeners
If you want a particular listener to process events asynchronously, you can reuse the
regular @Async
support.
The following example shows how to do so:
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}
@EventListener
@Async
fun processBlackListEvent(event: BlackListEvent) {
// BlackListEvent is processed in a separate thread
}
Be aware of the following limitations when using asynchronous events:
-
If an asynchronous event listener throws an
Exception
, it is not propagated to the caller. SeeAsyncUncaughtExceptionHandler
for more details. -
Asynchronous event listener methods cannot publish a subsequent event by returning a value. If you need to publish another event as the result of the processing, inject an
ApplicationEventPublisher
to publish the event manually.
Ordering Listeners
If you need one listener to be invoked before another one, you can add the @Order
annotation to the method declaration, as the following example shows:
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
@EventListener
@Order(42)
fun processBlackListEvent(event: BlackListEvent) {
// notify appropriate parties via notificationAddress...
}
Generic Events
You can also use generics to further define the structure of your event. Consider using an
EntityCreatedEvent<T>
where T
is the type of the actual entity that got created. For example, you
can create the following listener definition to receive only EntityCreatedEvent
for a
Person
:
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
// ...
}
@EventListener
fun onPersonCreated(event: EntityCreatedEvent<Person>) {
// ...
}
Due to type erasure, this works only if the event that is fired resolves the generic
parameters on which the event listener filters (that is, something like
class PersonCreatedEvent extends EntityCreatedEvent<Person> { … }
).
In certain circumstances, this may become quite tedious if all events follow the same
structure (as should be the case for the event in the preceding example). In such a case,
you can implement ResolvableTypeProvider
to guide the framework beyond what the runtime
environment provides. The following event shows how to do so:
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()));
}
}
class EntityCreatedEvent<T>(entity: T) : ApplicationEvent(entity), ResolvableTypeProvider {
override fun getResolvableType(): ResolvableType? {
return ResolvableType.forClassWithGenerics(javaClass, ResolvableType.forInstance(getSource()))
}
}
This works not only for ApplicationEvent but any arbitrary object that you send as
an event.
|
Convenient Access to Low-level Resources
For optimal usage and understanding of application contexts, you should familiarize
yourself with Spring’s Resource
abstraction, as described in resources.
An application context is a ResourceLoader
, which can be used to load Resource
objects.
A Resource
is essentially a more feature rich version of the JDK java.net.URL
class.
In fact, the implementations of the Resource
wrap an instance of java.net.URL
, where
appropriate. A Resource
can obtain low-level resources from almost any location in a
transparent fashion, including from the classpath, a filesystem location, anywhere
describable with a standard URL, and some other variations. If the resource location
string is a simple path without any special prefixes, where those resources come from is
specific and appropriate to the actual application context type.
You can configure a bean deployed into the application context to implement the special
callback interface, ResourceLoaderAware
, to be automatically called back at
initialization time with the application context itself passed in as the ResourceLoader
.
You can also expose properties of type Resource
, to be used to access static resources.
They are injected into it like any other properties. You can specify those Resource
properties as simple String
paths and rely on automatic conversion from those text
strings to actual Resource
objects when the bean is deployed.
The location path or paths supplied to an ApplicationContext
constructor are actually
resource strings and, in simple form, are treated appropriately according to the specific
context implementation. For example ClassPathXmlApplicationContext
treats a simple
location path as a classpath location. You can also use location paths (resource strings)
with special prefixes to force loading of definitions from the classpath or a URL,
regardless of the actual context type.
Convenient ApplicationContext Instantiation for Web Applications
You can create ApplicationContext
instances declaratively by using, for example, a
ContextLoader
. Of course, you can also create ApplicationContext
instances
programmatically by using one of the ApplicationContext
implementations.
You can register an ApplicationContext
by using the ContextLoaderListener
, as the
following example shows:
<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>
The listener inspects the contextConfigLocation
parameter. If the parameter does not
exist, the listener uses /WEB-INF/applicationContext.xml
as a default. When the
parameter does exist, the listener separates the String
by using predefined
delimiters (comma, semicolon, and whitespace) and uses the values as locations where
application contexts are searched. Ant-style path patterns are supported as well.
Examples are /WEB-INF/*Context.xml
(for all files with names that end with
Context.xml
and that reside in the WEB-INF
directory) and /WEB-INF/**/*Context.xml
(for all such files in any subdirectory of WEB-INF
).
Deploying a Spring ApplicationContext
as a Java EE RAR File
It is possible to deploy a Spring ApplicationContext
as a RAR file, encapsulating the
context and all of its required bean classes and library JARs in a Java EE RAR deployment
unit. This is the equivalent of bootstrapping a stand-alone ApplicationContext
(only hosted
in Java EE environment) being able to access the Java EE servers facilities. RAR deployment
is a more natural alternative to a scenario of deploying a headless WAR file — in effect,
a WAR file without any HTTP entry points that is used only for bootstrapping a Spring
ApplicationContext
in a Java EE environment.
RAR deployment is ideal for application contexts that do not need HTTP entry points but
rather consist only of message endpoints and scheduled jobs. Beans in such a context can
use application server resources such as the JTA transaction manager and JNDI-bound JDBC
DataSource
instances and JMS ConnectionFactory
instances and can also register with
the platform’s JMX server — all through Spring’s standard transaction management and JNDI
and JMX support facilities. Application components can also interact with the application
server’s JCA WorkManager
through Spring’s TaskExecutor
abstraction.
See the javadoc of the
SpringContextResourceAdapter
class for the configuration details involved in RAR deployment.
For a simple deployment of a Spring ApplicationContext as a Java EE RAR file:
-
Package all application classes into a RAR file (which is a standard JAR file with a different file extension). .Add all required library JARs into the root of the RAR archive. .Add a
META-INF/ra.xml
deployment descriptor (as shown in the javadoc forSpringContextResourceAdapter
) and the corresponding Spring XML bean definition file(s) (typicallyMETA-INF/applicationContext.xml
). -
Drop the resulting RAR file into your application server’s deployment directory.
Such RAR deployment units are usually self-contained. They do not expose components
to the outside world, not even to other modules of the same application. Interaction with a
RAR-based ApplicationContext usually occurs through JMS destinations that it shares with
other modules. A RAR-based ApplicationContext may also, for example, schedule some jobs
or react to new files in the file system (or the like). If it needs to allow synchronous
access from the outside, it could (for example) export RMI endpoints, which may be used
by other application modules on the same machine.
|