This part of the reference documentation covers Spring Framework’s integration with a number of technologies.
1. REST Clients
The Spring Framework provides the following choices for making calls to REST endpoints:
-
WebClient
- non-blocking, reactive client w fluent API. -
RestTemplate
- synchronous client with template method API. -
HTTP Interface - annotated interface with generated, dynamic proxy implementation.
1.1. WebClient
WebClient
is a non-blocking, reactive client to perform HTTP requests. It was
introduced in 5.0 and offers an alternative to the RestTemplate
, with support for
synchronous, asynchronous, and streaming scenarios.
WebClient
supports the following:
-
Non-blocking I/O.
-
Reactive Streams back pressure.
-
High concurrency with fewer hardware resources.
-
Functional-style, fluent API that takes advantage of Java 8 lambdas.
-
Synchronous and asynchronous interactions.
-
Streaming up to or streaming down from a server.
See WebClient for more details.
1.2. RestTemplate
The RestTemplate
provides a higher level API over HTTP client libraries. It makes it
easy to invoke REST endpoints in a single line. It exposes the following groups of
overloaded methods:
RestTemplate is in maintenance mode, with only requests for minor
changes and bugs to be accepted. Please, consider using the
WebClient instead.
|
Method group | Description |
---|---|
|
Retrieves a representation via GET. |
|
Retrieves a |
|
Retrieves all headers for a resource by using HEAD. |
|
Creates a new resource by using POST and returns the |
|
Creates a new resource by using POST and returns the representation from the response. |
|
Creates a new resource by using POST and returns the representation from the response. |
|
Creates or updates a resource by using PUT. |
|
Updates a resource by using PATCH and returns the representation from the response.
Note that the JDK |
|
Deletes the resources at the specified URI by using DELETE. |
|
Retrieves allowed HTTP methods for a resource by using ALLOW. |
|
More generalized (and less opinionated) version of the preceding methods that provides extra
flexibility when needed. It accepts a These methods allow the use of |
|
The most generalized way to perform a request, with full control over request preparation and response extraction through callback interfaces. |
1.2.1. Initialization
The default constructor uses java.net.HttpURLConnection
to perform requests. You can
switch to a different HTTP library with an implementation of ClientHttpRequestFactory
.
There is built-in support for the following:
-
Apache HttpComponents
-
Netty
-
OkHttp
For example, to switch to Apache HttpComponents, you can use the following:
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
Each ClientHttpRequestFactory
exposes configuration options specific to the underlying
HTTP client library — for example, for credentials, connection pooling, and other details.
Note that the java.net implementation for HTTP requests can raise an exception when
accessing the status of a response that represents an error (such as 401). If this is an
issue, switch to another HTTP client library.
|
URIs
Many of the RestTemplate
methods accept a URI template and URI template variables,
either as a String
variable argument, or as Map<String,String>
.
The following example uses a String
variable argument:
String result = restTemplate.getForObject(
"https://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");
The following example uses a Map<String, String>
:
Map<String, String> vars = Collections.singletonMap("hotel", "42");
String result = restTemplate.getForObject(
"https://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
Keep in mind URI templates are automatically encoded, as the following example shows:
restTemplate.getForObject("https://example.com/hotel list", String.class);
// Results in request to "https://example.com/hotel%20list"
You can use the uriTemplateHandler
property of RestTemplate
to customize how URIs
are encoded. Alternatively, you can prepare a java.net.URI
and pass it into one of
the RestTemplate
methods that accepts a URI
.
For more details on working with and encoding URIs, see URI Links.
Headers
You can use the exchange()
methods to specify request headers, as the following example shows:
String uriTemplate = "https://example.com/hotels/{hotel}";
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42);
RequestEntity<Void> requestEntity = RequestEntity.get(uri)
.header("MyRequestHeader", "MyValue")
.build();
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();
You can obtain response headers through many RestTemplate
method variants that return
ResponseEntity
.
1.2.2. Body
Objects passed into and returned from RestTemplate
methods are converted to and from raw
content with the help of an HttpMessageConverter
.
On a POST, an input object is serialized to the request body, as the following example shows:
URI location = template.postForLocation("https://example.com/people", person);
You need not explicitly set the Content-Type header of the request. In most cases,
you can find a compatible message converter based on the source Object
type, and the chosen
message converter sets the content type accordingly. If necessary, you can use the
exchange
methods to explicitly provide the Content-Type
request header, and that, in
turn, influences what message converter is selected.
On a GET, the body of the response is deserialized to an output Object
, as the following example shows:
Person person = restTemplate.getForObject("https://example.com/people/{id}", Person.class, 42);
The Accept
header of the request does not need to be explicitly set. In most cases,
a compatible message converter can be found based on the expected response type, which
then helps to populate the Accept
header. If necessary, you can use the exchange
methods to provide the Accept
header explicitly.
By default, RestTemplate
registers all built-in
message converters, depending on classpath checks that help
to determine what optional conversion libraries are present. You can also set the message
converters to use explicitly.
1.2.3. Message Conversion
The spring-web
module contains the HttpMessageConverter
contract for reading and
writing the body of HTTP requests and responses through InputStream
and OutputStream
.
HttpMessageConverter
instances are used on the client side (for example, in the RestTemplate
) and
on the server side (for example, in Spring MVC REST controllers).
Concrete implementations for the main media (MIME) types are provided in the framework
and are, by default, registered with the RestTemplate
on the client side and with
RequestMappingHandlerAdapter
on the server side (see
Configuring Message Converters).
The implementations of HttpMessageConverter
are described in the following sections.
For all converters, a default media type is used, but you can override it by setting the
supportedMediaTypes
bean property. The following table describes each implementation:
MessageConverter | Description |
---|---|
|
An |
|
An |
|
An |
|
An |
|
An |
|
An |
|
An |
|
An |
1.2.4. Jackson JSON Views
You can specify a Jackson JSON View to serialize only a subset of the object properties, as the following example shows:
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);
RequestEntity<MappingJacksonValue> requestEntity =
RequestEntity.post(new URI("https://example.com/user")).body(value);
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
Multipart
To send multipart data, you need to provide a MultiValueMap<String, Object>
whose values
may be an Object
for part content, a Resource
for a file part, or an HttpEntity
for
part content with headers. For example:
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("fieldPart", "fieldValue");
parts.add("filePart", new FileSystemResource("...logo.png"));
parts.add("jsonPart", new Person("Jason"));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
parts.add("xmlPart", new HttpEntity<>(myBean, headers));
In most cases, you do not have to specify the Content-Type
for each part. The content
type is determined automatically based on the HttpMessageConverter
chosen to serialize
it or, in the case of a Resource
based on the file extension. If necessary, you can
explicitly provide the MediaType
with an HttpEntity
wrapper.
Once the MultiValueMap
is ready, you can pass it to the RestTemplate
, as show below:
MultiValueMap<String, Object> parts = ...;
template.postForObject("https://example.com/upload", parts, Void.class);
If the MultiValueMap
contains at least one non-String
value, the Content-Type
is set
to multipart/form-data
by the FormHttpMessageConverter
. If the MultiValueMap
has
String
values the Content-Type
is defaulted to application/x-www-form-urlencoded
.
If necessary the Content-Type
may also be set explicitly.
1.3. HTTP Interface
The Spring Framework lets you define an HTTP service as a Java interface with annotated methods for HTTP exchanges. You can then generate a proxy that implements this interface and performs the exchanges. This helps to simplify HTTP remote access which often involves a facade that wraps the details of using the underlying HTTP client.
One, declare an interface with @HttpExchange
methods:
interface RepositoryService {
@GetExchange("/repos/{owner}/{repo}")
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
// more HTTP exchange methods...
}
Two, create a proxy that will perform the declared HTTP exchanges:
WebClient client = WebClient.builder().baseUrl("https://api.github.com/").build();
HttpServiceProxyFactory factory = WebClientAdapter.createHttpServiceProxyFactory(client);
factory.afterPropertiesSet();
RepositoryService service = factory.createClient(RepositoryService.class);
@HttpExchange
is supported at the type level where it applies to all methods:
@HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json")
interface RepositoryService {
@GetExchange
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
@PatchExchange(contentType = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
void updateRepository(@PathVariable String owner, @PathVariable String repo,
@RequestParam String name, @RequestParam String description, @RequestParam String homepage);
}
1.3.1. Method Parameters
Annotated, HTTP exchange methods support flexible method signatures with the following method parameters:
Controller method argument | Description |
---|---|
|
Dynamically set the URL for the request, overriding the annotation’s |
|
Dynamically set the HTTP method for the request, overriding the annotation’s |
|
Add a request header or mutliple headers. The argument may be a |
|
Add a variable for expand a placeholder in the request URL. The argument may be a
|
|
Provide the body of the request either as an Object to be serialized, or a
Reactive Streams |
|
Add a request parameter or mutliple parameters. The argument may be a When |
|
Add a cookie or mutliple cookies. The argument may be a |
1.3.2. Return Values
Annotated, HTTP exchange methods support the following return values:
Controller method return value | Description |
---|---|
|
Perform the given request, and release the response content, if any. |
|
Perform the given request, release the response content, if any, and return the response headers. |
|
Perform the given request and decode the response content to the declared return type. |
|
Perform the given request and decode the response content to a stream of the declared element type. |
|
Perform the given request, and release the response content, if any, and return a
|
|
Perform the given request, decode the response content to the declared return type, and
return a |
|
Perform the given request, decode the response content to a stream of the declared
element type, and return a |
You can also use any other async or reactive types registered in the
ReactiveAdapterRegistry .
|
1.3.3. Exception Handling
By default, WebClient
raises WebClientResponseException
for 4xx and 5xx HTTP status
codes. To customize this, you can register a response status handler that applies to all
responses performed through the client:
WebClient webClient = WebClient.builder()
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
.build();
HttpServiceProxyFactory proxyFactory =
WebClientAdapter.createHttpServiceProxyFactory(webClient);
For more details and options, such as suppressing error status codes, see the Javadoc of
defaultStatusHandler
in WebClient.Builder
.
2. JMS (Java Message Service)
Spring provides a JMS integration framework that simplifies the use of the JMS API in much the same way as Spring’s integration does for the JDBC API.
JMS can be roughly divided into two areas of functionality, namely the production and
consumption of messages. The JmsTemplate
class is used for message production and
synchronous message reception. For asynchronous reception similar to Jakarta EE’s
message-driven bean style, Spring provides a number of message-listener containers that
you can use to create Message-Driven POJOs (MDPs). Spring also provides a declarative way
to create message listeners.
The org.springframework.jms.core
package provides the core functionality for using
JMS. It contains JMS template classes that simplify the use of the JMS by handling the
creation and release of resources, much like the JdbcTemplate
does for JDBC. The
design principle common to Spring template classes is to provide helper methods to
perform common operations and, for more sophisticated usage, delegate the essence of the
processing task to user-implemented callback interfaces. The JMS template follows the
same design. The classes offer various convenience methods for sending messages,
consuming messages synchronously, and exposing the JMS session and message producer to
the user.
The org.springframework.jms.support
package provides JMSException
translation
functionality. The translation converts the checked JMSException
hierarchy to a
mirrored hierarchy of unchecked exceptions. If any provider-specific subclasses
of the checked jakarta.jms.JMSException
exist, this exception is wrapped in the
unchecked UncategorizedJmsException
.
The org.springframework.jms.support.converter
package provides a MessageConverter
abstraction to convert between Java objects and JMS messages.
The org.springframework.jms.support.destination
package provides various strategies
for managing JMS destinations, such as providing a service locator for destinations
stored in JNDI.
The org.springframework.jms.annotation
package provides the necessary infrastructure
to support annotation-driven listener endpoints by using @JmsListener
.
The org.springframework.jms.config
package provides the parser implementation for the
jms
namespace as well as the java config support to configure listener containers and
create listener endpoints.
Finally, the org.springframework.jms.connection
package provides an implementation of
the ConnectionFactory
suitable for use in standalone applications. It also contains an
implementation of Spring’s PlatformTransactionManager
for JMS (the cunningly named
JmsTransactionManager
). This allows for seamless integration of JMS as a transactional
resource into Spring’s transaction management mechanisms.
As of Spring Framework 5, Spring’s JMS package fully supports JMS 2.0 and requires the JMS 2.0 API to be present at runtime. We recommend the use of a JMS 2.0 compatible provider. If you happen to use an older message broker in your system, you may try upgrading to a JMS 2.0 compatible driver for your existing broker generation. Alternatively, you may also try to run against a JMS 1.1 based driver, simply putting the JMS 2.0 API jar on the classpath but only using JMS 1.1 compatible API against your driver. Spring’s JMS support adheres to JMS 1.1 conventions by default, so with corresponding configuration it does support such a scenario. However, please consider this for transition scenarios only. |
2.1. Using Spring JMS
This section describes how to use Spring’s JMS components.
2.1.1. Using JmsTemplate
The JmsTemplate
class is the central class in the JMS core package. It simplifies the
use of JMS, since it handles the creation and release of resources when sending or
synchronously receiving messages.
Code that uses the JmsTemplate
needs only to implement callback interfaces that give them
a clearly defined high-level contract. The MessageCreator
callback interface creates a
message when given a Session
provided by the calling code in JmsTemplate
. To
allow for more complex usage of the JMS API, SessionCallback
provides the
JMS session, and ProducerCallback
exposes a Session
and
MessageProducer
pair.
The JMS API exposes two types of send methods, one that takes delivery mode, priority,
and time-to-live as Quality of Service (QOS) parameters and one that takes no QOS
parameters and uses default values. Since JmsTemplate
has many send methods,
setting the QOS parameters have been exposed as bean properties to
avoid duplication in the number of send methods. Similarly, the timeout value for
synchronous receive calls is set by using the setReceiveTimeout
property.
Some JMS providers allow the setting of default QOS values administratively through the
configuration of the ConnectionFactory
. This has the effect that a call to a
MessageProducer
instance’s send
method (send(Destination destination, Message message)
)
uses different QOS default values than those specified in the JMS specification. In order
to provide consistent management of QOS values, the JmsTemplate
must, therefore, be
specifically enabled to use its own QOS values by setting the boolean property
isExplicitQosEnabled
to true
.
For convenience, JmsTemplate
also exposes a basic request-reply operation that allows
for sending a message and waiting for a reply on a temporary queue that is created as part of
the operation.
Instances of the JmsTemplate class are thread-safe, once configured. This is
important, because it means that you can configure a single instance of a JmsTemplate
and then safely inject this shared reference into multiple collaborators. To be
clear, the JmsTemplate is stateful, in that it maintains a reference to a
ConnectionFactory , but this state is not conversational state.
|
As of Spring Framework 4.1, JmsMessagingTemplate
is built on top of JmsTemplate
and provides an integration with the messaging abstraction — that is,
org.springframework.messaging.Message
. This lets you create the message to
send in a generic manner.
2.1.2. Connections
The JmsTemplate
requires a reference to a ConnectionFactory
. The ConnectionFactory
is part of the JMS specification and serves as the entry point for working with JMS. It
is used by the client application as a factory to create connections with the JMS
provider and encapsulates various configuration parameters, many of which are
vendor-specific, such as SSL configuration options.
When using JMS inside an EJB, the vendor provides implementations of the JMS interfaces
so that they can participate in declarative transaction management and perform pooling
of connections and sessions. In order to use this implementation, Jakarta EE containers
typically require that you declare a JMS connection factory as a resource-ref
inside
the EJB or servlet deployment descriptors. To ensure the use of these features with the
JmsTemplate
inside an EJB, the client application should ensure that it references the
managed implementation of the ConnectionFactory
.
Caching Messaging Resources
The standard API involves creating many intermediate objects. To send a message, the following 'API' walk is performed:
ConnectionFactory->Connection->Session->MessageProducer->send
Between the ConnectionFactory
and the Send
operation, three intermediate
objects are created and destroyed. To optimize the resource usage and increase
performance, Spring provides two implementations of ConnectionFactory
.
Using SingleConnectionFactory
Spring provides an implementation of the ConnectionFactory
interface,
SingleConnectionFactory
, that returns the same Connection
on all
createConnection()
calls and ignores calls to close()
. This is useful for testing and
standalone environments so that the same connection can be used for multiple
JmsTemplate
calls that may span any number of transactions. SingleConnectionFactory
takes a reference to a standard ConnectionFactory
that would typically come from JNDI.
Using CachingConnectionFactory
The CachingConnectionFactory
extends the functionality of SingleConnectionFactory
and adds the caching of Session
, MessageProducer
, and MessageConsumer
instances.
The initial cache size is set to 1
. You can use the sessionCacheSize
property to
increase the number of cached sessions. Note that the number of actual cached sessions
is more than that number, as sessions are cached based on their acknowledgment mode,
so there can be up to four cached session instances (one for each acknowledgment mode)
when sessionCacheSize
is set to one. MessageProducer
and MessageConsumer
instances
are cached within their owning session and also take into account the unique properties
of the producers and consumers when caching. MessageProducers are cached based on their
destination. MessageConsumers are cached based on a key composed of the destination, selector,
noLocal delivery flag, and the durable subscription name (if creating durable consumers).
MessageProducers and MessageConsumers for temporary queues and topics
(TemporaryQueue/TemporaryTopic) will never be cached. Unfortunately, WebLogic JMS happens
to implement the temporary queue/topic interfaces on its regular destination implementation,
mis-indicating that none of its destinations can be cached. Please use a different connection
pool/cache on WebLogic, or customize |
2.1.3. Destination Management
Destinations, as ConnectionFactory
instances, are JMS administered objects that you can store
and retrieve in JNDI. When configuring a Spring application context, you can use the
JNDI JndiObjectFactoryBean
factory class or <jee:jndi-lookup>
to perform dependency
injection on your object’s references to JMS destinations. However, this strategy
is often cumbersome if there are a large number of destinations in the application or if there
are advanced destination management features unique to the JMS provider. Examples of
such advanced destination management include the creation of dynamic destinations or
support for a hierarchical namespace of destinations. The JmsTemplate
delegates the
resolution of a destination name to a JMS destination object that implements the
DestinationResolver
interface. DynamicDestinationResolver
is the default
implementation used by JmsTemplate
and accommodates resolving dynamic destinations. A
JndiDestinationResolver
is also provided to act as a service locator for
destinations contained in JNDI and optionally falls back to the behavior contained in
DynamicDestinationResolver
.
Quite often, the destinations used in a JMS application are only known at runtime and,
therefore, cannot be administratively created when the application is deployed. This is
often because there is shared application logic between interacting system components
that create destinations at runtime according to a well-known naming convention. Even
though the creation of dynamic destinations is not part of the JMS specification, most
vendors have provided this functionality. Dynamic destinations are created with a user-defined name,
which differentiates them from temporary destinations, and are often
not registered in JNDI. The API used to create dynamic destinations varies from provider
to provider since the properties associated with the destination are vendor-specific.
However, a simple implementation choice that is sometimes made by vendors is to
disregard the warnings in the JMS specification and to use the method TopicSession
createTopic(String topicName)
or the QueueSession
createQueue(String
queueName)
method to create a new destination with default destination properties. Depending
on the vendor implementation, DynamicDestinationResolver
can then also create a
physical destination instead of only resolving one.
The boolean property pubSubDomain
is used to configure the JmsTemplate
with
knowledge of what JMS domain is being used. By default, the value of this property is
false, indicating that the point-to-point domain, Queues
, is to be used. This property
(used by JmsTemplate
) determines the behavior of dynamic destination resolution through
implementations of the DestinationResolver
interface.
You can also configure the JmsTemplate
with a default destination through the
property defaultDestination
. The default destination is with send and receive
operations that do not refer to a specific destination.
2.1.4. Message Listener Containers
One of the most common uses of JMS messages in the EJB world is to drive message-driven
beans (MDBs). Spring offers a solution to create message-driven POJOs (MDPs) in a way
that does not tie a user to an EJB container. (See Asynchronous reception: Message-Driven POJOs for detailed
coverage of Spring’s MDP support.) Since Spring Framework 4.1, endpoint methods can be
annotated with @JmsListener
— see Annotation-driven Listener Endpoints for more details.
A message listener container is used to receive messages from a JMS message queue and
drive the MessageListener
that is injected into it. The listener container is
responsible for all threading of message reception and dispatches into the listener for
processing. A message listener container is the intermediary between an MDP and a
messaging provider and takes care of registering to receive messages, participating in
transactions, resource acquisition and release, exception conversion, and so on. This
lets you write the (possibly complex) business logic
associated with receiving a message (and possibly respond to it), and delegates
boilerplate JMS infrastructure concerns to the framework.
There are two standard JMS message listener containers packaged with Spring, each with its specialized feature set.
Using SimpleMessageListenerContainer
This message listener container is the simpler of the two standard flavors. It creates
a fixed number of JMS sessions and consumers at startup, registers the listener by using
the standard JMS MessageConsumer.setMessageListener()
method, and leaves it up the JMS
provider to perform listener callbacks. This variant does not allow for dynamic adaption
to runtime demands or for participation in externally managed transactions.
Compatibility-wise, it stays very close to the spirit of the standalone JMS
specification, but is generally not compatible with Jakarta EE’s JMS restrictions.
While SimpleMessageListenerContainer does not allow for participation in externally
managed transactions, it does support native JMS transactions. To enable this feature,
you can switch the sessionTransacted flag to true or, in the XML namespace, set the
acknowledge attribute to transacted . Exceptions thrown from your listener then lead
to a rollback, with the message getting redelivered. Alternatively, consider using
CLIENT_ACKNOWLEDGE mode, which provides redelivery in case of an exception as well but
does not use transacted Session instances and, therefore, does not include any other
Session operations (such as sending response messages) in the transaction protocol.
|
The default AUTO_ACKNOWLEDGE mode does not provide proper reliability guarantees.
Messages can get lost when listener execution fails (since the provider automatically
acknowledges each message after listener invocation, with no exceptions to be propagated to
the provider) or when the listener container shuts down (you can configure this by setting
the acceptMessagesWhileStopping flag). Make sure to use transacted sessions in case of
reliability needs (for example, for reliable queue handling and durable topic subscriptions).
|
Using DefaultMessageListenerContainer
This message listener container is used in most cases. In contrast to
SimpleMessageListenerContainer
, this container variant allows for dynamic adaptation
to runtime demands and is able to participate in externally managed transactions.
Each received message is registered with an XA transaction when configured with a
JtaTransactionManager
. As a result, processing may take advantage of XA transaction
semantics. This listener container strikes a good balance between low requirements on
the JMS provider, advanced functionality (such as participation in externally managed
transactions), and compatibility with Jakarta EE environments.
You can customize the cache level of the container. Note that, when no caching is enabled, a new connection and a new session is created for each message reception. Combining this with a non-durable subscription with high loads may lead to message loss. Make sure to use a proper cache level in such a case.
This container also has recoverable capabilities when the broker goes down. By default,
a simple BackOff
implementation retries every five seconds. You can specify
a custom BackOff
implementation for more fine-grained recovery options. See
ExponentialBackOff
for an example.
Like its sibling (SimpleMessageListenerContainer ),
DefaultMessageListenerContainer supports native JMS transactions and allows for
customizing the acknowledgment mode. If feasible for your scenario, This is strongly
recommended over externally managed transactions — that is, if you can live with
occasional duplicate messages in case of the JVM dying. Custom duplicate message
detection steps in your business logic can cover such situations — for example,
in the form of a business entity existence check or a protocol table check.
Any such arrangements are significantly more efficient than the alternative:
wrapping your entire processing with an XA transaction (through configuring your
DefaultMessageListenerContainer with an JtaTransactionManager ) to cover the
reception of the JMS message as well as the execution of the business logic in your
message listener (including database operations, etc.).
|
The default AUTO_ACKNOWLEDGE mode does not provide proper reliability guarantees.
Messages can get lost when listener execution fails (since the provider automatically
acknowledges each message after listener invocation, with no exceptions to be propagated to
the provider) or when the listener container shuts down (you can configure this by setting
the acceptMessagesWhileStopping flag). Make sure to use transacted sessions in case of
reliability needs (for example, for reliable queue handling and durable topic subscriptions).
|
2.1.5. Transaction Management
Spring provides a JmsTransactionManager
that manages transactions for a single JMS
ConnectionFactory
. This lets JMS applications leverage the managed-transaction
features of Spring, as described in
Transaction Management section of the Data Access chapter.
The JmsTransactionManager
performs local resource transactions, binding a JMS
Connection/Session pair from the specified ConnectionFactory
to the thread.
JmsTemplate
automatically detects such transactional resources and operates
on them accordingly.
In a Jakarta EE environment, the ConnectionFactory
pools Connection and Session instances,
so those resources are efficiently reused across transactions. In a standalone environment,
using Spring’s SingleConnectionFactory
result in a shared JMS Connection
, with
each transaction having its own independent Session
. Alternatively, consider the use
of a provider-specific pooling adapter, such as ActiveMQ’s PooledConnectionFactory
class.
You can also use JmsTemplate
with the JtaTransactionManager
and an XA-capable JMS
ConnectionFactory
to perform distributed transactions. Note that this requires the
use of a JTA transaction manager as well as a properly XA-configured ConnectionFactory.
(Check your Jakarta EE server’s or JMS provider’s documentation.)
Reusing code across a managed and unmanaged transactional environment can be confusing
when using the JMS API to create a Session
from a Connection
. This is because the
JMS API has only one factory method to create a Session
, and it requires values for the
transaction and acknowledgment modes. In a managed environment, setting these values is
the responsibility of the environment’s transactional infrastructure, so these values
are ignored by the vendor’s wrapper to the JMS Connection. When you use the JmsTemplate
in an unmanaged environment, you can specify these values through the use of the
properties sessionTransacted
and sessionAcknowledgeMode
. When you use a
PlatformTransactionManager
with JmsTemplate
, the template is always given a
transactional JMS Session
.
2.2. Sending a Message
The JmsTemplate
contains many convenience methods to send a message. Send
methods specify the destination by using a jakarta.jms.Destination
object, and others
specify the destination by using a String
in a JNDI lookup. The send
method
that takes no destination argument uses the default destination.
The following example uses the MessageCreator
callback to create a text message from the
supplied Session
object:
import jakarta.jms.ConnectionFactory;
import jakarta.jms.JMSException;
import jakarta.jms.Message;
import jakarta.jms.Queue;
import jakarta.jms.Session;
import org.springframework.jms.core.MessageCreator;
import org.springframework.jms.core.JmsTemplate;
public class JmsQueueSender {
private JmsTemplate jmsTemplate;
private Queue queue;
public void setConnectionFactory(ConnectionFactory cf) {
this.jmsTemplate = new JmsTemplate(cf);
}
public void setQueue(Queue queue) {
this.queue = queue;
}
public void simpleSend() {
this.jmsTemplate.send(this.queue, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage("hello queue world");
}
});
}
}
In the preceding example, the JmsTemplate
is constructed by passing a reference to a
ConnectionFactory
. As an alternative, a zero-argument constructor and
connectionFactory
is provided and can be used for constructing the instance in
JavaBean style (using a BeanFactory
or plain Java code). Alternatively, consider
deriving from Spring’s JmsGatewaySupport
convenience base class, which provides
pre-built bean properties for JMS configuration.
The send(String destinationName, MessageCreator creator)
method lets you send a
message by using the string name of the destination. If these names are registered in JNDI,
you should set the destinationResolver
property of the template to an instance of
JndiDestinationResolver
.
If you created the JmsTemplate
and specified a default destination, the
send(MessageCreator c)
sends a message to that destination.
2.2.1. Using Message Converters
To facilitate the sending of domain model objects, the JmsTemplate
has
various send methods that take a Java object as an argument for a message’s data
content. The overloaded methods convertAndSend()
and receiveAndConvert()
methods in
JmsTemplate
delegate the conversion process to an instance of the MessageConverter
interface. This interface defines a simple contract to convert between Java objects and
JMS messages. The default implementation (SimpleMessageConverter
) supports conversion
between String
and TextMessage
, byte[]
and BytesMessage
, and java.util.Map
and MapMessage
. By using the converter, you and your application code can focus on the
business object that is being sent or received through JMS and not be concerned with the
details of how it is represented as a JMS message.
The sandbox currently includes a MapMessageConverter
, which uses reflection to convert
between a JavaBean and a MapMessage
. Other popular implementation choices you might
implement yourself are converters that use an existing XML marshalling package (such as
JAXB or XStream) to create a TextMessage
that represents the object.
To accommodate the setting of a message’s properties, headers, and body that can not be
generically encapsulated inside a converter class, the MessagePostProcessor
interface
gives you access to the message after it has been converted but before it is sent. The
following example shows how to modify a message header and a property after a
java.util.Map
is converted to a message:
public void sendWithConversion() {
Map map = new HashMap();
map.put("Name", "Mark");
map.put("Age", new Integer(47));
jmsTemplate.convertAndSend("testQueue", map, new MessagePostProcessor() {
public Message postProcessMessage(Message message) throws JMSException {
message.setIntProperty("AccountID", 1234);
message.setJMSCorrelationID("123-00001");
return message;
}
});
}
This results in a message of the following form:
MapMessage={ Header={ ... standard headers ... CorrelationID={123-00001} } Properties={ AccountID={Integer:1234} } Fields={ Name={String:Mark} Age={Integer:47} } }
2.2.2. Using SessionCallback
and ProducerCallback
While the send operations cover many common usage scenarios, you might sometimes
want to perform multiple operations on a JMS Session
or MessageProducer
. The
SessionCallback
and ProducerCallback
expose the JMS Session
and Session
/
MessageProducer
pair, respectively. The execute()
methods on JmsTemplate
run
these callback methods.
2.3. Receiving a Message
This describes how to receive messages with JMS in Spring.
2.3.1. Synchronous Reception
While JMS is typically associated with asynchronous processing, you can
consume messages synchronously. The overloaded receive(..)
methods provide this
functionality. During a synchronous receive, the calling thread blocks until a message
becomes available. This can be a dangerous operation, since the calling thread can
potentially be blocked indefinitely. The receiveTimeout
property specifies how long
the receiver should wait before giving up waiting for a message.
2.3.2. Asynchronous reception: Message-Driven POJOs
Spring also supports annotated-listener endpoints through the use of the @JmsListener
annotation and provides an open infrastructure to register endpoints programmatically.
This is, by far, the most convenient way to setup an asynchronous receiver.
See Enable Listener Endpoint Annotations for more details.
|
In a fashion similar to a Message-Driven Bean (MDB) in the EJB world, the Message-Driven
POJO (MDP) acts as a receiver for JMS messages. The one restriction (but see
Using MessageListenerAdapter
) on an MDP is that it must implement
the jakarta.jms.MessageListener
interface. Note that, if your POJO receives messages
on multiple threads, it is important to ensure that your implementation is thread-safe.
The following example shows a simple implementation of an MDP:
import jakarta.jms.JMSException;
import jakarta.jms.Message;
import jakarta.jms.MessageListener;
import jakarta.jms.TextMessage;
public class ExampleListener implements MessageListener {
public void onMessage(Message message) {
if (message instanceof TextMessage textMessage) {
try {
System.out.println(textMessage.getText());
}
catch (JMSException ex) {
throw new RuntimeException(ex);
}
}
else {
throw new IllegalArgumentException("Message must be of type TextMessage");
}
}
}
Once you have implemented your MessageListener
, it is time to create a message listener
container.
The following example shows how to define and configure one of the message listener
containers that ships with Spring (in this case, DefaultMessageListenerContainer
):
<!-- this is the Message Driven POJO (MDP) -->
<bean id="messageListener" class="jmsexample.ExampleListener"/>
<!-- and this is the message listener container -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="messageListener"/>
</bean>
See the Spring javadoc of the various message listener containers (all of which implement MessageListenerContainer) for a full description of the features supported by each implementation.
2.3.3. Using the SessionAwareMessageListener
Interface
The SessionAwareMessageListener
interface is a Spring-specific interface that provides
a similar contract to the JMS MessageListener
interface but also gives the message-handling
method access to the JMS Session
from which the Message
was received.
The following listing shows the definition of the SessionAwareMessageListener
interface:
package org.springframework.jms.listener;
public interface SessionAwareMessageListener {
void onMessage(Message message, Session session) throws JMSException;
}
You can choose to have your MDPs implement this interface (in preference to the standard
JMS MessageListener
interface) if you want your MDPs to be able to respond to any
received messages (by using the Session
supplied in the onMessage(Message, Session)
method). All of the message listener container implementations that ship with Spring
have support for MDPs that implement either the MessageListener
or
SessionAwareMessageListener
interface. Classes that implement the
SessionAwareMessageListener
come with the caveat that they are then tied to Spring
through the interface. The choice of whether or not to use it is left entirely up to you
as an application developer or architect.
Note that the onMessage(..)
method of the SessionAwareMessageListener
interface throws JMSException
. In contrast to the standard JMS MessageListener
interface, when using the SessionAwareMessageListener
interface, it is the
responsibility of the client code to handle any thrown exceptions.
2.3.4. Using MessageListenerAdapter
The MessageListenerAdapter
class is the final component in Spring’s asynchronous
messaging support. In a nutshell, it lets you expose almost any class as an MDP
(though there are some constraints).
Consider the following interface definition:
public interface MessageDelegate {
void handleMessage(String message);
void handleMessage(Map message);
void handleMessage(byte[] message);
void handleMessage(Serializable message);
}
Notice that, although the interface extends neither the MessageListener
nor the
SessionAwareMessageListener
interface, you can still use it as an MDP by using the
MessageListenerAdapter
class. Notice also how the various message handling methods are
strongly typed according to the contents of the various Message
types that they can
receive and handle.
Now consider the following implementation of the MessageDelegate
interface:
public class DefaultMessageDelegate implements MessageDelegate {
// implementation elided for clarity...
}
In particular, note how the preceding implementation of the MessageDelegate
interface (the
DefaultMessageDelegate
class) has no JMS dependencies at all. It truly is a
POJO that we can make into an MDP through the following configuration:
<!-- this is the Message Driven POJO (MDP) -->
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<constructor-arg>
<bean class="jmsexample.DefaultMessageDelegate"/>
</constructor-arg>
</bean>
<!-- and this is the message listener container... -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="messageListener"/>
</bean>
The next example shows another MDP that can handle only receiving JMS
TextMessage
messages. Notice how the message handling method is actually called
receive
(the name of the message handling method in a MessageListenerAdapter
defaults to handleMessage
), but it is configurable (as you can see later in this section). Notice
also how the receive(..)
method is strongly typed to receive and respond only to JMS
TextMessage
messages.
The following listing shows the definition of the TextMessageDelegate
interface:
public interface TextMessageDelegate {
void receive(TextMessage message);
}
The following listing shows a class that implements the TextMessageDelegate
interface:
public class DefaultTextMessageDelegate implements TextMessageDelegate {
// implementation elided for clarity...
}
The configuration of the attendant MessageListenerAdapter
would then be as follows:
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<constructor-arg>
<bean class="jmsexample.DefaultTextMessageDelegate"/>
</constructor-arg>
<property name="defaultListenerMethod" value="receive"/>
<!-- we don't want automatic message context extraction -->
<property name="messageConverter">
<null/>
</property>
</bean>
Note that, if the messageListener
receives a JMS Message
of a type
other than TextMessage
, an IllegalStateException
is thrown (and subsequently
swallowed). Another of the capabilities of the MessageListenerAdapter
class is the
ability to automatically send back a response Message
if a handler method returns a
non-void value. Consider the following interface and class:
public interface ResponsiveTextMessageDelegate {
// notice the return type...
String receive(TextMessage message);
}
public class DefaultResponsiveTextMessageDelegate implements ResponsiveTextMessageDelegate {
// implementation elided for clarity...
}
If you use the DefaultResponsiveTextMessageDelegate
in conjunction with a
MessageListenerAdapter
, any non-null value that is returned from the execution of
the 'receive(..)'
method is (in the default configuration) converted into a
TextMessage
. The resulting TextMessage
is then sent to the Destination
(if
one exists) defined in the JMS Reply-To
property of the original Message
or the
default Destination
set on the MessageListenerAdapter
(if one has been configured).
If no Destination
is found, an InvalidDestinationException
is thrown
(note that this exception is not swallowed and propagates up the
call stack).
2.3.5. Processing Messages Within Transactions
Invoking a message listener within a transaction requires only reconfiguration of the listener container.
You can activate local resource transactions through the sessionTransacted
flag
on the listener container definition. Each message listener invocation then operates
within an active JMS transaction, with message reception rolled back in case of listener
execution failure. Sending a response message (through SessionAwareMessageListener
) is
part of the same local transaction, but any other resource operations (such as
database access) operate independently. This usually requires duplicate message
detection in the listener implementation, to cover the case where database processing
has committed but message processing failed to commit.
Consider the following bean definition:
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="messageListener"/>
<property name="sessionTransacted" value="true"/>
</bean>
To participate in an externally managed transaction, you need to configure a
transaction manager and use a listener container that supports externally managed
transactions (typically, DefaultMessageListenerContainer
).
To configure a message listener container for XA transaction participation, you want
to configure a JtaTransactionManager
(which, by default, delegates to the Jakarta EE
server’s transaction subsystem). Note that the underlying JMS ConnectionFactory
needs to
be XA-capable and properly registered with your JTA transaction coordinator. (Check your
Jakarta EE server’s configuration of JNDI resources.) This lets message reception as well
as (for example) database access be part of the same transaction (with unified commit
semantics, at the expense of XA transaction log overhead).
The following bean definition creates a transaction manager:
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
Then we need to add it to our earlier container configuration. The container takes care of the rest. The following example shows how to do so:
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="messageListener"/>
<property name="transactionManager" ref="transactionManager"/> (1)
</bean>
1 | Our transaction manager. |
2.4. Support for JCA Message Endpoints
Beginning with version 2.5, Spring also provides support for a JCA-based
MessageListener
container. The JmsMessageEndpointManager
tries to
automatically determine the ActivationSpec
class name from the provider’s
ResourceAdapter
class name. Therefore, it is typically possible to provide
Spring’s generic JmsActivationSpecConfig
, as the following example shows:
<bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
<property name="resourceAdapter" ref="resourceAdapter"/>
<property name="activationSpecConfig">
<bean class="org.springframework.jms.listener.endpoint.JmsActivationSpecConfig">
<property name="destinationName" value="myQueue"/>
</bean>
</property>
<property name="messageListener" ref="myMessageListener"/>
</bean>
Alternatively, you can set up a JmsMessageEndpointManager
with a given
ActivationSpec
object. The ActivationSpec
object may also come from a JNDI lookup
(using <jee:jndi-lookup>
). The following example shows how to do so:
<bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
<property name="resourceAdapter" ref="resourceAdapter"/>
<property name="activationSpec">
<bean class="org.apache.activemq.ra.ActiveMQActivationSpec">
<property name="destination" value="myQueue"/>
<property name="destinationType" value="jakarta.jms.Queue"/>
</bean>
</property>
<property name="messageListener" ref="myMessageListener"/>
</bean>
Using Spring’s ResourceAdapterFactoryBean
, you can configure the target ResourceAdapter
locally, as the following example shows:
<bean id="resourceAdapter" class="org.springframework.jca.support.ResourceAdapterFactoryBean">
<property name="resourceAdapter">
<bean class="org.apache.activemq.ra.ActiveMQResourceAdapter">
<property name="serverUrl" value="tcp://localhost:61616"/>
</bean>
</property>
<property name="workManager">
<bean class="org.springframework.jca.work.SimpleTaskWorkManager"/>
</property>
</bean>
The specified WorkManager
can also point to an environment-specific thread pool — typically through a SimpleTaskWorkManager
instance’s asyncTaskExecutor
property. Consider
defining a shared thread pool for all your ResourceAdapter
instances if you happen to
use multiple adapters.
In some environments (such as WebLogic 9 or above), you can instead obtain the entire ResourceAdapter
object
from JNDI (by using <jee:jndi-lookup>
). The Spring-based message
listeners can then interact with the server-hosted ResourceAdapter
, which also use the
server’s built-in WorkManager
.
See the javadoc for JmsMessageEndpointManager
,
JmsActivationSpecConfig
,
and ResourceAdapterFactoryBean
for more details.
Spring also provides a generic JCA message endpoint manager that is not tied to JMS:
org.springframework.jca.endpoint.GenericMessageEndpointManager
. This component allows
for using any message listener type (such as a JMS MessageListener
) and any
provider-specific ActivationSpec
object. See your JCA provider’s documentation to
find out about the actual capabilities of your connector, and see the
GenericMessageEndpointManager
javadoc for the Spring-specific configuration details.
JCA-based message endpoint management is very analogous to EJB 2.1 Message-Driven Beans. It uses the same underlying resource provider contract. As with EJB 2.1 MDBs, you can use any message listener interface supported by your JCA provider in the Spring context as well. Spring nevertheless provides explicit “convenience” support for JMS, because JMS is the most common endpoint API used with the JCA endpoint management contract. |
2.5. Annotation-driven Listener Endpoints
The easiest way to receive a message asynchronously is to use the annotated listener endpoint infrastructure. In a nutshell, it lets you expose a method of a managed bean as a JMS listener endpoint. The following example shows how to use it:
@Component
public class MyService {
@JmsListener(destination = "myDestination")
public void processOrder(String data) { ... }
}
The idea of the preceding example is that, whenever a message is available on the
jakarta.jms.Destination
myDestination
, the processOrder
method is invoked
accordingly (in this case, with the content of the JMS message, similar to
what the MessageListenerAdapter
provides).
The annotated endpoint infrastructure creates a message listener container
behind the scenes for each annotated method, by using a JmsListenerContainerFactory
.
Such a container is not registered against the application context but can be easily
located for management purposes by using the JmsListenerEndpointRegistry
bean.
@JmsListener is a repeatable annotation on Java 8, so you can associate
several JMS destinations with the same method by adding additional @JmsListener
declarations to it.
|
2.5.1. Enable Listener Endpoint Annotations
To enable support for @JmsListener
annotations, you can add @EnableJms
to one of
your @Configuration
classes, as the following example shows:
@Configuration
@EnableJms
public class AppConfig {
@Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setDestinationResolver(destinationResolver());
factory.setSessionTransacted(true);
factory.setConcurrency("3-10");
return factory;
}
}
By default, the infrastructure looks for a bean named jmsListenerContainerFactory
as the source for the factory to use to create message listener containers. In this
case (and ignoring the JMS infrastructure setup), you can invoke the processOrder
method with a core poll size of three threads and a maximum pool size of ten threads.
You can customize the listener container factory to use for each annotation or you can
configure an explicit default by implementing the JmsListenerConfigurer
interface.
The default is required only if at least one endpoint is registered without a specific
container factory. See the javadoc of classes that implement
JmsListenerConfigurer
for details and examples.
If you prefer XML configuration, you can use the <jms:annotation-driven>
element, as the following example shows:
<jms:annotation-driven/>
<bean id="jmsListenerContainerFactory"
class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destinationResolver" ref="destinationResolver"/>
<property name="sessionTransacted" value="true"/>
<property name="concurrency" value="3-10"/>
</bean>
2.5.2. Programmatic Endpoint Registration
JmsListenerEndpoint
provides a model of a JMS endpoint and is responsible for configuring
the container for that model. The infrastructure lets you programmatically configure endpoints
in addition to the ones that are detected by the JmsListener
annotation.
The following example shows how to do so:
@Configuration
@EnableJms
public class AppConfig implements JmsListenerConfigurer {
@Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId("myJmsEndpoint");
endpoint.setDestination("anotherQueue");
endpoint.setMessageListener(message -> {
// processing
});
registrar.registerEndpoint(endpoint);
}
}
In the preceding example, we used SimpleJmsListenerEndpoint
, which provides the actual
MessageListener
to invoke. However, you could also build your own endpoint variant
to describe a custom invocation mechanism.
Note that you could skip the use of @JmsListener
altogether
and programmatically register only your endpoints through JmsListenerConfigurer
.
2.5.3. Annotated Endpoint Method Signature
So far, we have been injecting a simple String
in our endpoint, but it can actually
have a very flexible method signature. In the following example, we rewrite it to inject the Order
with
a custom header:
@Component
public class MyService {
@JmsListener(destination = "myDestination")
public void processOrder(Order order, @Header("order_type") String orderType) {
...
}
}
The main elements you can inject in JMS listener endpoints are as follows:
-
The raw
jakarta.jms.Message
or any of its subclasses (provided that it matches the incoming message type). -
The
jakarta.jms.Session
for optional access to the native JMS API (for example, for sending a custom reply). -
The
org.springframework.messaging.Message
that represents the incoming JMS message. Note that this message holds both the custom and the standard headers (as defined byJmsHeaders
). -
@Header
-annotated method arguments to extract a specific header value, including standard JMS headers. -
A
@Headers
-annotated argument that must also be assignable tojava.util.Map
for getting access to all headers. -
A non-annotated element that is not one of the supported types (
Message
orSession
) is considered to be the payload. You can make that explicit by annotating the parameter with@Payload
. You can also turn on validation by adding an extra@Valid
.
The ability to inject Spring’s Message
abstraction is particularly useful to benefit
from all the information stored in the transport-specific message without relying on
transport-specific API. The following example shows how to do so:
@JmsListener(destination = "myDestination")
public void processOrder(Message<Order> order) { ... }
Handling of method arguments is provided by DefaultMessageHandlerMethodFactory
, which you can
further customize to support additional method arguments. You can customize the conversion and validation
support there as well.
For instance, if we want to make sure our Order
is valid before processing it, we can
annotate the payload with @Valid
and configure the necessary validator, as the following example shows:
@Configuration
@EnableJms
public class AppConfig implements JmsListenerConfigurer {
@Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(myJmsHandlerMethodFactory());
}
@Bean
public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setValidator(myValidator());
return factory;
}
}
2.5.4. Response Management
The existing support in MessageListenerAdapter
already lets your method have a non-void
return type. When that is the case, the result of
the invocation is encapsulated in a jakarta.jms.Message
, sent either in the destination specified
in the JMSReplyTo
header of the original message or in the default destination configured on
the listener. You can now set that default destination by using the @SendTo
annotation of the
messaging abstraction.
Assuming that our processOrder
method should now return an OrderStatus
, we can write it
to automatically send a response, as the following example shows:
@JmsListener(destination = "myDestination")
@SendTo("status")
public OrderStatus processOrder(Order order) {
// order processing
return status;
}
If you have several @JmsListener -annotated methods, you can also place the @SendTo
annotation at the class level to share a default reply destination.
|
If you need to set additional headers in a transport-independent manner, you can return a
Message
instead, with a method similar to the following:
@JmsListener(destination = "myDestination")
@SendTo("status")
public Message<OrderStatus> processOrder(Order order) {
// order processing
return MessageBuilder
.withPayload(status)
.setHeader("code", 1234)
.build();
}
If you need to compute the response destination at runtime, you can encapsulate your response
in a JmsResponse
instance that also provides the destination to use at runtime. We can rewrite the previous
example as follows:
@JmsListener(destination = "myDestination")
public JmsResponse<Message<OrderStatus>> processOrder(Order order) {
// order processing
Message<OrderStatus> response = MessageBuilder
.withPayload(status)
.setHeader("code", 1234)
.build();
return JmsResponse.forQueue(response, "status");
}
Finally, if you need to specify some QoS values for the response such as the priority or
the time to live, you can configure the JmsListenerContainerFactory
accordingly,
as the following example shows:
@Configuration
@EnableJms
public class AppConfig {
@Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
QosSettings replyQosSettings = new QosSettings();
replyQosSettings.setPriority(2);
replyQosSettings.setTimeToLive(10000);
factory.setReplyQosSettings(replyQosSettings);
return factory;
}
}
2.6. JMS Namespace Support
Spring provides an XML namespace for simplifying JMS configuration. To use the JMS namespace elements, you need to reference the JMS schema, as the following example shows:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms" (1)
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms https://www.springframework.org/schema/jms/spring-jms.xsd">
<!-- bean definitions here -->
</beans>
1 | Referencing the JMS schema. |
The namespace consists of three top-level elements: <annotation-driven/>
, <listener-container/>
and <jca-listener-container/>
. <annotation-driven/>
enables the use of annotation-driven listener endpoints. <listener-container/>
and <jca-listener-container/>
define shared listener container configuration and can contain <listener/>
child elements.
The following example shows a basic configuration for two listeners:
<jms:listener-container>
<jms:listener destination="queue.orders" ref="orderService" method="placeOrder"/>
<jms:listener destination="queue.confirmations" ref="confirmationLogger" method="log"/>
</jms:listener-container>
The preceding example is equivalent to creating two distinct listener container bean
definitions and two distinct MessageListenerAdapter
bean definitions, as shown
in Using MessageListenerAdapter
. In addition to the attributes shown
in the preceding example, the listener
element can contain several optional ones.
The following table describes all of the available attributes:
Attribute | Description |
---|---|
|
A bean name for the hosting listener container. If not specified, a bean name is automatically generated. |
|
The destination name for this listener, resolved through the |
|
The bean name of the handler object. |
|
The name of the handler method to invoke. If the |
|
The name of the default response destination to which to send response messages. This is
applied in case of a request message that does not carry a |
|
The name of the durable subscription, if any. |
|
An optional message selector for this listener. |
|
The number of concurrent sessions or consumers to start for this listener. This value can either be
a simple number indicating the maximum number (for example, |
The <listener-container/>
element also accepts several optional attributes. This
allows for customization of the various strategies (for example, taskExecutor
and
destinationResolver
) as well as basic JMS settings and resource references. By using
these attributes, you can define highly-customized listener containers while
still benefiting from the convenience of the namespace.
You can automatically expose such settings as a JmsListenerContainerFactory
by
specifying the id
of the bean to expose through the factory-id
attribute,
as the following example shows:
<jms:listener-container connection-factory="myConnectionFactory"
task-executor="myTaskExecutor"
destination-resolver="myDestinationResolver"
transaction-manager="myTransactionManager"
concurrency="10">
<jms:listener destination="queue.orders" ref="orderService" method="placeOrder"/>
<jms:listener destination="queue.confirmations" ref="confirmationLogger" method="log"/>
</jms:listener-container>
The following table describes all available attributes. See the class-level javadoc
of the AbstractMessageListenerContainer
and its concrete subclasses for more details on the individual properties. The javadoc
also provides a discussion of transaction choices and message redelivery scenarios.
Attribute | Description |
---|---|
|
The type of this listener container. The available options are |
|
A custom listener container implementation class as a fully qualified class name.
The default is Spring’s standard |
|
Exposes the settings defined by this element as a |
|
A reference to the JMS |
|
A reference to the Spring |
|
A reference to the |
|
A reference to the |
|
A reference to an |
|
The JMS destination type for this listener: |
|
The JMS destination type for responses: |
|
The JMS client ID for this listener container. You must specify it when you use durable subscriptions. |
|
The cache level for JMS resources: |
|
The native JMS acknowledge mode: |
|
A reference to an external |
|
The number of concurrent sessions or consumers to start for each listener. It can either be
a simple number indicating the maximum number (for example, |
|
The maximum number of messages to load into a single session. Note that raising this number might lead to starvation of concurrent consumers. |
|
The timeout (in milliseconds) to use for receive calls. The default is |
|
Specifies the |
|
Specifies the interval between recovery attempts, in milliseconds. It offers a convenient
way to create a |
|
The lifecycle phase within which this container should start and stop. The lower the
value, the earlier this container starts and the later it stops. The default is
|
Configuring a JCA-based listener container with the jms
schema support is very similar,
as the following example shows:
<jms:jca-listener-container resource-adapter="myResourceAdapter"
destination-resolver="myDestinationResolver"
transaction-manager="myTransactionManager"
concurrency="10">
<jms:listener destination="queue.orders" ref="myMessageListener"/>
</jms:jca-listener-container>
The following table describes the available configuration options for the JCA variant:
Attribute | Description |
---|---|
|
Exposes the settings defined by this element as a |
|
A reference to the JCA |
|
A reference to the |
|
A reference to the |
|
A reference to the |
|
The JMS destination type for this listener: |
|
The JMS destination type for responses: |
|
The JMS client ID for this listener container. It needs to be specified when using durable subscriptions. |
|
The native JMS acknowledge mode: |
|
A reference to a Spring |
|
The number of concurrent sessions or consumers to start for each listener. It can either be
a simple number indicating the maximum number (for example |
|
The maximum number of messages to load into a single session. Note that raising this number might lead to starvation of concurrent consumers. |
3. JMX
The JMX (Java Management Extensions) support in Spring provides features that let you easily and transparently integrate your Spring application into a JMX infrastructure.
Specifically, Spring’s JMX support provides four core features:
-
The automatic registration of any Spring bean as a JMX MBean.
-
A flexible mechanism for controlling the management interface of your beans.
-
The declarative exposure of MBeans over remote, JSR-160 connectors.
-
The simple proxying of both local and remote MBean resources.
These features are designed to work without coupling your application components to either Spring or JMX interfaces and classes. Indeed, for the most part, your application classes need not be aware of either Spring or JMX in order to take advantage of the Spring JMX features.
3.1. Exporting Your Beans to JMX
The core class in Spring’s JMX framework is the MBeanExporter
. This class is
responsible for taking your Spring beans and registering them with a JMX MBeanServer
.
For example, consider the following class:
package org.springframework.jmx;
public class JmxTestBean implements IJmxTestBean {
private String name;
private int age;
private boolean isSuperman;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int add(int x, int y) {
return x + y;
}
public void dontExposeMe() {
throw new RuntimeException();
}
}
To expose the properties and methods of this bean as attributes and operations of an
MBean, you can configure an instance of the MBeanExporter
class in your
configuration file and pass in the bean, as the following example shows:
<beans>
<!-- this bean must not be lazily initialized if the exporting is to happen -->
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
The pertinent bean definition from the preceding configuration snippet is the exporter
bean. The beans
property tells the MBeanExporter
exactly which of your beans must be
exported to the JMX MBeanServer
. In the default configuration, the key of each entry
in the beans
Map
is used as the ObjectName
for the bean referenced by the
corresponding entry value. You can change this behavior, as described in Controlling ObjectName
Instances for Your Beans.
With this configuration, the testBean
bean is exposed as an MBean under the
ObjectName
bean:name=testBean1
. By default, all public
properties of the bean
are exposed as attributes and all public
methods (except those inherited from the
Object
class) are exposed as operations.
MBeanExporter is a Lifecycle bean (see Startup and Shutdown Callbacks). By default, MBeans are exported as late as possible during
the application lifecycle. You can configure the phase at which
the export happens or disable automatic registration by setting the autoStartup flag.
|
3.1.1. Creating an MBeanServer
The configuration shown in the preceding section assumes that the
application is running in an environment that has one (and only one) MBeanServer
already running. In this case, Spring tries to locate the running MBeanServer
and
register your beans with that server (if any). This behavior is useful when your
application runs inside a container (such as Tomcat or IBM WebSphere) that has its
own MBeanServer
.
However, this approach is of no use in a standalone environment or when running inside
a container that does not provide an MBeanServer
. To address this, you can create an
MBeanServer
instance declaratively by adding an instance of the
org.springframework.jmx.support.MBeanServerFactoryBean
class to your configuration.
You can also ensure that a specific MBeanServer
is used by setting the value of the
MBeanExporter
instance’s server
property to the MBeanServer
value returned by an
MBeanServerFactoryBean
, as the following example shows:
<beans>
<bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean"/>
<!--
this bean needs to be eagerly pre-instantiated in order for the exporting to occur;
this means that it must not be marked as lazily initialized
-->
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
<property name="server" ref="mbeanServer"/>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
In the preceding example, an instance of MBeanServer
is created by the MBeanServerFactoryBean
and is
supplied to the MBeanExporter
through the server
property. When you supply your own
MBeanServer
instance, the MBeanExporter
does not try to locate a running
MBeanServer
and uses the supplied MBeanServer
instance. For this to work
correctly, you must have a JMX implementation on your classpath.
3.1.2. Reusing an Existing MBeanServer
If no server is specified, the MBeanExporter
tries to automatically detect a running
MBeanServer
. This works in most environments, where only one MBeanServer
instance is
used. However, when multiple instances exist, the exporter might pick the wrong server.
In such cases, you should use the MBeanServer
agentId
to indicate which instance to
be used, as the following example shows:
<beans>
<bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
<!-- indicate to first look for a server -->
<property name="locateExistingServerIfPossible" value="true"/>
<!-- search for the MBeanServer instance with the given agentId -->
<property name="agentId" value="MBeanServer_instance_agentId>"/>
</bean>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="server" ref="mbeanServer"/>
...
</bean>
</beans>
For platforms or cases where the existing MBeanServer
has a dynamic (or unknown)
agentId
that is retrieved through lookup methods, you should use
factory-method,
as the following example shows:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="server">
<!-- Custom MBeanServerLocator -->
<bean class="platform.package.MBeanServerLocator" factory-method="locateMBeanServer"/>
</property>
</bean>
<!-- other beans here -->
</beans>
3.1.3. Lazily Initialized MBeans
If you configure a bean with an MBeanExporter
that is also configured for lazy
initialization, the MBeanExporter
does not break this contract and avoids
instantiating the bean. Instead, it registers a proxy with the MBeanServer
and
defers obtaining the bean from the container until the first invocation on the proxy
occurs.
3.1.4. Automatic Registration of MBeans
Any beans that are exported through the MBeanExporter
and are already valid MBeans are
registered as-is with the MBeanServer
without further intervention from Spring. You can cause MBeans
to be automatically detected by the MBeanExporter
by setting the autodetect
property to true
, as the following example shows:
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="autodetect" value="true"/>
</bean>
<bean name="spring:mbean=true" class="org.springframework.jmx.export.TestDynamicMBean"/>
In the preceding example, the bean called spring:mbean=true
is already a valid JMX MBean
and is automatically registered by Spring. By default, a bean that is autodetected for JMX
registration has its bean name used as the ObjectName
. You can override this behavior,
as detailed in Controlling ObjectName
Instances for Your Beans.
3.1.5. Controlling the Registration Behavior
Consider the scenario where a Spring MBeanExporter
attempts to register an MBean
with an MBeanServer
by using the ObjectName
bean:name=testBean1
. If an MBean
instance has already been registered under that same ObjectName
, the default behavior
is to fail (and throw an InstanceAlreadyExistsException
).
You can control exactly what happens when an MBean
is
registered with an MBeanServer
. Spring’s JMX support allows for three different
registration behaviors to control the registration behavior when the registration
process finds that an MBean
has already been registered under the same ObjectName
.
The following table summarizes these registration behaviors:
Registration behavior | Explanation |
---|---|
|
This is the default registration behavior. If an |
|
If an |
|
If an |
The values in the preceding table are defined as enums on the RegistrationPolicy
class.
If you want to change the default registration behavior, you need to set the value of the
registrationPolicy
property on your MBeanExporter
definition to one of those
values.
The following example shows how to change from the default registration
behavior to the REPLACE_EXISTING
behavior:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
<property name="registrationPolicy" value="REPLACE_EXISTING"/>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
3.2. Controlling the Management Interface of Your Beans
In the example in the preceding section,
you had little control over the management interface of your bean. All of the public
properties and methods of each exported bean were exposed as JMX attributes and
operations, respectively. To exercise finer-grained control over exactly which
properties and methods of your exported beans are actually exposed as JMX attributes
and operations, Spring JMX provides a comprehensive and extensible mechanism for
controlling the management interfaces of your beans.
3.2.1. Using the MBeanInfoAssembler
Interface
Behind the scenes, the MBeanExporter
delegates to an implementation of the
org.springframework.jmx.export.assembler.MBeanInfoAssembler
interface, which is
responsible for defining the management interface of each bean that is exposed.
The default implementation,
org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler
,
defines a management interface that exposes all public properties and methods
(as you saw in the examples in the preceding sections). Spring provides two
additional implementations of the MBeanInfoAssembler
interface that let you
control the generated management interface by using either source-level metadata
or any arbitrary interface.
3.2.2. Using Source-level Metadata: Java Annotations
By using the MetadataMBeanInfoAssembler
, you can define the management interfaces
for your beans by using source-level metadata. The reading of metadata is encapsulated
by the org.springframework.jmx.export.metadata.JmxAttributeSource
interface.
Spring JMX provides a default implementation that uses Java annotations, namely
org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource
.
You must configure the MetadataMBeanInfoAssembler
with an implementation instance of
the JmxAttributeSource
interface for it to function correctly (there is no default).
To mark a bean for export to JMX, you should annotate the bean class with the
ManagedResource
annotation. You must mark each method you wish to expose as an operation
with the ManagedOperation
annotation and mark each property you wish to expose
with the ManagedAttribute
annotation. When marking properties, you can omit
either the annotation of the getter or the setter to create a write-only or read-only
attribute, respectively.
A ManagedResource -annotated bean must be public, as must the methods exposing
an operation or an attribute.
|
The following example shows the annotated version of the JmxTestBean
class that we
used in Creating an MBeanServer:
package org.springframework.jmx;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedAttribute;
@ManagedResource(
objectName="bean:name=testBean4",
description="My Managed Bean",
log=true,
logFile="jmx.log",
currencyTimeLimit=15,
persistPolicy="OnUpdate",
persistPeriod=200,
persistLocation="foo",
persistName="bar")
public class AnnotationTestBean implements IJmxTestBean {
private String name;
private int age;
@ManagedAttribute(description="The Age Attribute", currencyTimeLimit=15)
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@ManagedAttribute(description="The Name Attribute",
currencyTimeLimit=20,
defaultValue="bar",
persistPolicy="OnUpdate")
public void setName(String name) {
this.name = name;
}
@ManagedAttribute(defaultValue="foo", persistPeriod=300)
public String getName() {
return name;
}
@ManagedOperation(description="Add two numbers")
@ManagedOperationParameters({
@ManagedOperationParameter(name = "x", description = "The first number"),
@ManagedOperationParameter(name = "y", description = "The second number")})
public int add(int x, int y) {
return x + y;
}
public void dontExposeMe() {
throw new RuntimeException();
}
}
In the preceding example, you can see that the JmxTestBean
class is marked with the
ManagedResource
annotation and that this ManagedResource
annotation is configured
with a set of properties. These properties can be used to configure various aspects
of the MBean that is generated by the MBeanExporter
and are explained in greater
detail later in Source-level Metadata Types.
Both the age
and name
properties are annotated with the ManagedAttribute
annotation, but, in the case of the age
property, only the getter is marked.
This causes both of these properties to be included in the management interface
as attributes, but the age
attribute is read-only.
Finally, the add(int, int)
method is marked with the ManagedOperation
attribute,
whereas the dontExposeMe()
method is not. This causes the management interface to
contain only one operation (add(int, int)
) when you use the MetadataMBeanInfoAssembler
.
The following configuration shows how you can configure the MBeanExporter
to use the
MetadataMBeanInfoAssembler
:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="assembler" ref="assembler"/>
<property name="namingStrategy" ref="namingStrategy"/>
<property name="autodetect" value="true"/>
</bean>
<bean id="jmxAttributeSource"
class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
<!-- will create management interface using annotation metadata -->
<bean id="assembler"
class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
<property name="attributeSource" ref="jmxAttributeSource"/>
</bean>
<!-- will pick up the ObjectName from the annotation -->
<bean id="namingStrategy"
class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
<property name="attributeSource" ref="jmxAttributeSource"/>
</bean>
<bean id="testBean" class="org.springframework.jmx.AnnotationTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
In the preceding example, an MetadataMBeanInfoAssembler
bean has been configured with an
instance of the AnnotationJmxAttributeSource
class and passed to the MBeanExporter
through the assembler property. This is all that is required to take advantage of
metadata-driven management interfaces for your Spring-exposed MBeans.
3.2.3. Source-level Metadata Types
The following table describes the source-level metadata types that are available for use in Spring JMX:
Purpose | Annotation | Annotation Type |
---|---|---|
Mark all instances of a |
|
Class |
Mark a method as a JMX operation. |
|
Method |
Mark a getter or setter as one half of a JMX attribute. |
|
Method (only getters and setters) |
Define descriptions for operation parameters. |
|
Method |
The following table describes the configuration parameters that are available for use on these source-level metadata types:
Parameter | Description | Applies to |
---|---|---|
|
Used by |
|
|
Sets the friendly description of the resource, attribute or operation. |
|
|
Sets the value of the |
|
|
Sets the value of the |
|
|
Sets the value of the |
|
|
Sets the value of the |
|
|
Sets the value of the |
|
|
Sets the value of the |
|
|
Sets the value of the |
|
|
Sets the value of the |
|
|
Sets the display name of an operation parameter. |
|
|
Sets the index of an operation parameter. |
|
3.2.4. Using the AutodetectCapableMBeanInfoAssembler
Interface
To simplify configuration even further, Spring includes the
AutodetectCapableMBeanInfoAssembler
interface, which extends the MBeanInfoAssembler
interface to add support for autodetection of MBean resources. If you configure the
MBeanExporter
with an instance of AutodetectCapableMBeanInfoAssembler
, it is
allowed to “vote” on the inclusion of beans for exposure to JMX.
The only implementation of the AutodetectCapableMBeanInfo
interface is
the MetadataMBeanInfoAssembler
, which votes to include any bean that is marked
with the ManagedResource
attribute. The default approach in this case is to use the
bean name as the ObjectName
, which results in a configuration similar to the following:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<!-- notice how no 'beans' are explicitly configured here -->
<property name="autodetect" value="true"/>
<property name="assembler" ref="assembler"/>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
<bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
<property name="attributeSource">
<bean class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
</property>
</bean>
</beans>
Notice that, in the preceding configuration, no beans are passed to the MBeanExporter
.
However, the JmxTestBean
is still registered, since it is marked with the ManagedResource
attribute and the MetadataMBeanInfoAssembler
detects this and votes to include it.
The only problem with this approach is that the name of the JmxTestBean
now has business
meaning. You can address this issue by changing the default behavior for ObjectName
creation as defined in Controlling ObjectName
Instances for Your Beans.
3.2.5. Defining Management Interfaces by Using Java Interfaces
In addition to the MetadataMBeanInfoAssembler
, Spring also includes the
InterfaceBasedMBeanInfoAssembler
, which lets you constrain the methods and
properties that are exposed based on the set of methods defined in a collection of
interfaces.
Although the standard mechanism for exposing MBeans is to use interfaces and a simple
naming scheme, InterfaceBasedMBeanInfoAssembler
extends this functionality by
removing the need for naming conventions, letting you use more than one interface
and removing the need for your beans to implement the MBean interfaces.
Consider the following interface, which is used to define a management interface for the
JmxTestBean
class that we showed earlier:
public interface IJmxTestBean {
public int add(int x, int y);
public long myOperation();
public int getAge();
public void setAge(int age);
public void setName(String name);
public String getName();
}
This interface defines the methods and properties that are exposed as operations and attributes on the JMX MBean. The following code shows how to configure Spring JMX to use this interface as the definition for the management interface:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean5" value-ref="testBean"/>
</map>
</property>
<property name="assembler">
<bean class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler">
<property name="managedInterfaces">
<value>org.springframework.jmx.IJmxTestBean</value>
</property>
</bean>
</property>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
In the preceding example, the InterfaceBasedMBeanInfoAssembler
is configured to use the
IJmxTestBean
interface when constructing the management interface for any bean. It is
important to understand that beans processed by the InterfaceBasedMBeanInfoAssembler
are not required to implement the interface used to generate the JMX management
interface.
In the preceding case, the IJmxTestBean
interface is used to construct all management
interfaces for all beans. In many cases, this is not the desired behavior, and you may
want to use different interfaces for different beans. In this case, you can pass
InterfaceBasedMBeanInfoAssembler
a Properties
instance through the interfaceMappings
property, where the key of each entry is the bean name and the value of each entry is a
comma-separated list of interface names to use for that bean.
If no management interface is specified through either the managedInterfaces
or
interfaceMappings
properties, the InterfaceBasedMBeanInfoAssembler
reflects
on the bean and uses all of the interfaces implemented by that bean to create the
management interface.
3.2.6. Using MethodNameBasedMBeanInfoAssembler
MethodNameBasedMBeanInfoAssembler
lets you specify a list of method names
that are exposed to JMX as attributes and operations. The following code shows a sample
configuration:
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean5" value-ref="testBean"/>
</map>
</property>
<property name="assembler">
<bean class="org.springframework.jmx.export.assembler.MethodNameBasedMBeanInfoAssembler">
<property name="managedMethods">
<value>add,myOperation,getName,setName,getAge</value>
</property>
</bean>
</property>
</bean>
In the preceding example, you can see that the add
and myOperation
methods are exposed as JMX
operations, and getName()
, setName(String)
, and getAge()
are exposed as the
appropriate half of a JMX attribute. In the preceding code, the method mappings apply to
beans that are exposed to JMX. To control method exposure on a bean-by-bean basis, you can use
the methodMappings
property of MethodNameMBeanInfoAssembler
to map bean names to
lists of method names.
3.3. Controlling ObjectName
Instances for Your Beans
Behind the scenes, the MBeanExporter
delegates to an implementation of the
ObjectNamingStrategy
to obtain an ObjectName
instance for each of the beans it registers.
By default, the default implementation, KeyNamingStrategy
uses the key of the
beans
Map
as the ObjectName
. In addition, the KeyNamingStrategy
can map the key
of the beans
Map
to an entry in a Properties
file (or files) to resolve the
ObjectName
. In addition to the KeyNamingStrategy
, Spring provides two additional
ObjectNamingStrategy
implementations: the IdentityNamingStrategy
(which builds an
ObjectName
based on the JVM identity of the bean) and the MetadataNamingStrategy
(which
uses source-level metadata to obtain the ObjectName
).
3.3.1. Reading ObjectName
Instances from Properties
You can configure your own KeyNamingStrategy
instance and configure it to read
ObjectName
instances from a Properties
instance rather than use a bean key. The
KeyNamingStrategy
tries to locate an entry in the Properties
with a key
that corresponds to the bean key. If no entry is found or if the Properties
instance is
null
, the bean key itself is used.
The following code shows a sample configuration for the KeyNamingStrategy
:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="testBean" value-ref="testBean"/>
</map>
</property>
<property name="namingStrategy" ref="namingStrategy"/>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
<bean id="namingStrategy" class="org.springframework.jmx.export.naming.KeyNamingStrategy">
<property name="mappings">
<props>
<prop key="testBean">bean:name=testBean1</prop>
</props>
</property>
<property name="mappingLocations">
<value>names1.properties,names2.properties</value>
</property>
</bean>
</beans>
The preceding example configures an instance of KeyNamingStrategy
with a Properties
instance that
is merged from the Properties
instance defined by the mapping property and the
properties files located in the paths defined by the mappings property. In this
configuration, the testBean
bean is given an ObjectName
of bean:name=testBean1
,
since this is the entry in the Properties
instance that has a key corresponding to the
bean key.
If no entry in the Properties
instance can be found, the bean key name is used as
the ObjectName
.
3.3.2. Using MetadataNamingStrategy
MetadataNamingStrategy
uses the objectName
property of the ManagedResource
attribute on each bean to create the ObjectName
. The following code shows the
configuration for the MetadataNamingStrategy
:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="testBean" value-ref="testBean"/>
</map>
</property>
<property name="namingStrategy" ref="namingStrategy"/>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
<bean id="namingStrategy" class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
<property name="attributeSource" ref="attributeSource"/>
</bean>
<bean id="attributeSource"
class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
</beans>
If no objectName
has been provided for the ManagedResource
attribute, an
ObjectName
is created with the following
format: [fully-qualified-package-name]:type=[short-classname],name=[bean-name]. For
example, the generated ObjectName
for the following bean would be
com.example:type=MyClass,name=myBean
:
<bean id="myBean" class="com.example.MyClass"/>
3.3.3. Configuring Annotation-based MBean Export
If you prefer to use the annotation-based approach to define
your management interfaces, a convenience subclass of MBeanExporter
is available:
AnnotationMBeanExporter
. When defining an instance of this subclass, you no longer need the
namingStrategy
, assembler
, and attributeSource
configuration,
since it always uses standard Java annotation-based metadata (autodetection is
always enabled as well). In fact, rather than defining an MBeanExporter
bean, an even
simpler syntax is supported by the @EnableMBeanExport
@Configuration
annotation,
as the following example shows:
@Configuration
@EnableMBeanExport
public class AppConfig {
}
If you prefer XML-based configuration, the <context:mbean-export/>
element serves the
same purpose and is shown in the following listing:
<context:mbean-export/>
If necessary, you can provide a reference to a particular MBean server
, and the
defaultDomain
attribute (a property of AnnotationMBeanExporter
) accepts an alternate
value for the generated MBean ObjectName
domains. This is used in place of the
fully qualified package name as described in the previous section on
MetadataNamingStrategy, as the following example shows:
@EnableMBeanExport(server="myMBeanServer", defaultDomain="myDomain")
@Configuration
ContextConfiguration {
}
The following example shows the XML equivalent of the preceding annotation-based example:
<context:mbean-export server="myMBeanServer" default-domain="myDomain"/>
Do not use interface-based AOP proxies in combination with autodetection of JMX
annotations in your bean classes. Interface-based proxies “hide” the target class, which
also hides the JMX-managed resource annotations. Hence, you should use target-class proxies in that
case (through setting the 'proxy-target-class' flag on <aop:config/> ,
<tx:annotation-driven/> and so on). Otherwise, your JMX beans might be silently ignored at
startup.
|
3.4. Using JSR-160 Connectors
For remote access, Spring JMX module offers two FactoryBean
implementations inside the
org.springframework.jmx.support
package for creating both server- and client-side
connectors.
3.4.1. Server-side Connectors
To have Spring JMX create, start, and expose a JSR-160 JMXConnectorServer
, you can use the
following configuration:
<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean"/>
By default, ConnectorServerFactoryBean
creates a JMXConnectorServer
bound to
service:jmx:jmxmp://localhost:9875
. The serverConnector
bean thus exposes the
local MBeanServer
to clients through the JMXMP protocol on localhost, port 9875. Note
that the JMXMP protocol is marked as optional by the JSR 160 specification. Currently,
the main open-source JMX implementation, MX4J, and the one provided with the JDK
do not support JMXMP.
To specify another URL and register the JMXConnectorServer
itself with the
MBeanServer
, you can use the serviceUrl
and ObjectName
properties, respectively,
as the following example shows:
<bean id="serverConnector"
class="org.springframework.jmx.support.ConnectorServerFactoryBean">
<property name="objectName" value="connector:name=rmi"/>
<property name="serviceUrl"
value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector"/>
</bean>
If the ObjectName
property is set, Spring automatically registers your connector
with the MBeanServer
under that ObjectName
. The following example shows the full set of
parameters that you can pass to the ConnectorServerFactoryBean
when creating a
JMXConnector
:
<bean id="serverConnector"
class="org.springframework.jmx.support.ConnectorServerFactoryBean">
<property name="objectName" value="connector:name=iiop"/>
<property name="serviceUrl"
value="service:jmx:iiop://localhost/jndi/iiop://localhost:900/myconnector"/>
<property name="threaded" value="true"/>
<property name="daemon" value="true"/>
<property name="environment">
<map>
<entry key="someKey" value="someValue"/>
</map>
</property>
</bean>
Note that, when you use a RMI-based connector, you need the lookup service (tnameserv
or
rmiregistry
) to be started in order for the name registration to complete.
3.4.2. Client-side Connectors
To create an MBeanServerConnection
to a remote JSR-160-enabled MBeanServer
, you can use the
MBeanServerConnectionFactoryBean
, as the following example shows:
<bean id="clientConnector" class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
<property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"/>
</bean>
3.4.3. JMX over Hessian or SOAP
JSR-160 permits extensions to the way in which communication is done between the client and the server. The examples shown in the preceding sections use the mandatory RMI-based implementation required by the JSR-160 specification (IIOP and JRMP) and the (optional) JMXMP. By using other providers or JMX implementations (such as MX4J) you can take advantage of protocols such as SOAP or Hessian over simple HTTP or SSL and others, as the following example shows:
<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean">
<property name="objectName" value="connector:name=burlap"/>
<property name="serviceUrl" value="service:jmx:burlap://localhost:9874"/>
</bean>
In the preceding example, we used MX4J 3.0.0. See the official MX4J documentation for more information.
3.5. Accessing MBeans through Proxies
Spring JMX lets you create proxies that re-route calls to MBeans that are registered in a
local or remote MBeanServer
. These proxies provide you with a standard Java interface,
through which you can interact with your MBeans. The following code shows how to configure a
proxy for an MBean running in a local MBeanServer
:
<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
<property name="objectName" value="bean:name=testBean"/>
<property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/>
</bean>
In the preceding example, you can see that a proxy is created for the MBean registered under the
ObjectName
of bean:name=testBean
. The set of interfaces that the proxy implements
is controlled by the proxyInterfaces
property, and the rules for mapping methods and
properties on these interfaces to operations and attributes on the MBean are the same
rules used by the InterfaceBasedMBeanInfoAssembler
.
The MBeanProxyFactoryBean
can create a proxy to any MBean that is accessible through an
MBeanServerConnection
. By default, the local MBeanServer
is located and used, but
you can override this and provide an MBeanServerConnection
that points to a remote
MBeanServer
to cater for proxies that point to remote MBeans:
<bean id="clientConnector"
class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
<property name="serviceUrl" value="service:jmx:rmi://remotehost:9875"/>
</bean>
<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
<property name="objectName" value="bean:name=testBean"/>
<property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/>
<property name="server" ref="clientConnector"/>
</bean>
In the preceding example, we create an MBeanServerConnection
that points to a remote machine
that uses the MBeanServerConnectionFactoryBean
. This MBeanServerConnection
is then
passed to the MBeanProxyFactoryBean
through the server
property. The proxy that is
created forwards all invocations to the MBeanServer
through this
MBeanServerConnection
.
3.6. Notifications
Spring’s JMX offering includes comprehensive support for JMX notifications.
3.6.1. Registering Listeners for Notifications
Spring’s JMX support makes it easy to register any number of
NotificationListeners
with any number of MBeans (this includes MBeans exported by
Spring’s MBeanExporter
and MBeans registered through some other mechanism). For
example, consider the scenario where one would like to be informed (through a
Notification
) each and every time an attribute of a target MBean changes. The following
example writes notifications to the console:
package com.example;
import javax.management.AttributeChangeNotification;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
public class ConsoleLoggingNotificationListener
implements NotificationListener, NotificationFilter {
public void handleNotification(Notification notification, Object handback) {
System.out.println(notification);
System.out.println(handback);
}
public boolean isNotificationEnabled(Notification notification) {
return AttributeChangeNotification.class.isAssignableFrom(notification.getClass());
}
}
The following example adds ConsoleLoggingNotificationListener
(defined in the preceding
example) to notificationListenerMappings
:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
<property name="notificationListenerMappings">
<map>
<entry key="bean:name=testBean1">
<bean class="com.example.ConsoleLoggingNotificationListener"/>
</entry>
</map>
</property>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
With the preceding configuration in place, every time a JMX Notification
is broadcast from
the target MBean (bean:name=testBean1
), the ConsoleLoggingNotificationListener
bean
that was registered as a listener through the notificationListenerMappings
property is
notified. The ConsoleLoggingNotificationListener
bean can then take whatever action
it deems appropriate in response to the Notification
.
You can also use straight bean names as the link between exported beans and listeners, as the following example shows:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
<property name="notificationListenerMappings">
<map>
<entry key="testBean">
<bean class="com.example.ConsoleLoggingNotificationListener"/>
</entry>
</map>
</property>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
If you want to register a single NotificationListener
instance for all of the beans
that the enclosing MBeanExporter
exports, you can use the special wildcard (*
)
as the key for an entry in the notificationListenerMappings
property
map, as the following example shows:
<property name="notificationListenerMappings">
<map>
<entry key="*">
<bean class="com.example.ConsoleLoggingNotificationListener"/>
</entry>
</map>
</property>
If you need to do the inverse (that is, register a number of distinct listeners against
an MBean), you must instead use the notificationListeners
list property (in
preference to the notificationListenerMappings
property). This time, instead of
configuring a NotificationListener
for a single MBean, we configure
NotificationListenerBean
instances. A NotificationListenerBean
encapsulates a
NotificationListener
and the ObjectName
(or ObjectNames
) that it is to be
registered against in an MBeanServer
. The NotificationListenerBean
also encapsulates
a number of other properties, such as a NotificationFilter
and an arbitrary handback
object that can be used in advanced JMX notification scenarios.
The configuration when using NotificationListenerBean
instances is not wildly
different to what was presented previously, as the following example shows:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
<property name="notificationListeners">
<list>
<bean class="org.springframework.jmx.export.NotificationListenerBean">
<constructor-arg>
<bean class="com.example.ConsoleLoggingNotificationListener"/>
</constructor-arg>
<property name="mappedObjectNames">
<list>
<value>bean:name=testBean1</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
The preceding example is equivalent to the first notification example. Assume, then, that
we want to be given a handback object every time a Notification
is raised and that
we also want to filter out extraneous Notifications
by supplying a
NotificationFilter
. The following example accomplishes these goals:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean1"/>
<entry key="bean:name=testBean2" value-ref="testBean2"/>
</map>
</property>
<property name="notificationListeners">
<list>
<bean class="org.springframework.jmx.export.NotificationListenerBean">
<constructor-arg ref="customerNotificationListener"/>
<property name="mappedObjectNames">
<list>
<!-- handles notifications from two distinct MBeans -->
<value>bean:name=testBean1</value>
<value>bean:name=testBean2</value>
</list>
</property>
<property name="handback">
<bean class="java.lang.String">
<constructor-arg value="This could be anything..."/>
</bean>
</property>
<property name="notificationFilter" ref="customerNotificationListener"/>
</bean>
</list>
</property>
</bean>
<!-- implements both the NotificationListener and NotificationFilter interfaces -->
<bean id="customerNotificationListener" class="com.example.ConsoleLoggingNotificationListener"/>
<bean id="testBean1" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
<bean id="testBean2" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="ANOTHER TEST"/>
<property name="age" value="200"/>
</bean>
</beans>
(For a full discussion of what a handback object is and,
indeed, what a NotificationFilter
is, see the section of the JMX
specification (1.2) entitled 'The JMX Notification Model'.)
3.6.2. Publishing Notifications
Spring provides support not only for registering to receive Notifications
but also
for publishing Notifications
.
This section is really only relevant to Spring-managed beans that have
been exposed as MBeans through an MBeanExporter . Any existing user-defined MBeans should
use the standard JMX APIs for notification publication.
|
The key interface in Spring’s JMX notification publication support is the
NotificationPublisher
interface (defined in the
org.springframework.jmx.export.notification
package). Any bean that is going to be
exported as an MBean through an MBeanExporter
instance can implement the related
NotificationPublisherAware
interface to gain access to a NotificationPublisher
instance. The NotificationPublisherAware
interface supplies an instance of a
NotificationPublisher
to the implementing bean through a simple setter method,
which the bean can then use to publish Notifications
.
As stated in the javadoc of the
NotificationPublisher
interface, managed beans that publish events through the NotificationPublisher
mechanism are not responsible for the state management of notification listeners.
Spring’s JMX support takes care of handling all the JMX infrastructure issues.
All you need to do, as an application developer, is implement the
NotificationPublisherAware
interface and start publishing events by using the
supplied NotificationPublisher
instance. Note that the NotificationPublisher
is set after the managed bean has been registered with an MBeanServer
.
Using a NotificationPublisher
instance is quite straightforward. You create a JMX
Notification
instance (or an instance of an appropriate Notification
subclass),
populate the notification with the data pertinent to the event that is to be
published, and invoke the sendNotification(Notification)
on the
NotificationPublisher
instance, passing in the Notification
.
In the following example, exported instances of the JmxTestBean
publish a
NotificationEvent
every time the add(int, int)
operation is invoked:
package org.springframework.jmx;
import org.springframework.jmx.export.notification.NotificationPublisherAware;
import org.springframework.jmx.export.notification.NotificationPublisher;
import javax.management.Notification;
public class JmxTestBean implements IJmxTestBean, NotificationPublisherAware {
private String name;
private int age;
private boolean isSuperman;
private NotificationPublisher publisher;
// other getters and setters omitted for clarity
public int add(int x, int y) {
int answer = x + y;
this.publisher.sendNotification(new Notification("add", this, 0));
return answer;
}
public void dontExposeMe() {
throw new RuntimeException();
}
public void setNotificationPublisher(NotificationPublisher notificationPublisher) {
this.publisher = notificationPublisher;
}
}
The NotificationPublisher
interface and the machinery to get it all working is one of
the nicer features of Spring’s JMX support. It does, however, come with the price tag of
coupling your classes to both Spring and JMX. As always, the advice here is to be
pragmatic. If you need the functionality offered by the NotificationPublisher
and
you can accept the coupling to both Spring and JMX, then do so.
3.7. Further Resources
This section contains links to further resources about JMX:
-
The JMX homepage at Oracle.
-
The JMX specification (JSR-000003).
-
The JMX Remote API specification (JSR-000160).
-
The MX4J homepage. (MX4J is an open-source implementation of various JMX specs.)
4. Email
This section describes how to send email with the Spring Framework.
The Spring Framework provides a helpful utility library for sending email that shields you from the specifics of the underlying mailing system and is responsible for low-level resource handling on behalf of the client.
The org.springframework.mail
package is the root level package for the Spring
Framework’s email support. The central interface for sending emails is the MailSender
interface. A simple value object that encapsulates the properties of a simple mail such
as from
and to
(plus many others) is the SimpleMailMessage
class. This package
also contains a hierarchy of checked exceptions that provide a higher level of
abstraction over the lower level mail system exceptions, with the root exception being
MailException
. See the javadoc
for more information on the rich mail exception hierarchy.
The org.springframework.mail.javamail.JavaMailSender
interface adds specialized
JavaMail features, such as MIME message support to the MailSender
interface
(from which it inherits). JavaMailSender
also provides a callback interface called
org.springframework.mail.javamail.MimeMessagePreparator
for preparing a MimeMessage
.
4.1. Usage
Assume that we have a business interface called OrderManager
, as the following example shows:
public interface OrderManager {
void placeOrder(Order order);
}
Further assume that we have a requirement stating that an email message with an order number needs to be generated and sent to a customer who placed the relevant order.
4.1.1. Basic MailSender
and SimpleMailMessage
Usage
The following example shows how to use MailSender
and SimpleMailMessage
to send an
email when someone places an order:
import org.springframework.mail.MailException;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
public class SimpleOrderManager implements OrderManager {
private MailSender mailSender;
private SimpleMailMessage templateMessage;
public void setMailSender(MailSender mailSender) {
this.mailSender = mailSender;
}
public void setTemplateMessage(SimpleMailMessage templateMessage) {
this.templateMessage = templateMessage;
}
public void placeOrder(Order order) {
// Do the business calculations...
// Call the collaborators to persist the order...
// Create a thread safe "copy" of the template message and customize it
SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
msg.setTo(order.getCustomer().getEmailAddress());
msg.setText(
"Dear " + order.getCustomer().getFirstName()
+ order.getCustomer().getLastName()
+ ", thank you for placing order. Your order number is "
+ order.getOrderNumber());
try {
this.mailSender.send(msg);
}
catch (MailException ex) {
// simply log it and go on...
System.err.println(ex.getMessage());
}
}
}
The following example shows the bean definitions for the preceding code:
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="mail.mycompany.example"/>
</bean>
<!-- this is a template message that we can pre-load with default state -->
<bean id="templateMessage" class="org.springframework.mail.SimpleMailMessage">
<property name="from" value="[email protected]"/>
<property name="subject" value="Your order"/>
</bean>
<bean id="orderManager" class="com.mycompany.businessapp.support.SimpleOrderManager">
<property name="mailSender" ref="mailSender"/>
<property name="templateMessage" ref="templateMessage"/>
</bean>
4.1.2. Using JavaMailSender
and MimeMessagePreparator
This section describes another implementation of OrderManager
that uses the MimeMessagePreparator
callback interface. In the following example, the mailSender
property is of type
JavaMailSender
so that we are able to use the JavaMail MimeMessage
class:
import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeMessage;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessagePreparator;
public class SimpleOrderManager implements OrderManager {
private JavaMailSender mailSender;
public void setMailSender(JavaMailSender mailSender) {
this.mailSender = mailSender;
}
public void placeOrder(final Order order) {
// Do the business calculations...
// Call the collaborators to persist the order...
MimeMessagePreparator preparator = new MimeMessagePreparator() {
public void prepare(MimeMessage mimeMessage) throws Exception {
mimeMessage.setRecipient(Message.RecipientType.TO,
new InternetAddress(order.getCustomer().getEmailAddress()));
mimeMessage.setFrom(new InternetAddress("[email protected]"));
mimeMessage.setText("Dear " + order.getCustomer().getFirstName() + " " +
order.getCustomer().getLastName() + ", thanks for your order. " +
"Your order number is " + order.getOrderNumber() + ".");
}
};
try {
this.mailSender.send(preparator);
}
catch (MailException ex) {
// simply log it and go on...
System.err.println(ex.getMessage());
}
}
}
The mail code is a crosscutting concern and could well be a candidate for
refactoring into a custom Spring AOP aspect, which could then
be run at appropriate joinpoints on the OrderManager target.
|
The Spring Framework’s mail support ships with the standard JavaMail implementation. See the relevant javadoc for more information.
4.2. Using the JavaMail MimeMessageHelper
A class that comes in pretty handy when dealing with JavaMail messages is
org.springframework.mail.javamail.MimeMessageHelper
, which shields you from
having to use the verbose JavaMail API. Using the MimeMessageHelper
, it is
pretty easy to create a MimeMessage
, as the following example shows:
// of course you would use DI in any real-world cases
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");
MimeMessage message = sender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message);
helper.setTo("[email protected]");
helper.setText("Thank you for ordering!");
sender.send(message);
4.2.1. Sending Attachments and Inline Resources
Multipart email messages allow for both attachments and inline resources. Examples of inline resources include an image or a stylesheet that you want to use in your message but that you do not want displayed as an attachment.
Attachments
The following example shows you how to use the MimeMessageHelper
to send an email
with a single JPEG image attachment:
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");
MimeMessage message = sender.createMimeMessage();
// use the true flag to indicate you need a multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo("[email protected]");
helper.setText("Check out this image!");
// let's attach the infamous windows Sample file (this time copied to c:/)
FileSystemResource file = new FileSystemResource(new File("c:/Sample.jpg"));
helper.addAttachment("CoolImage.jpg", file);
sender.send(message);
Inline Resources
The following example shows you how to use the MimeMessageHelper
to send an email
with an inline image:
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");
MimeMessage message = sender.createMimeMessage();
// use the true flag to indicate you need a multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo("[email protected]");
// use the true flag to indicate the text included is HTML
helper.setText("<html><body><img src='cid:identifier1234'></body></html>", true);
// let's include the infamous windows Sample file (this time copied to c:/)
FileSystemResource res = new FileSystemResource(new File("c:/Sample.jpg"));
helper.addInline("identifier1234", res);
sender.send(message);
Inline resources are added to the MimeMessage by using the specified Content-ID
(identifier1234 in the above example). The order in which you add the text
and the resource are very important. Be sure to first add the text and then
the resources. If you are doing it the other way around, it does not work.
|
4.2.2. Creating Email Content by Using a Templating Library
The code in the examples shown in the previous sections explicitly created the content of the email message,
by using methods calls such as message.setText(..)
. This is fine for simple cases, and it
is okay in the context of the aforementioned examples, where the intent was to show you
the very basics of the API.
In your typical enterprise application, though, developers often do not create the content of email messages by using the previously shown approach for a number of reasons:
-
Creating HTML-based email content in Java code is tedious and error prone.
-
There is no clear separation between display logic and business logic.
-
Changing the display structure of the email content requires writing Java code, recompiling, redeploying, and so on.
Typically, the approach taken to address these issues is to use a template library (such as FreeMarker) to define the display structure of email content. This leaves your code tasked only with creating the data that is to be rendered in the email template and sending the email. It is definitely a best practice when the content of your email messages becomes even moderately complex, and, with the Spring Framework’s support classes for FreeMarker, it becomes quite easy to do.
5. Task Execution and Scheduling
The Spring Framework provides abstractions for the asynchronous execution and scheduling of
tasks with the TaskExecutor
and TaskScheduler
interfaces, respectively. Spring also
features implementations of those interfaces that support thread pools or delegation to
CommonJ within an application server environment. Ultimately, the use of these
implementations behind the common interfaces abstracts away the differences between Java
SE 5, Java SE 6, and Jakarta EE environments.
Spring also features integration classes to support scheduling with the Timer
(part of the JDK since 1.3) and the Quartz Scheduler ( https://www.quartz-scheduler.org/).
You can set up both of those schedulers by using a FactoryBean
with optional references to
Timer
or Trigger
instances, respectively. Furthermore, a convenience class for both
the Quartz Scheduler and the Timer
is available that lets you invoke a method of
an existing target object (analogous to the normal MethodInvokingFactoryBean
operation).
5.1. The Spring TaskExecutor
Abstraction
Executors are the JDK name for the concept of thread pools. The “executor” naming is due to the fact that there is no guarantee that the underlying implementation is actually a pool. An executor may be single-threaded or even synchronous. Spring’s abstraction hides implementation details between the Java SE and Jakarta EE environments.
Spring’s TaskExecutor
interface is identical to the java.util.concurrent.Executor
interface. In fact, originally, its primary reason for existence was to abstract away
the need for Java 5 when using thread pools. The interface has a single method
(execute(Runnable task)
) that accepts a task for execution based on the semantics
and configuration of the thread pool.
The TaskExecutor
was originally created to give other Spring components an abstraction
for thread pooling where needed. Components such as the ApplicationEventMulticaster
,
JMS’s AbstractMessageListenerContainer
, and Quartz integration all use the
TaskExecutor
abstraction to pool threads. However, if your beans need thread pooling
behavior, you can also use this abstraction for your own needs.
5.1.1. TaskExecutor
Types
Spring includes a number of pre-built implementations of TaskExecutor
.
In all likelihood, you should never need to implement your own.
The variants that Spring provides are as follows:
-
SyncTaskExecutor
: This implementation does not run invocations asynchronously. Instead, each invocation takes place in the calling thread. It is primarily used in situations where multi-threading is not necessary, such as in simple test cases. -
SimpleAsyncTaskExecutor
: This implementation does not reuse any threads. Rather, it starts up a new thread for each invocation. However, it does support a concurrency limit that blocks any invocations that are over the limit until a slot has been freed up. If you are looking for true pooling, seeThreadPoolTaskExecutor
, later in this list. -
ConcurrentTaskExecutor
: This implementation is an adapter for ajava.util.concurrent.Executor
instance. There is an alternative (ThreadPoolTaskExecutor
) that exposes theExecutor
configuration parameters as bean properties. There is rarely a need to useConcurrentTaskExecutor
directly. However, if theThreadPoolTaskExecutor
is not flexible enough for your needs,ConcurrentTaskExecutor
is an alternative. -
ThreadPoolTaskExecutor
: This implementation is most commonly used. It exposes bean properties for configuring ajava.util.concurrent.ThreadPoolExecutor
and wraps it in aTaskExecutor
. If you need to adapt to a different kind ofjava.util.concurrent.Executor
, we recommend that you use aConcurrentTaskExecutor
instead. -
DefaultManagedTaskExecutor
: This implementation uses a JNDI-obtainedManagedExecutorService
in a JSR-236 compatible runtime environment (such as a Jakarta EE application server), replacing a CommonJ WorkManager for that purpose.
5.1.2. Using a TaskExecutor
Spring’s TaskExecutor
implementations are used as simple JavaBeans. In the following example,
we define a bean that uses the ThreadPoolTaskExecutor
to asynchronously print
out a set of messages:
import org.springframework.core.task.TaskExecutor;
public class TaskExecutorExample {
private class MessagePrinterTask implements Runnable {
private String message;
public MessagePrinterTask(String message) {
this.message = message;
}
public void run() {
System.out.println(message);
}
}
private TaskExecutor taskExecutor;
public TaskExecutorExample(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
public void printMessages() {
for(int i = 0; i < 25; i++) {
taskExecutor.execute(new MessagePrinterTask("Message" + i));
}
}
}
As you can see, rather than retrieving a thread from the pool and executing it yourself,
you add your Runnable
to the queue. Then the TaskExecutor
uses its internal rules to
decide when the task gets run.
To configure the rules that the TaskExecutor
uses, we expose simple bean properties:
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5"/>
<property name="maxPoolSize" value="10"/>
<property name="queueCapacity" value="25"/>
</bean>
<bean id="taskExecutorExample" class="TaskExecutorExample">
<constructor-arg ref="taskExecutor"/>
</bean>
5.2. The Spring TaskScheduler
Abstraction
In addition to the TaskExecutor
abstraction, Spring 3.0 introduced a TaskScheduler
with a variety of methods for scheduling tasks to run at some point in the future.
The following listing shows the TaskScheduler
interface definition:
public interface TaskScheduler {
ScheduledFuture schedule(Runnable task, Trigger trigger);
ScheduledFuture schedule(Runnable task, Instant startTime);
ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);
ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);
The simplest method is the one named schedule
that takes only a Runnable
and an Instant
.
That causes the task to run once after the specified time. All of the other methods
are capable of scheduling tasks to run repeatedly. The fixed-rate and fixed-delay
methods are for simple, periodic execution, but the method that accepts a Trigger
is
much more flexible.
5.2.1. Trigger
Interface
The Trigger
interface is essentially inspired by JSR-236 which, as of Spring 3.0,
was not yet officially implemented. The basic idea of the Trigger
is that execution
times may be determined based on past execution outcomes or even arbitrary conditions.
If these determinations do take into account the outcome of the preceding execution,
that information is available within a TriggerContext
. The Trigger
interface itself
is quite simple, as the following listing shows:
public interface Trigger {
Date nextExecutionTime(TriggerContext triggerContext);
}
The TriggerContext
is the most important part. It encapsulates all of
the relevant data and is open for extension in the future, if necessary. The
TriggerContext
is an interface (a SimpleTriggerContext
implementation is used by
default). The following listing shows the available methods for Trigger
implementations.
public interface TriggerContext {
Date lastScheduledExecutionTime();
Date lastActualExecutionTime();
Date lastCompletionTime();
}
5.2.2. Trigger
Implementations
Spring provides two implementations of the Trigger
interface. The most interesting one
is the CronTrigger
. It enables the scheduling of tasks based on
cron expressions.
For example, the following task is scheduled to run 15 minutes past each hour but only
during the 9-to-5 “business hours” on weekdays:
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
The other implementation is a PeriodicTrigger
that accepts a fixed
period, an optional initial delay value, and a boolean to indicate whether the period
should be interpreted as a fixed-rate or a fixed-delay. Since the TaskScheduler
interface already defines methods for scheduling tasks at a fixed rate or with a
fixed delay, those methods should be used directly whenever possible. The value of the
PeriodicTrigger
implementation is that you can use it within components that rely on
the Trigger
abstraction. For example, it may be convenient to allow periodic triggers,
cron-based triggers, and even custom trigger implementations to be used interchangeably.
Such a component could take advantage of dependency injection so that you can configure such Triggers
externally and, therefore, easily modify or extend them.
5.2.3. TaskScheduler
implementations
As with Spring’s TaskExecutor
abstraction, the primary benefit of the TaskScheduler
arrangement is that an application’s scheduling needs are decoupled from the deployment
environment. This abstraction level is particularly relevant when deploying to an
application server environment where threads should not be created directly by the
application itself. For such scenarios, Spring provides a TimerManagerTaskScheduler
that delegates to a CommonJ TimerManager
on WebLogic or WebSphere as well as a more recent
DefaultManagedTaskScheduler
that delegates to a JSR-236 ManagedScheduledExecutorService
in a Jakarta EE environment. Both are typically configured with a JNDI lookup.
Whenever external thread management is not a requirement, a simpler alternative is
a local ScheduledExecutorService
setup within the application, which can be adapted
through Spring’s ConcurrentTaskScheduler
. As a convenience, Spring also provides a
ThreadPoolTaskScheduler
, which internally delegates to a ScheduledExecutorService
to provide common bean-style configuration along the lines of ThreadPoolTaskExecutor
.
These variants work perfectly fine for locally embedded thread pool setups in lenient
application server environments, as well — in particular on Tomcat and Jetty.
5.3. Annotation Support for Scheduling and Asynchronous Execution
Spring provides annotation support for both task scheduling and asynchronous method execution.
5.3.1. Enable Scheduling Annotations
To enable support for @Scheduled
and @Async
annotations, you can add @EnableScheduling
and
@EnableAsync
to one of your @Configuration
classes, as the following example shows:
@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}
You can pick and choose the relevant annotations for your application. For example,
if you need only support for @Scheduled
, you can omit @EnableAsync
. For more
fine-grained control, you can additionally implement the SchedulingConfigurer
interface, the AsyncConfigurer
interface, or both. See the
SchedulingConfigurer
and AsyncConfigurer
javadoc for full details.
If you prefer XML configuration, you can use the <task:annotation-driven>
element,
as the following example shows:
<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="5"/>
<task:scheduler id="myScheduler" pool-size="10"/>
Note that, with the preceding XML, an executor reference is provided for handling those
tasks that correspond to methods with the @Async
annotation, and the scheduler
reference is provided for managing those methods annotated with @Scheduled
.
The default advice mode for processing @Async annotations is proxy which allows
for interception of calls through the proxy only. Local calls within the same class
cannot get intercepted that way. For a more advanced mode of interception, consider
switching to aspectj mode in combination with compile-time or load-time weaving.
|
5.3.2. The @Scheduled
annotation
You can add the @Scheduled
annotation to a method, along with trigger metadata. For
example, the following method is invoked every five seconds (5000 milliseconds) with a
fixed delay, meaning that the period is measured from the completion time of each
preceding invocation.
@Scheduled(fixedDelay = 5000)
public void doSomething() {
// something that should run periodically
}
By default, milliseconds will be used as the time unit for fixed delay, fixed rate, and
initial delay values. If you would like to use a different time unit such as seconds or
minutes, you can configure this via the For example, the previous example can also be written as follows.
|
If you need a fixed-rate execution, you can use the fixedRate
attribute within the
annotation. The following method is invoked every five seconds (measured between the
successive start times of each invocation).
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS)
public void doSomething() {
// something that should run periodically
}
For fixed-delay and fixed-rate tasks, you can specify an initial delay by indicating the
amount of time to wait before the first execution of the method, as the following
fixedRate
example shows.
@Scheduled(initialDelay = 1000, fixedRate = 5000)
public void doSomething() {
// something that should run periodically
}
If simple periodic scheduling is not expressive enough, you can provide a cron expression. The following example runs only on weekdays:
@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
// something that should run on weekdays only
}
You can also use the zone attribute to specify the time zone in which the cron
expression is resolved.
|
Notice that the methods to be scheduled must have void returns and must not accept any arguments. If the method needs to interact with other objects from the application context, those would typically have been provided through dependency injection.
As of Spring Framework 4.3, Make sure that you are not initializing multiple instances of the same |
5.3.3. The @Async
annotation
You can provide the @Async
annotation on a method so that invocation of that method
occurs asynchronously. In other words, the caller returns immediately upon
invocation, while the actual execution of the method occurs in a task that has been
submitted to a Spring TaskExecutor
. In the simplest case, you can apply the annotation
to a method that returns void
, as the following example shows:
@Async
void doSomething() {
// this will be run asynchronously
}
Unlike the methods annotated with the @Scheduled
annotation, these methods can expect
arguments, because they are invoked in the “normal” way by callers at runtime rather
than from a scheduled task being managed by the container. For example, the following code is
a legitimate application of the @Async
annotation:
@Async
void doSomething(String s) {
// this will be run asynchronously
}
Even methods that return a value can be invoked asynchronously. However, such methods
are required to have a Future
-typed return value. This still provides the benefit of
asynchronous execution so that the caller can perform other tasks prior to calling
get()
on that Future
. The following example shows how to use @Async
on a method
that returns a value:
@Async
Future<String> returnSomething(int i) {
// this will be run asynchronously
}
@Async methods may not only declare a regular java.util.concurrent.Future return type
but also Spring’s org.springframework.util.concurrent.ListenableFuture or, as of Spring
4.2, JDK 8’s java.util.concurrent.CompletableFuture , for richer interaction with the
asynchronous task and for immediate composition with further processing steps.
|
You can not use @Async
in conjunction with lifecycle callbacks such as
@PostConstruct
. To asynchronously initialize Spring beans, you currently have to use
a separate initializing Spring bean that then invokes the @Async
annotated method on the
target, as the following example shows:
public class SampleBeanImpl implements SampleBean {
@Async
void doSomething() {
// ...
}
}
public class SampleBeanInitializer {
private final SampleBean bean;
public SampleBeanInitializer(SampleBean bean) {
this.bean = bean;
}
@PostConstruct
public void initialize() {
bean.doSomething();
}
}
There is no direct XML equivalent for @Async , since such methods should be designed
for asynchronous execution in the first place, not externally re-declared to be asynchronous.
However, you can manually set up Spring’s AsyncExecutionInterceptor with Spring AOP,
in combination with a custom pointcut.
|
5.3.4. Executor Qualification with @Async
By default, when specifying @Async
on a method, the executor that is used is the
one configured when enabling async support,
i.e. the “annotation-driven” element if you are using XML or your AsyncConfigurer
implementation, if any. However, you can use the value
attribute of the @Async
annotation when you need to indicate that an executor other than the default should be
used when executing a given method. The following example shows how to do so:
@Async("otherExecutor")
void doSomething(String s) {
// this will be run asynchronously by "otherExecutor"
}
In this case, "otherExecutor"
can be the name of any Executor
bean in the Spring
container, or it may be the name of a qualifier associated with any Executor
(for example, as
specified with the <qualifier>
element or Spring’s @Qualifier
annotation).
5.3.5. Exception Management with @Async
When an @Async
method has a Future
-typed return value, it is easy to manage
an exception that was thrown during the method execution, as this exception is
thrown when calling get
on the Future
result. With a void
return type,
however, the exception is uncaught and cannot be transmitted. You can provide an
AsyncUncaughtExceptionHandler
to handle such exceptions. The following example shows
how to do so:
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
// handle exception
}
}
By default, the exception is merely logged. You can define a custom AsyncUncaughtExceptionHandler
by using AsyncConfigurer
or the <task:annotation-driven/>
XML element.
5.4. The task
Namespace
As of version 3.0, Spring includes an XML namespace for configuring TaskExecutor
and
TaskScheduler
instances. It also provides a convenient way to configure tasks to be
scheduled with a trigger.
5.4.1. The 'scheduler' Element
The following element creates a ThreadPoolTaskScheduler
instance with the
specified thread pool size:
<task:scheduler id="scheduler" pool-size="10"/>
The value provided for the id
attribute is used as the prefix for thread names
within the pool. The scheduler
element is relatively straightforward. If you do not
provide a pool-size
attribute, the default thread pool has only a single thread.
There are no other configuration options for the scheduler.
5.4.2. The executor
Element
The following creates a ThreadPoolTaskExecutor
instance:
<task:executor id="executor" pool-size="10"/>
As with the scheduler shown in the previous section,
the value provided for the id
attribute is used as the prefix for thread names within
the pool. As far as the pool size is concerned, the executor
element supports more
configuration options than the scheduler
element. For one thing, the thread pool for
a ThreadPoolTaskExecutor
is itself more configurable. Rather than only a single size,
an executor’s thread pool can have different values for the core and the max size.
If you provide a single value, the executor has a fixed-size thread pool (the core and
max sizes are the same). However, the executor
element’s pool-size
attribute also
accepts a range in the form of min-max
. The following example sets a minimum value of
5
and a maximum value of 25
:
<task:executor
id="executorWithPoolSizeRange"
pool-size="5-25"
queue-capacity="100"/>
In the preceding configuration, a queue-capacity
value has also been provided.
The configuration of the thread pool should also be considered in light of the
executor’s queue capacity. For the full description of the relationship between pool
size and queue capacity, see the documentation for
ThreadPoolExecutor
.
The main idea is that, when a task is submitted, the executor first tries to use a
free thread if the number of active threads is currently less than the core size.
If the core size has been reached, the task is added to the queue, as long as its
capacity has not yet been reached. Only then, if the queue’s capacity has been
reached, does the executor create a new thread beyond the core size. If the max size
has also been reached, then the executor rejects the task.
By default, the queue is unbounded, but this is rarely the desired configuration,
because it can lead to OutOfMemoryErrors
if enough tasks are added to that queue while
all pool threads are busy. Furthermore, if the queue is unbounded, the max size has
no effect at all. Since the executor always tries the queue before creating a new
thread beyond the core size, a queue must have a finite capacity for the thread pool to
grow beyond the core size (this is why a fixed-size pool is the only sensible case
when using an unbounded queue).
Consider the case, as mentioned above, when a task is rejected. By default, when a
task is rejected, a thread pool executor throws a TaskRejectedException
. However,
the rejection policy is actually configurable. The exception is thrown when using
the default rejection policy, which is the AbortPolicy
implementation.
For applications where some tasks can be skipped under heavy load, you can instead
configure either DiscardPolicy
or DiscardOldestPolicy
. Another option that works
well for applications that need to throttle the submitted tasks under heavy load is
the CallerRunsPolicy
. Instead of throwing an exception or discarding tasks,
that policy forces the thread that is calling the submit method to run the task itself.
The idea is that such a caller is busy while running that task and not able to submit
other tasks immediately. Therefore, it provides a simple way to throttle the incoming
load while maintaining the limits of the thread pool and queue. Typically, this allows
the executor to “catch up” on the tasks it is handling and thereby frees up some
capacity on the queue, in the pool, or both. You can choose any of these options from an
enumeration of values available for the rejection-policy
attribute on the executor
element.
The following example shows an executor
element with a number of attributes to specify
various behaviors:
<task:executor
id="executorWithCallerRunsPolicy"
pool-size="5-25"
queue-capacity="100"
rejection-policy="CALLER_RUNS"/>
Finally, the keep-alive
setting determines the time limit (in seconds) for which threads
may remain idle before being stopped. If there are more than the core number of threads
currently in the pool, after waiting this amount of time without processing a task, excess
threads get stopped. A time value of zero causes excess threads to stop
immediately after executing a task without remaining follow-up work in the task queue.
The following example sets the keep-alive
value to two minutes:
<task:executor
id="executorWithKeepAlive"
pool-size="5-25"
keep-alive="120"/>
5.4.3. The 'scheduled-tasks' Element
The most powerful feature of Spring’s task namespace is the support for configuring
tasks to be scheduled within a Spring Application Context. This follows an approach
similar to other “method-invokers” in Spring, such as that provided by the JMS namespace
for configuring message-driven POJOs. Basically, a ref
attribute can point to any
Spring-managed object, and the method
attribute provides the name of a method to be
invoked on that object. The following listing shows a simple example:
<task:scheduled-tasks scheduler="myScheduler">
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/>
</task:scheduled-tasks>
<task:scheduler id="myScheduler" pool-size="10"/>
The scheduler is referenced by the outer element, and each individual
task includes the configuration of its trigger metadata. In the preceding example, that
metadata defines a periodic trigger with a fixed delay indicating the number of
milliseconds to wait after each task execution has completed. Another option is
fixed-rate
, indicating how often the method should be run regardless of how long
any previous execution takes. Additionally, for both fixed-delay
and fixed-rate
tasks, you can specify an
'initial-delay' parameter, indicating the number of milliseconds to wait
before the first execution of the method. For more control, you can instead provide a cron
attribute
to provide a cron expression.
The following example shows these other options:
<task:scheduled-tasks scheduler="myScheduler">
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/>
<task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/>
<task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/>
</task:scheduled-tasks>
<task:scheduler id="myScheduler" pool-size="10"/>
5.5. Cron Expressions
All Spring cron expressions have to conform to the same format, whether you are using them in
@Scheduled
annotations,
task:scheduled-tasks
elements,
or someplace else.
A well-formed cron expression, such as * * * * * *
, consists of six space-separated time and date
fields, each with its own range of valid values:
┌───────────── second (0-59) │ ┌───────────── minute (0 - 59) │ │ ┌───────────── hour (0 - 23) │ │ │ ┌───────────── day of the month (1 - 31) │ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC) │ │ │ │ │ ┌───────────── day of the week (0 - 7) │ │ │ │ │ │ (0 or 7 is Sunday, or MON-SUN) │ │ │ │ │ │ * * * * * *
There are some rules that apply:
-
A field may be an asterisk (
*
), which always stands for “first-last”. For the day-of-the-month or day-of-the-week fields, a question mark (?
) may be used instead of an asterisk. -
Commas (
,
) are used to separate items of a list. -
Two numbers separated with a hyphen (
-
) express a range of numbers. The specified range is inclusive. -
Following a range (or
*
) with/
specifies the interval of the number’s value through the range. -
English names can also be used for the day-of-month and day-of-week fields. Use the first three letters of the particular day or month (case does not matter).
-
The day-of-month and day-of-week fields can contain a
L
character, which has a different meaning-
In the day-of-month field,
L
stands for the last day of the month. If followed by a negative offset (that is,L-n
), it meansn
th-to-last day of the month. -
In the day-of-week field,
L
stands for the last day of the week. If prefixed by a number or three-letter name (dL
orDDDL
), it means the last day of week (d
orDDD
) in the month.
-
-
The day-of-month field can be
nW
, which stands for the nearest weekday to day of the monthn
. Ifn
falls on Saturday, this yields the Friday before it. Ifn
falls on Sunday, this yields the Monday after, which also happens ifn
is1
and falls on a Saturday (that is:1W
stands for the first weekday of the month). -
If the day-of-month field is
LW
, it means the last weekday of the month. -
The day-of-week field can be
d#n
(orDDD#n
), which stands for then
th day of weekd
(orDDD
) in the month.
Here are some examples:
Cron Expression | Meaning |
---|---|
|
top of every hour of every day |
|
every ten seconds |
|
8, 9 and 10 o’clock of every day |
|
6:00 AM and 7:00 PM every day |
|
8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day |
|
on the hour nine-to-five weekdays |
|
every Christmas Day at midnight |
|
last day of the month at midnight |
|
third-to-last day of the month at midnight |
|
last Friday of the month at midnight |
|
last Thursday of the month at midnight |
|
first weekday of the month at midnight |
|
last weekday of the month at midnight |
|
the second Friday in the month at midnight |
|
the first Monday in the month at midnight |
5.5.1. Macros
Expressions such as 0 0 * * * *
are hard for humans to parse and are, therefore, hard to fix in case of bugs.
To improve readability, Spring supports the following macros, which represent commonly used sequences.
You can use these macros instead of the six-digit value, thus: @Scheduled(cron = "@hourly")
.
Macro | Meaning |
---|---|
|
once a year ( |
|
once a month ( |
|
once a week ( |
|
once a day ( |
|
once an hour, ( |
5.6. Using the Quartz Scheduler
Quartz uses Trigger
, Job
, and JobDetail
objects to realize scheduling of all kinds
of jobs. For the basic concepts behind Quartz, see
https://www.quartz-scheduler.org/. For convenience purposes, Spring offers a couple of
classes that simplify using Quartz within Spring-based applications.
5.6.1. Using the JobDetailFactoryBean
Quartz JobDetail
objects contain all the information needed to run a job. Spring provides a
JobDetailFactoryBean
, which provides bean-style properties for XML configuration purposes.
Consider the following example:
<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="example.ExampleJob"/>
<property name="jobDataAsMap">
<map>
<entry key="timeout" value="5"/>
</map>
</property>
</bean>
The job detail configuration has all the information it needs to run the job (ExampleJob
).
The timeout is specified in the job data map. The job data map is available through the
JobExecutionContext
(passed to you at execution time), but the JobDetail
also gets
its properties from the job data mapped to properties of the job instance. So, in the following example,
the ExampleJob
contains a bean property named timeout
, and the JobDetail
has it applied automatically:
package example;
public class ExampleJob extends QuartzJobBean {
private int timeout;
/**
* Setter called after the ExampleJob is instantiated
* with the value from the JobDetailFactoryBean (5)
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
}
protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
// do the actual work
}
}
All additional properties from the job data map are available to you as well.
By using the name and group properties, you can modify the name and the group
of the job, respectively. By default, the name of the job matches the bean name
of the JobDetailFactoryBean (exampleJob in the preceding example above).
|
5.6.2. Using the MethodInvokingJobDetailFactoryBean
Often you merely need to invoke a method on a specific object. By using the
MethodInvokingJobDetailFactoryBean
, you can do exactly this, as the following example shows:
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="exampleBusinessObject"/>
<property name="targetMethod" value="doIt"/>
</bean>
The preceding example results in the doIt
method being called on the
exampleBusinessObject
method, as the following example shows:
public class ExampleBusinessObject {
// properties and collaborators
public void doIt() {
// do the actual work
}
}
<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>
By using the MethodInvokingJobDetailFactoryBean
, you need not create one-line jobs
that merely invoke a method. You need only create the actual business object and
wire up the detail object.
By default, Quartz Jobs are stateless, resulting in the possibility of jobs interfering
with each other. If you specify two triggers for the same JobDetail
, it is
possible that, before the first job has finished, the second one starts. If
JobDetail
classes implement the Stateful
interface, this does not happen. The second
job does not start before the first one has finished. To make jobs resulting from the
MethodInvokingJobDetailFactoryBean
be non-concurrent, set the concurrent
flag to
false
, as the following example shows:
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="exampleBusinessObject"/>
<property name="targetMethod" value="doIt"/>
<property name="concurrent" value="false"/>
</bean>
By default, jobs will run in a concurrent fashion. |
5.6.3. Wiring up Jobs by Using Triggers and SchedulerFactoryBean
We have created job details and jobs. We have also reviewed the convenience bean that lets
you invoke a method on a specific object. Of course, we still need to schedule the
jobs themselves. This is done by using triggers and a SchedulerFactoryBean
. Several
triggers are available within Quartz, and Spring offers two Quartz FactoryBean
implementations with convenient defaults: CronTriggerFactoryBean
and
SimpleTriggerFactoryBean
.
Triggers need to be scheduled. Spring offers a SchedulerFactoryBean
that exposes
triggers to be set as properties. SchedulerFactoryBean
schedules the actual jobs with
those triggers.
The following listing uses both a SimpleTriggerFactoryBean
and a CronTriggerFactoryBean
:
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<!-- see the example of method invoking job above -->
<property name="jobDetail" ref="jobDetail"/>
<!-- 10 seconds -->
<property name="startDelay" value="10000"/>
<!-- repeat every 50 seconds -->
<property name="repeatInterval" value="50000"/>
</bean>
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="exampleJob"/>
<!-- run every morning at 6 AM -->
<property name="cronExpression" value="0 0 6 * * ?"/>
</bean>
The preceding example sets up two triggers, one running every 50 seconds with a starting delay of 10
seconds and one running every morning at 6 AM. To finalize everything, we need to set up the
SchedulerFactoryBean
, as the following example shows:
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger"/>
<ref bean="simpleTrigger"/>
</list>
</property>
</bean>
More properties are available for the SchedulerFactoryBean
, such as the calendars used by the
job details, properties to customize Quartz with, and a Spring-provided JDBC DataSource. See
the SchedulerFactoryBean
javadoc for more information.
SchedulerFactoryBean also recognizes a quartz.properties file in the classpath,
based on Quartz property keys, as with regular Quartz configuration. Please note that many
SchedulerFactoryBean settings interact with common Quartz settings in the properties file;
it is therefore not recommended to specify values at both levels. For example, do not set
an "org.quartz.jobStore.class" property if you mean to rely on a Spring-provided DataSource,
or specify an org.springframework.scheduling.quartz.LocalDataSourceJobStore variant which
is a full-fledged replacement for the standard org.quartz.impl.jdbcjobstore.JobStoreTX .
|
6. Cache Abstraction
Since version 3.1, the Spring Framework provides support for transparently adding caching to an existing Spring application. Similar to the transaction support, the caching abstraction allows consistent use of various caching solutions with minimal impact on the code.
In Spring Framework 4.1, the cache abstraction was significantly extended with support for JSR-107 annotations and more customization options.
6.1. Understanding the Cache Abstraction
At its core, the cache abstraction applies caching to Java methods, thus reducing the number of executions based on the information available in the cache. That is, each time a targeted method is invoked, the abstraction applies a caching behavior that checks whether the method has been already invoked for the given arguments. If it has been invoked, the cached result is returned without having to invoke the actual method. If the method has not been invoked, then it is invoked, and the result is cached and returned to the user so that, the next time the method is invoked, the cached result is returned. This way, expensive methods (whether CPU- or IO-bound) can be invoked only once for a given set of parameters and the result reused without having to actually invoke the method again. The caching logic is applied transparently without any interference to the invoker.
This approach works only for methods that are guaranteed to return the same output (result) for a given input (or arguments) no matter how many times they are invoked. |
The caching abstraction provides other cache-related operations, such as the ability to update the content of the cache or to remove one or all entries. These are useful if the cache deals with data that can change during the course of the application.
As with other services in the Spring Framework, the caching service is an abstraction
(not a cache implementation) and requires the use of actual storage to store the cache data — that is, the abstraction frees you from having to write the caching logic but does not
provide the actual data store. This abstraction is materialized by the
org.springframework.cache.Cache
and org.springframework.cache.CacheManager
interfaces.
Spring provides a few implementations of that abstraction:
JDK java.util.concurrent.ConcurrentMap
based caches, Gemfire cache,
Caffeine, and JSR-107 compliant caches (such
as Ehcache 3.x). See Plugging-in Different Back-end Caches for more information on plugging in other cache
stores and providers.
The caching abstraction has no special handling for multi-threaded and multi-process environments, as such features are handled by the cache implementation. |
If you have a multi-process environment (that is, an application deployed on several nodes), you need to configure your cache provider accordingly. Depending on your use cases, a copy of the same data on several nodes can be enough. However, if you change the data during the course of the application, you may need to enable other propagation mechanisms.
Caching a particular item is a direct equivalent of the typical get-if-not-found-then-proceed-and-put-eventually code blocks found with programmatic cache interaction. No locks are applied, and several threads may try to load the same item concurrently. The same applies to eviction. If several threads are trying to update or evict data concurrently, you may use stale data. Certain cache providers offer advanced features in that area. See the documentation of your cache provider for more details.
To use the cache abstraction, you need to take care of two aspects:
-
Caching declaration: Identify the methods that need to be cached and their policies.
-
Cache configuration: The backing cache where the data is stored and from which it is read.
6.2. Declarative Annotation-based Caching
For caching declaration, Spring’s caching abstraction provides a set of Java annotations:
-
@Cacheable
: Triggers cache population. -
@CacheEvict
: Triggers cache eviction. -
@CachePut
: Updates the cache without interfering with the method execution. -
@Caching
: Regroups multiple cache operations to be applied on a method. -
@CacheConfig
: Shares some common cache-related settings at class-level.
6.2.1. The @Cacheable
Annotation
As the name implies, you can use @Cacheable
to demarcate methods that are cacheable — that is, methods for which the result is stored in the cache so that, on subsequent
invocations (with the same arguments), the value in the cache is returned without
having to actually invoke the method. In its simplest form, the annotation declaration
requires the name of the cache associated with the annotated method, as the following
example shows:
@Cacheable("books")
public Book findBook(ISBN isbn) {...}
In the preceding snippet, the findBook
method is associated with the cache named books
.
Each time the method is called, the cache is checked to see whether the invocation has
already been run and does not have to be repeated. While in most cases, only one
cache is declared, the annotation lets multiple names be specified so that more than one
cache is being used. In this case, each of the caches is checked before invoking the
method — if at least one cache is hit, the associated value is returned.
All the other caches that do not contain the value are also updated, even though the cached method was not actually invoked. |
The following example uses @Cacheable
on the findBook
method with multiple caches:
@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}
Default Key Generation
Since caches are essentially key-value stores, each invocation of a cached method
needs to be translated into a suitable key for cache access. The caching abstraction
uses a simple KeyGenerator
based on the following algorithm:
-
If no params are given, return
SimpleKey.EMPTY
. -
If only one param is given, return that instance.
-
If more than one param is given, return a
SimpleKey
that contains all parameters.
This approach works well for most use-cases, as long as parameters have natural keys
and implement valid hashCode()
and equals()
methods. If that is not the case,
you need to change the strategy.
To provide a different default key generator, you need to implement the
org.springframework.cache.interceptor.KeyGenerator
interface.
The default key generation strategy changed with the release of Spring 4.0. Earlier
versions of Spring used a key generation strategy that, for multiple key parameters,
considered only the If you want to keep using the previous key strategy, you can configure the deprecated
|
Custom Key Generation Declaration
Since caching is generic, the target methods are quite likely to have various signatures that cannot be readily mapped on top of the cache structure. This tends to become obvious when the target method has multiple arguments out of which only some are suitable for caching (while the rest are used only by the method logic). Consider the following example:
@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
At first glance, while the two boolean
arguments influence the way the book is found,
they are no use for the cache. Furthermore, what if only one of the two is important
while the other is not?
For such cases, the @Cacheable
annotation lets you specify how the key is generated
through its key
attribute. You can use SpEL to pick the
arguments of interest (or their nested properties), perform operations, or even
invoke arbitrary methods without having to write any code or implement any interface.
This is the recommended approach over the
default generator, since methods tend to be
quite different in signatures as the code base grows. While the default strategy might
work for some methods, it rarely works for all methods.
The following examples use various SpEL declarations (if you are not familiar with SpEL, do yourself a favor and read Spring Expression Language):
@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
The preceding snippets show how easy it is to select a certain argument, one of its properties, or even an arbitrary (static) method.
If the algorithm responsible for generating the key is too specific or if it needs
to be shared, you can define a custom keyGenerator
on the operation. To do so,
specify the name of the KeyGenerator
bean implementation to use, as the following
example shows:
@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
The key and keyGenerator parameters are mutually exclusive and an operation
that specifies both results in an exception.
|
Default Cache Resolution
The caching abstraction uses a simple CacheResolver
that
retrieves the caches defined at the operation level by using the configured
CacheManager
.
To provide a different default cache resolver, you need to implement the
org.springframework.cache.interceptor.CacheResolver
interface.
Custom Cache Resolution
The default cache resolution fits well for applications that work with a
single CacheManager
and have no complex cache resolution requirements.
For applications that work with several cache managers, you can set the
cacheManager
to use for each operation, as the following example shows:
@Cacheable(cacheNames="books", cacheManager="anotherCacheManager") (1)
public Book findBook(ISBN isbn) {...}
1 | Specifying anotherCacheManager . |
You can also replace the CacheResolver
entirely in a fashion similar to that of
replacing key generation. The resolution is
requested for every cache operation, letting the implementation actually resolve
the caches to use based on runtime arguments. The following example shows how to
specify a CacheResolver
:
@Cacheable(cacheResolver="runtimeCacheResolver") (1)
public Book findBook(ISBN isbn) {...}
1 | Specifying the CacheResolver . |
Since Spring 4.1, the Similarly to |
Synchronized Caching
In a multi-threaded environment, certain operations might be concurrently invoked for the same argument (typically on startup). By default, the cache abstraction does not lock anything, and the same value may be computed several times, defeating the purpose of caching.
For those particular cases, you can use the sync
attribute to instruct the underlying
cache provider to lock the cache entry while the value is being computed. As a result,
only one thread is busy computing the value, while the others are blocked until the entry
is updated in the cache. The following example shows how to use the sync
attribute:
@Cacheable(cacheNames="foos", sync=true) (1)
public Foo executeExpensiveOperation(String id) {...}
1 | Using the sync attribute. |
This is an optional feature, and your favorite cache library may not support it.
All CacheManager implementations provided by the core framework support it. See the
documentation of your cache provider for more details.
|
Conditional Caching
Sometimes, a method might not be suitable for caching all the time (for example, it might
depend on the given arguments). The cache annotations support such use cases through the
condition
parameter, which takes a SpEL
expression that is evaluated to either true
or false
. If true
, the method is cached. If not, it behaves as if the method is not
cached (that is, the method is invoked every time no matter what values are in the cache
or what arguments are used). For example, the following method is cached only if the
argument name
has a length shorter than 32:
@Cacheable(cacheNames="book", condition="#name.length() < 32") (1)
public Book findBook(String name)
1 | Setting a condition on @Cacheable . |
In addition to the condition
parameter, you can use the unless
parameter to veto the
adding of a value to the cache. Unlike condition
, unless
expressions are evaluated
after the method has been invoked. To expand on the previous example, perhaps we only
want to cache paperback books, as the following example does:
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback") (1)
public Book findBook(String name)
1 | Using the unless attribute to block hardbacks. |
The cache abstraction supports java.util.Optional
return types. If an Optional
value
is present, it will be stored in the associated cache. If an Optional
value is not
present, null
will be stored in the associated cache. #result
always refers to the
business entity and never a supported wrapper, so the previous example can be rewritten
as follows:
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback")
public Optional<Book> findBook(String name)
Note that #result
still refers to Book
and not Optional<Book>
. Since it might be
null
, we use SpEL’s safe navigation operator.
Available Caching SpEL Evaluation Context
Each SpEL
expression evaluates against a dedicated context
.
In addition to the built-in parameters, the framework provides dedicated caching-related
metadata, such as the argument names. The following table describes the items made
available to the context so that you can use them for key and conditional computations:
Name | Location | Description | Example |
---|---|---|---|
|
Root object |
The name of the method being invoked |
|
|
Root object |
The method being invoked |
|
|
Root object |
The target object being invoked |
|
|
Root object |
The class of the target being invoked |
|
|
Root object |
The arguments (as array) used for invoking the target |
|
|
Root object |
Collection of caches against which the current method is run |
|
Argument name |
Evaluation context |
Name of any of the method arguments. If the names are not available
(perhaps due to having no debug information), the argument names are also available under the |
|
|
Evaluation context |
The result of the method call (the value to be cached). Only available in |
|
6.2.2. The @CachePut
Annotation
When the cache needs to be updated without interfering with the method execution,
you can use the @CachePut
annotation. That is, the method is always invoked and its
result is placed into the cache (according to the @CachePut
options). It supports
the same options as @Cacheable
and should be used for cache population rather than
method flow optimization. The following example uses the @CachePut
annotation:
@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
Using @CachePut and @Cacheable annotations on the same method is generally
strongly discouraged because they have different behaviors. While the latter causes the
method invocation to be skipped by using the cache, the former forces the invocation in
order to run a cache update. This leads to unexpected behavior and, with the exception
of specific corner-cases (such as annotations having conditions that exclude them from each
other), such declarations should be avoided. Note also that such conditions should not rely
on the result object (that is, the #result variable), as these are validated up-front to
confirm the exclusion.
|
6.2.3. The @CacheEvict
annotation
The cache abstraction allows not just population of a cache store but also eviction.
This process is useful for removing stale or unused data from the cache. As opposed to
@Cacheable
, @CacheEvict
demarcates methods that perform cache
eviction (that is, methods that act as triggers for removing data from the cache).
Similarly to its sibling, @CacheEvict
requires specifying one or more caches
that are affected by the action, allows a custom cache and key resolution or a
condition to be specified, and features an extra parameter
(allEntries
) that indicates whether a cache-wide eviction needs to be performed
rather than just an entry eviction (based on the key). The following example evicts
all entries from the books
cache:
@CacheEvict(cacheNames="books", allEntries=true) (1)
public void loadBooks(InputStream batch)
1 | Using the allEntries attribute to evict all entries from the cache. |
This option comes in handy when an entire cache region needs to be cleared out. Rather than evicting each entry (which would take a long time, since it is inefficient), all the entries are removed in one operation, as the preceding example shows. Note that the framework ignores any key specified in this scenario as it does not apply (the entire cache is evicted, not only one entry).
You can also indicate whether the eviction should occur after (the default) or before
the method is invoked by using the beforeInvocation
attribute. The former provides the
same semantics as the rest of the annotations: Once the method completes successfully,
an action (in this case, eviction) on the cache is run. If the method does not
run (as it might be cached) or an exception is thrown, the eviction does not occur.
The latter (beforeInvocation=true
) causes the eviction to always occur before the
method is invoked. This is useful in cases where the eviction does not need to be tied
to the method outcome.
Note that void
methods can be used with @CacheEvict
- as the methods act as a
trigger, the return values are ignored (as they do not interact with the cache). This is
not the case with @Cacheable
which adds data to the cache or updates data in the cache
and, thus, requires a result.
6.2.4. The @Caching
Annotation
Sometimes, multiple annotations of the same type (such as @CacheEvict
or
@CachePut
) need to be specified — for example, because the condition or the key
expression is different between different caches. @Caching
lets multiple nested
@Cacheable
, @CachePut
, and @CacheEvict
annotations be used on the same method.
The following example uses two @CacheEvict
annotations:
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)
6.2.5. The @CacheConfig
annotation
So far, we have seen that caching operations offer many customization options and that
you can set these options for each operation. However, some of the customization options
can be tedious to configure if they apply to all operations of the class. For
instance, specifying the name of the cache to use for every cache operation of the
class can be replaced by a single class-level definition. This is where @CacheConfig
comes into play. The following examples uses @CacheConfig
to set the name of the cache:
@CacheConfig("books") (1)
public class BookRepositoryImpl implements BookRepository {
@Cacheable
public Book findBook(ISBN isbn) {...}
}
1 | Using @CacheConfig to set the name of the cache. |
@CacheConfig
is a class-level annotation that allows sharing the cache names,
the custom KeyGenerator
, the custom CacheManager
, and the custom CacheResolver
.
Placing this annotation on the class does not turn on any caching operation.
An operation-level customization always overrides a customization set on @CacheConfig
.
Therefore, this gives three levels of customizations for each cache operation:
-
Globally configured, available for
CacheManager
,KeyGenerator
. -
At the class level, using
@CacheConfig
. -
At the operation level.
6.2.6. Enabling Caching Annotations
It is important to note that even though declaring the cache annotations does not automatically trigger their actions - like many things in Spring, the feature has to be declaratively enabled (which means if you ever suspect caching is to blame, you can disable it by removing only one configuration line rather than all the annotations in your code).
To enable caching annotations add the annotation @EnableCaching
to one of your
@Configuration
classes:
@Configuration
@EnableCaching
public class AppConfig {
}
Alternatively, for XML configuration you can use the cache:annotation-driven
element:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache.xsd">
<cache:annotation-driven/>
</beans>
Both the cache:annotation-driven
element and the @EnableCaching
annotation let you
specify various options that influence the way the caching behavior is added to the
application through AOP. The configuration is intentionally similar with that of
@Transactional
.
The default advice mode for processing caching annotations is proxy , which allows
for interception of calls through the proxy only. Local calls within the same class
cannot get intercepted that way. For a more advanced mode of interception, consider
switching to aspectj mode in combination with compile-time or load-time weaving.
|
For more detail about advanced customizations (using Java configuration) that are
required to implement CachingConfigurer , see the
javadoc.
|
XML Attribute | Annotation Attribute | Default | Description |
---|---|---|---|
|
N/A (see the |
|
The name of the cache manager to use. A default |
|
N/A (see the |
A |
The bean name of the CacheResolver that is to be used to resolve the backing caches. This attribute is not required and needs to be specified only as an alternative to the 'cache-manager' attribute. |
|
N/A (see the |
|
Name of the custom key generator to use. |
|
N/A (see the |
|
The name of the custom cache error handler to use. By default, any exception thrown during a cache related operation is thrown back at the client. |
|
|
|
The default mode ( |
|
|
|
Applies to proxy mode only. Controls what type of caching proxies are created for
classes annotated with the |
|
|
Ordered.LOWEST_PRECEDENCE |
Defines the order of the cache advice that is applied to beans annotated with
|
<cache:annotation-driven/> looks for @Cacheable/@CachePut/@CacheEvict/@Caching
only on beans in the same application context in which it is defined. This means that,
if you put <cache:annotation-driven/> in a WebApplicationContext for a
DispatcherServlet , it checks for beans only in your controllers, not your services.
See the MVC section for more information.
|
Spring recommends that you only annotate concrete classes (and methods of concrete
classes) with the @Cache* annotations, as opposed to annotating interfaces.
You certainly can place an @Cache* annotation on an interface (or an interface
method), but this works only if you use the proxy mode (mode="proxy" ). If you use the
weaving-based aspect (mode="aspectj" ), the caching settings are not recognized on
interface-level declarations by the weaving infrastructure.
|
In proxy mode (the default), only external method calls coming in through the
proxy are intercepted. This means that self-invocation (in effect, a method within the
target object that calls another method of the target object) does not lead to actual
caching at runtime even if the invoked method is marked with @Cacheable . Consider
using the aspectj mode in this case. Also, the proxy must be fully initialized to
provide the expected behavior, so you should not rely on this feature in your
initialization code (that is, @PostConstruct ).
|
6.2.7. Using Custom Annotations
The caching abstraction lets you use your own annotations to identify what method
triggers cache population or eviction. This is quite handy as a template mechanism,
as it eliminates the need to duplicate cache annotation declarations, which is
especially useful if the key or condition are specified or if the foreign imports
(org.springframework
) are not allowed in your code base. Similarly to the rest
of the stereotype annotations, you can
use @Cacheable
, @CachePut
, @CacheEvict
, and @CacheConfig
as
meta-annotations (that is, annotations that
can annotate other annotations). In the following example, we replace a common
@Cacheable
declaration with our own custom annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Cacheable(cacheNames="books", key="#isbn")
public @interface SlowService {
}
In the preceding example, we have defined our own SlowService
annotation,
which itself is annotated with @Cacheable
. Now we can replace the following code:
@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
The following example shows the custom annotation with which we can replace the preceding code:
@SlowService
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
Even though @SlowService
is not a Spring annotation, the container automatically picks
up its declaration at runtime and understands its meaning. Note that, as mentioned
earlier, annotation-driven behavior needs to be enabled.
6.3. JCache (JSR-107) Annotations
Since version 4.1, Spring’s caching abstraction fully supports the JCache standard
(JSR-107) annotations: @CacheResult
, @CachePut
, @CacheRemove
, and @CacheRemoveAll
as well as the @CacheDefaults
, @CacheKey
, and @CacheValue
companions.
You can use these annotations even without migrating your cache store to JSR-107.
The internal implementation uses Spring’s caching abstraction and provides default
CacheResolver
and KeyGenerator
implementations that are compliant with the
specification. In other words, if you are already using Spring’s caching abstraction,
you can switch to these standard annotations without changing your cache storage
(or configuration, for that matter).
6.3.1. Feature Summary
For those who are familiar with Spring’s caching annotations, the following table describes the main differences between the Spring annotations and their JSR-107 counterparts:
Spring | JSR-107 | Remark |
---|---|---|
|
|
Fairly similar. |
|
|
While Spring updates the cache with the result of the method invocation, JCache
requires that it be passed it as an argument that is annotated with |
|
|
Fairly similar. |
|
|
See |
|
|
Lets you configure the same concepts, in a similar fashion. |
JCache has the notion of javax.cache.annotation.CacheResolver
, which is identical
to the Spring’s CacheResolver
interface, except that JCache supports only a single
cache. By default, a simple implementation retrieves the cache to use based on the
name declared on the annotation. It should be noted that, if no cache name is
specified on the annotation, a default is automatically generated. See the javadoc
of @CacheResult#cacheName()
for more information.
CacheResolver
instances are retrieved by a CacheResolverFactory
. It is possible
to customize the factory for each cache operation, as the following example shows:
@CacheResult(cacheNames="books", cacheResolverFactory=MyCacheResolverFactory.class) (1)
public Book findBook(ISBN isbn)
1 | Customizing the factory for this operation. |
For all referenced classes, Spring tries to locate a bean with the given type. If more than one match exists, a new instance is created and can use the regular bean lifecycle callbacks, such as dependency injection. |
Keys are generated by a javax.cache.annotation.CacheKeyGenerator
that serves the
same purpose as Spring’s KeyGenerator
. By default, all method arguments are taken
into account, unless at least one parameter is annotated with @CacheKey
. This is
similar to Spring’s custom key generation
declaration. For instance, the following are identical operations, one using
Spring’s abstraction and the other using JCache:
@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@CacheResult(cacheName="books")
public Book findBook(@CacheKey ISBN isbn, boolean checkWarehouse, boolean includeUsed)
You can also specify the CacheKeyResolver
on the operation, similar to how you can
specify the CacheResolverFactory
.
JCache can manage exceptions thrown by annotated methods. This can prevent an update of
the cache, but it can also cache the exception as an indicator of the failure instead of
calling the method again. Assume that InvalidIsbnNotFoundException
is thrown if the
structure of the ISBN is invalid. This is a permanent failure (no book could ever be
retrieved with such a parameter). The following caches the exception so that further
calls with the same, invalid, ISBN throw the cached exception directly instead of
invoking the method again:
@CacheResult(cacheName="books", exceptionCacheName="failures"
cachedExceptions = InvalidIsbnNotFoundException.class)
public Book findBook(ISBN isbn)
6.3.2. Enabling JSR-107 Support
You do not need to do anything specific to enable the JSR-107 support alongside Spring’s
declarative annotation support. Both @EnableCaching
and the cache:annotation-driven
XML element automatically enable the JCache support if both the JSR-107 API and the
spring-context-support
module are present in the classpath.
Depending on your use case, the choice is basically yours. You can even mix and match services by using the JSR-107 API on some and using Spring’s own annotations on others. However, if these services impact the same caches, you should use a consistent and identical key generation implementation. |
6.4. Declarative XML-based Caching
If annotations are not an option (perhaps due to having no access to the sources or no external code), you can use XML for declarative caching. So, instead of annotating the methods for caching, you can specify the target method and the caching directives externally (similar to the declarative transaction management advice). The example from the previous section can be translated into the following example:
<!-- the service we want to make cacheable -->
<bean id="bookService" class="x.y.service.DefaultBookService"/>
<!-- cache definitions -->
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
<cache:caching cache="books">
<cache:cacheable method="findBook" key="#isbn"/>
<cache:cache-evict method="loadBooks" all-entries="true"/>
</cache:caching>
</cache:advice>
<!-- apply the cacheable behavior to all BookService interfaces -->
<aop:config>
<aop:advisor advice-ref="cacheAdvice" pointcut="execution(* x.y.BookService.*(..))"/>
</aop:config>
<!-- cache manager definition omitted -->
In the preceding configuration, the bookService
is made cacheable. The caching semantics
to apply are encapsulated in the cache:advice
definition, which causes the findBooks
method to be used for putting data into the cache and the loadBooks
method for evicting
data. Both definitions work against the books
cache.
The aop:config
definition applies the cache advice to the appropriate points in the
program by using the AspectJ pointcut expression (more information is available in
Aspect Oriented Programming with Spring). In the preceding example,
all methods from the BookService
are considered and the cache advice is applied to them.
The declarative XML caching supports all of the annotation-based model, so moving between
the two should be fairly easy. Furthermore, both can be used inside the same application.
The XML-based approach does not touch the target code. However, it is inherently more
verbose. When dealing with classes that have overloaded methods that are targeted for
caching, identifying the proper methods does take an extra effort, since the method
argument is not a good discriminator. In these cases, you can use the AspectJ pointcut
to cherry pick the target methods and apply the appropriate caching functionality.
However, through XML, it is easier to apply package or group or interface-wide caching
(again, due to the AspectJ pointcut) and to create template-like definitions (as we did
in the preceding example by defining the target cache through the cache:definitions
cache
attribute).
6.5. Configuring the Cache Storage
The cache abstraction provides several storage integration options. To use them, you need
to declare an appropriate CacheManager
(an entity that controls and manages Cache
instances and that can be used to retrieve these for storage).
6.5.1. JDK ConcurrentMap
-based Cache
The JDK-based Cache
implementation resides under
org.springframework.cache.concurrent
package. It lets you use ConcurrentHashMap
as a backing Cache
store. The following example shows how to configure two caches:
<!-- simple cache manager -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="books"/>
</set>
</property>
</bean>
The preceding snippet uses the SimpleCacheManager
to create a CacheManager
for the
two nested ConcurrentMapCache
instances named default
and books
. Note that the
names are configured directly for each cache.
As the cache is created by the application, it is bound to its lifecycle, making it suitable for basic use cases, tests, or simple applications. The cache scales well and is very fast, but it does not provide any management, persistence capabilities, or eviction contracts.
6.5.2. Ehcache-based Cache
Ehcache 3.x is fully JSR-107 compliant and no dedicated support is required for it. See JSR-107 Cache for details.
6.5.3. Caffeine Cache
Caffeine is a Java 8 rewrite of Guava’s cache, and its implementation is located in the
org.springframework.cache.caffeine
package and provides access to several features
of Caffeine.
The following example configures a CacheManager
that creates the cache on demand:
<bean id="cacheManager"
class="org.springframework.cache.caffeine.CaffeineCacheManager"/>
You can also provide the caches to use explicitly. In that case, only those are made available by the manager. The following example shows how to do so:
<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
<property name="cacheNames">
<set>
<value>default</value>
<value>books</value>
</set>
</property>
</bean>
The Caffeine CacheManager
also supports custom Caffeine
and CacheLoader
.
See the Caffeine documentation
for more information about those.
6.5.4. GemFire-based Cache
GemFire is a memory-oriented, disk-backed, elastically scalable, continuously available,
active (with built-in pattern-based subscription notifications), globally replicated
database and provides fully-featured edge caching. For further information on how to
use GemFire as a CacheManager
(and more), see the
Spring Data GemFire reference documentation.
6.5.5. JSR-107 Cache
Spring’s caching abstraction can also use JSR-107-compliant caches. The JCache
implementation is located in the org.springframework.cache.jcache
package.
Again, to use it, you need to declare the appropriate CacheManager
.
The following example shows how to do so:
<bean id="cacheManager"
class="org.springframework.cache.jcache.JCacheCacheManager"
p:cache-manager-ref="jCacheManager"/>
<!-- JSR-107 cache manager setup -->
<bean id="jCacheManager" .../>
6.5.6. Dealing with Caches without a Backing Store
Sometimes, when switching environments or doing testing, you might have cache declarations without having an actual backing cache configured. As this is an invalid configuration, an exception is thrown at runtime, since the caching infrastructure is unable to find a suitable store. In situations like this, rather than removing the cache declarations (which can prove tedious), you can wire in a simple dummy cache that performs no caching — that is, it forces the cached methods to be invoked every time. The following example shows how to do so:
<bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
<property name="cacheManagers">
<list>
<ref bean="jdkCache"/>
<ref bean="gemfireCache"/>
</list>
</property>
<property name="fallbackToNoOpCache" value="true"/>
</bean>
The CompositeCacheManager
in the preceding chains multiple CacheManager
instances and,
through the fallbackToNoOpCache
flag, adds a no-op cache for all the definitions not
handled by the configured cache managers. That is, every cache definition not found in
either jdkCache
or gemfireCache
(configured earlier in the example) is handled by
the no-op cache, which does not store any information, causing the target method to be
invoked every time.
6.6. Plugging-in Different Back-end Caches
Clearly, there are plenty of caching products out there that you can use as a backing
store. For those that do not support JSR-107 you need to provide a CacheManager
and a
Cache
implementation. This may sound harder than it is, since, in practice, the classes
tend to be simple adapters that map the
caching abstraction framework on top of the storage API, as the Caffeine classes do.
Most CacheManager
classes can use the classes in the
org.springframework.cache.support
package (such as AbstractCacheManager
which takes
care of the boiler-plate code, leaving only the actual mapping to be completed).
6.7. How can I Set the TTL/TTI/Eviction policy/XXX feature?
Directly through your cache provider. The cache abstraction is an abstraction,
not a cache implementation. The solution you use might support various data
policies and different topologies that other solutions do not support (for example,
the JDK ConcurrentHashMap
— exposing that in the cache abstraction would be useless
because there would no backing support). Such functionality should be controlled
directly through the backing cache (when configuring it) or through its native API.
7. Appendix
7.1. XML Schemas
This part of the appendix lists XML schemas related to integration technologies.
7.1.1. The jee
Schema
The jee
elements deal with issues related to Jakarta EE (Enterprise Edition) configuration,
such as looking up a JNDI object and defining EJB references.
To use the elements in the jee
schema, you need to have the following preamble at the top
of your Spring XML configuration file. The text in the following snippet references the
correct schema so that the elements in the jee
namespace are available to you:
<?xml version="1.0" encoding="UTF-8"?>
<beans 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="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jee https://www.springframework.org/schema/jee/spring-jee.xsd">
<!-- bean definitions here -->
</beans>
<jee:jndi-lookup/> (simple)
The following example shows how to use JNDI to look up a data source without the jee
schema:
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/MyDataSource"/>
</bean>
<bean id="userDao" class="com.foo.JdbcUserDao">
<!-- Spring will do the cast automatically (as usual) -->
<property name="dataSource" ref="dataSource"/>
</bean>
The following example shows how to use JNDI to look up a data source with the jee
schema:
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/MyDataSource"/>
<bean id="userDao" class="com.foo.JdbcUserDao">
<!-- Spring will do the cast automatically (as usual) -->
<property name="dataSource" ref="dataSource"/>
</bean>
<jee:jndi-lookup/>
(with Single JNDI Environment Setting)
The following example shows how to use JNDI to look up an environment variable without
jee
:
<bean id="simple" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/MyDataSource"/>
<property name="jndiEnvironment">
<props>
<prop key="ping">pong</prop>
</props>
</property>
</bean>
The following example shows how to use JNDI to look up an environment variable with jee
:
<jee:jndi-lookup id="simple" jndi-name="jdbc/MyDataSource">
<jee:environment>ping=pong</jee:environment>
</jee:jndi-lookup>
<jee:jndi-lookup/>
(with Multiple JNDI Environment Settings)
The following example shows how to use JNDI to look up multiple environment variables
without jee
:
<bean id="simple" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/MyDataSource"/>
<property name="jndiEnvironment">
<props>
<prop key="sing">song</prop>
<prop key="ping">pong</prop>
</props>
</property>
</bean>
The following example shows how to use JNDI to look up multiple environment variables with
jee
:
<jee:jndi-lookup id="simple" jndi-name="jdbc/MyDataSource">
<!-- newline-separated, key-value pairs for the environment (standard Properties format) -->
<jee:environment>
sing=song
ping=pong
</jee:environment>
</jee:jndi-lookup>
<jee:jndi-lookup/>
(Complex)
The following example shows how to use JNDI to look up a data source and a number of
different properties without jee
:
<bean id="simple" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/MyDataSource"/>
<property name="cache" value="true"/>
<property name="resourceRef" value="true"/>
<property name="lookupOnStartup" value="false"/>
<property name="expectedType" value="com.myapp.DefaultThing"/>
<property name="proxyInterface" value="com.myapp.Thing"/>
</bean>
The following example shows how to use JNDI to look up a data source and a number of
different properties with jee
:
<jee:jndi-lookup id="simple"
jndi-name="jdbc/MyDataSource"
cache="true"
resource-ref="true"
lookup-on-startup="false"
expected-type="com.myapp.DefaultThing"
proxy-interface="com.myapp.Thing"/>
<jee:local-slsb/>
(Simple)
The <jee:local-slsb/>
element configures a reference to a local EJB Stateless Session Bean.
The following example shows how to configures a reference to a local EJB Stateless Session Bean
without jee
:
<bean id="simple"
class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
<property name="jndiName" value="ejb/RentalServiceBean"/>
<property name="businessInterface" value="com.foo.service.RentalService"/>
</bean>
The following example shows how to configures a reference to a local EJB Stateless Session Bean
with jee
:
<jee:local-slsb id="simpleSlsb" jndi-name="ejb/RentalServiceBean"
business-interface="com.foo.service.RentalService"/>
<jee:local-slsb/>
(Complex)
The <jee:local-slsb/>
element configures a reference to a local EJB Stateless Session Bean.
The following example shows how to configures a reference to a local EJB Stateless Session Bean
and a number of properties without jee
:
<bean id="complexLocalEjb"
class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
<property name="jndiName" value="ejb/RentalServiceBean"/>
<property name="businessInterface" value="com.example.service.RentalService"/>
<property name="cacheHome" value="true"/>
<property name="lookupHomeOnStartup" value="true"/>
<property name="resourceRef" value="true"/>
</bean>
The following example shows how to configures a reference to a local EJB Stateless Session Bean
and a number of properties with jee
:
<jee:local-slsb id="complexLocalEjb"
jndi-name="ejb/RentalServiceBean"
business-interface="com.foo.service.RentalService"
cache-home="true"
lookup-home-on-startup="true"
resource-ref="true">
<jee:remote-slsb/>
The <jee:remote-slsb/>
element configures a reference to a remote
EJB Stateless Session Bean.
The following example shows how to configures a reference to a remote EJB Stateless Session Bean
without jee
:
<bean id="complexRemoteEjb"
class="org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean">
<property name="jndiName" value="ejb/MyRemoteBean"/>
<property name="businessInterface" value="com.foo.service.RentalService"/>
<property name="cacheHome" value="true"/>
<property name="lookupHomeOnStartup" value="true"/>
<property name="resourceRef" value="true"/>
<property name="homeInterface" value="com.foo.service.RentalService"/>
<property name="refreshHomeOnConnectFailure" value="true"/>
</bean>
The following example shows how to configures a reference to a remote EJB Stateless Session Bean
with jee
:
<jee:remote-slsb id="complexRemoteEjb"
jndi-name="ejb/MyRemoteBean"
business-interface="com.foo.service.RentalService"
cache-home="true"
lookup-home-on-startup="true"
resource-ref="true"
home-interface="com.foo.service.RentalService"
refresh-home-on-connect-failure="true">
7.1.2. The jms
Schema
The jms
elements deal with configuring JMS-related beans, such as Spring’s
Message Listener Containers. These elements are detailed in the
section of the JMS chapter entitled JMS Namespace Support. See that chapter for full details on this support
and the jms
elements themselves.
In the interest of completeness, to use the elements in the jms
schema, you need to have
the following preamble at the top of your Spring XML configuration file. The text in the
following snippet references the correct schema so that the elements in the jms
namespace
are available to you:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms https://www.springframework.org/schema/jms/spring-jms.xsd">
<!-- bean definitions here -->
</beans>
7.1.3. Using <context:mbean-export/>
This element is detailed in Configuring Annotation-based MBean Export.
7.1.4. The cache
Schema
You can use the cache
elements to enable support for Spring’s @CacheEvict
, @CachePut
,
and @Caching
annotations. It it also supports declarative XML-based caching. See
Enabling Caching Annotations and
Declarative XML-based Caching for details.
To use the elements in the cache
schema, you need to have the following preamble at the
top of your Spring XML configuration file. The text in the following snippet references
the correct schema so that the elements in the cache
namespace are available to you:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache.xsd">
<!-- bean definitions here -->
</beans>