参考文档的这一部分介绍了Spring框架与许多技术的集成。

1. REST Clients

Spring框架为调用REST端点提供了以下选择:

  • WebClient-非阻塞、反应式客户端使用流畅的API。

  • RestTemplate-带模板方法接口的同步客户端。

  • HTTP接口-带有注释的接口,带有生成的动态代理实现。

1.1. WebClient

WebClient是执行HTTP请求的非阻塞、反应式客户端。它是在5.0中引入的,提供了RestTemplate的替代方案,支持同步、异步和流场景。

WebClient支持以下内容:

  • 非阻塞I/O。

  • 反作用力流反压。

  • 高并发性和更少的硬件资源。

  • 函数式、流畅的API,它利用了Java 8 lambdas。

  • 同步和异步交互。

  • 流到服务器或从服务器流下来。

有关详细信息,请参阅WebClient

1.2. RestTemplate

RestTemplate在HTTP客户端库上提供更高级别的API。它使得在一行中调用REST端点变得很容易。它公开以下几组重载方法:

RestTemplate is in maintenance mode, with only requests for minor changes and bugs to be accepted. Please, consider using the WebClient instead.
Table 1. RestTemplate methods
Method group Description

getForObject

通过GET检索表示形式。

getForEntity

使用Get检索ResponseEntity(即状态、标头和正文)。

HeadForHeaders

使用Head检索资源的所有标头。

postForLocation

使用POST创建新资源,并从响应中返回Location头。

postForObject

使用POST创建新资源并从响应中返回表示形式。

postForEntity

使用POST创建新资源并从响应中返回表示形式。

放置

使用PUT创建或更新资源。

patchForObject

使用Patch更新资源并从响应中返回表示形式。注意,JDKHttpURLConnection不支持补丁,但ApacheHttpComponents和其他补丁支持。

删除

使用DELETE删除指定URI处的资源。

optionsForAllow

使用Allow检索资源的允许的HTTP方法。

交换

前述方法的更通用(和更少固执己见)版本,在需要时提供额外的灵活性。它接受RequestEntity(包括HTTP方法、URL、Header和Body作为输入)并返回ResponseEntity

这些方法允许使用ParameterizedTypeReference而不是Class来指定带有泛型的响应类型。

执行

执行请求的最通用方式,通过回调接口完全控制请求准备和响应提取。

1.2.1. Initialization

默认构造函数使用java.net.HttpURLConnection执行请求。您可以通过实现ClientHttpRequestFactory切换到不同的HTTP库。它内置了对以下功能的支持:

  • ApacheHttpComponents

  • 内蒂

  • OkHttp

例如,要切换到Apache HttpComponents,您可以使用以下命令:

RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());

             

每个ClientHttpRequestFactory都公开了特定于底层HTTP客户端库 - 的配置选项,例如凭据、连接池和其他细节。

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

许多RestTemplate方法接受URI模板和URI模板变量,要么作为字符串变量参数,要么作为Map<;字符串

下面的示例使用字符串变量参数:

String result = restTemplate.getForObject(
        "https://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");

              

下面的示例使用Map<;字符串,字符串&>

Map<String, String> vars = Collections.singletonMap("hotel", "42");

String result = restTemplate.getForObject(
        "https://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);

              

请记住,URI模板是自动编码的,如下例所示:

restTemplate.getForObject("https://example.com/hotel list", String.class);

// Results in request to "https://example.com/hotel%20list"

              

您可以使用RestTemplateuriTemplateHandler属性来自定义URI的编码方式。或者,您可以准备一个java.net.URI并将其传递给接受URIRestTemplate方法之一。

有关使用和编码URI的详细信息,请参阅URI链接

Headers

您可以使用exchange()方法来指定请求头,如下例所示:

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();

              

您可以通过许多返回ResponseEntityRestTemplate方法变量来获取响应头。

1.2.2. Body

HttpMessageConverter的帮助下,传入RestTemplate方法和从RestTemplate方法返回的对象将转换为原始内容。

在POST中,输入对象被序列化为请求正文,如下例所示:

URI location = template.postForLocation("https://example.com/people", person);

您不需要显式设置请求的Content-Type标头。在大多数情况下,您可以根据源代码对象类型找到一个兼容的消息转换器,所选的消息转换器会相应地设置内容类型。如果需要,您可以使用交换方法显式地提供Content-Type请求标头,这反过来会影响所选择的消息转换器。

在GET上,响应的正文被反序列化为输出对象,如下例所示:

Person person = restTemplate.getForObject("https://example.com/people/{id}", Person.class, 42);

不需要显式设置请求的Accept标头。在大多数情况下,可以根据预期的响应类型找到兼容的消息转换器,这有助于填充Accept头。如果需要,您可以使用交换方法显式提供Accept头。

默认情况下,RestTemplate注册所有内置的消息转换器,这取决于帮助确定存在哪些可选转换库的类路径检查。您还可以将消息转换器设置为显式使用。

1.2.3. Message Conversion

Spring-web模块包含HttpMessageConverter约定,用于通过InputStreamOutputStream读写HTTP请求和响应的正文。HttpMessageConverter实例用于客户端(例如,在RestTemplate中)和服务器端(例如,在Spring MVC REST控制器中)。

框架中提供了主要媒体(MIME)类型的具体实现,默认情况下,注册到客户端的RestTemplate和服务器端的RequestMappingHandlerAdapter(请参阅配置消息转换器)。

HttpMessageConverter的实现将在以下几节中介绍。对于所有转换器,都使用默认的媒体类型,但是您可以通过设置supportedMediaTypesBean属性来覆盖它。下表描述了每种实施:

Table 2. HttpMessageConverter Implementations
MessageConverter Description

StringHttpMessageConverter

可以从HTTP请求和响应中读写字符串实例的HttpMessageConverter实现。默认情况下,此转换器支持所有文本媒体类型(Text/*),并使用Text/PlainContent-Type编写。

FormHttpMessageConverter

可以从HTTP请求和响应读写表单数据的HttpMessageConverter实现。默认情况下,此转换器读取和写入application/x-www-form-urlencoded媒体类型。表单数据从MultiValueMap<;字符串,字符串&>读取和写入。转换器还可以写入(但不能读取)从MultiValueMap<;字符串、对象读取的多部分数据。默认支持分块/Form-Data。从Spring Framework5.2开始,可以支持附加的多部件子类型来编写表单数据。有关详细信息,请参考FormHttpMessageConverter的javadoc。

ByteArrayHttpMessageConverter

可以从HTTP请求和响应中读写字节数组的HttpMessageConverter实现。默认情况下,该转换器支持所有媒体类型(*/*),并使用应用程序/八位字节流Content-Type编写。您可以通过设置supportedMediaTypes属性并覆盖getContent Type(byte[])来覆盖它。

MarshallingHttpMessageConverter

一个HttpMessageConverter实现,它可以使用Spring的编组反编组抽象从org.springFramework.oxm包中读取和写入XML。此转换器需要编组解组才能使用。您可以通过构造函数或Bean属性注入它们。默认情况下,此转换器支持Text/XMLapp/xml

MappingJackson2HttpMessageConverter

一个HttpMessageConverter实现,可以使用Jackson的对象映射器读写JSON。通过使用Jackson提供的注释,您可以根据需要定制JSON映射。当您需要进一步控制时(对于需要为特定类型提供自定义JSON序列化程序/反序列化程序的情况),您可以通过对象映射器属性注入一个自定义对象映射器。默认情况下,该转换器支持应用程序/json

MappingJackson2XmlHttpMessageConverter

可以使用Jackson XML扩展的XmlMapper读写XML的HttpMessageConverter实现。通过使用JAXB或Jackson提供的注释,您可以根据需要定制XML映射。当您需要进一步控制时(对于需要为特定类型提供自定义XML序列化程序/反序列化程序的情况),您可以通过ObjectMapper属性注入一个自定义XmlMapper。默认情况下,该转换器支持应用程序/xml

SourceHttpMessageConverter

一个HttpMessageConverter实现,可以从HTTP请求和响应中读写javax.xml.Trans.Source。目前仅支持DOMSourceSAXSourceStreamSource。默认情况下,此转换器支持Text/XMLapp/xml

BufferedImageHttpMessageConverter

一个HttpMessageConverter实现,可以从HTTP请求和响应中读写java.awt.Image.BufferedImage。该转换器读取和写入Java I/O API支持的媒体类型。

1.2.4. Jackson JSON Views

您可以指定Jackson JSON View仅序列化对象属性的子集,如下例所示:

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);

             

1.2.5. Multipart

若要发送多部分数据,您需要提供一个MultiValueMap<;字符串,Object&>,其值可以是部件内容的对象、文件部件的资源或带有标题的部件内容的HttpEntity。例如:

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));

             

大多数情况下,您不必为每个部分指定Content-Type。内容类型是根据选择序列化它的HttpMessageConverter自动确定的,如果是基于文件扩展名的资源,则自动确定内容类型。如果需要,您可以显式地为MediaType提供一个HttpEntity包装。

MultiValueMap准备好后,您可以将其传递给RestTemplate,如下所示:

MultiValueMap<String, Object> parts = ...;
template.postForObject("https://example.com/upload", parts, Void.class);

             

如果MultiValueMap至少包含一个非字符串值,则Content-TypeFormHttpMessageConverter设置为Multipart/Form-Data。如果多值映射具有字符串值,则内容类型默认为application/x-www-form-urlencoded.如果需要,还可以显式设置Content-Type

1.3. HTTP Interface

Spring框架允许您将HTTP服务定义为具有带注释的HTTP交换方法的Java接口。然后,您可以生成实现此接口并执行交换的代理。这有助于简化HTTP远程访问,这通常涉及包装使用底层HTTP客户端的细节的外观。

第一,使用@HttpExchange方法声明一个接口:

interface RepositoryService {

    @GetExchange("/repos/{owner}/{repo}")
    Repository getRepository(@PathVariable String owner, @PathVariable String repo);

    // more HTTP exchange methods...

}

            

第二,创建将执行声明的HTTP交换的代理:

WebClient client = WebClient.builder().baseUrl("https://api.github.com/").build();
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();

RepositoryService service = factory.createClient(RepositoryService.class);

            

@HttpExchange在应用于所有方法的类型级别受支持:

@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

带注释的HTTP交换方法支持使用以下方法参数的灵活方法签名:

Method argument Description

URI

动态设置请求的URL,覆盖批注的url属性。

HttpMethod

动态设置请求的HTTP方法,覆盖批注的方法属性

@RequestHeader

添加一个或多个请求标头。参数可以是具有多个标头的Map;字符串、MultiValueMap<;字符串、集合值?&>或单个值。非字符串值支持类型转换。

@路径变量

添加一个变量,用于在请求URL中展开占位符。参数可以是具有多个变量的Map<;字符串、?&>或单个值。非字符串值支持类型转换。

@RequestBody

将请求的正文作为要序列化的对象提供,或者以反应式流发布者的形式提供,如MonoFlux,或通过配置的Reactive AdapterRegistry支持的任何其他异步类型。

@RequestParam

添加一个或多个请求参数。参数可以是具有多个参数的Map;字符串、MultiValueMap<;字符串、集合值?&>或单个值。非字符串值支持类型转换。

“Content-TYPE”设置为“application/x-www-form-urlencoded”,时,请求参数将编码在请求正文中。否则,它们将被添加为URL查询参数。

@RequestPart

添加一个请求部件,它可以是字符串(表单字段)、资源(文件部件)、Object(要编码的实体,例如JSON)、HttpEntity(部件内容和头部)、Spring部件或以上任意一个的反应流发布者

@CookieValue

添加一个或多个Cookie。参数可以是具有多个Cookie的Map;字符串、?&>MultiValueMap<;字符串、集合值?&>或单个值。非字符串值支持类型转换。

1.3.2. Return Values

带注释的HTTP交换方法支持以下返回值:

Method return value Description

单色;空&>

执行给定的请求,并释放响应内容(如果有)。

HttpHeaders单一HttpHeaders<;

执行给定的请求,释放响应内容(如果有),并返回响应头。

<;T&>;单声道<;T&>

执行给定的请求并将响应内容解码为声明的返回类型。

<;T&>;通量<;T&>

执行给定的请求并将响应内容解码为声明的元素类型的流。

ResponseEntity<;void&>Mono<;ResponseEntity<;Void>;>;

执行给定的请求,释放响应内容(如果有),并返回带有状态和Header的ResponseEntity

ResponseEntity;T&>;Mono<;ResponseEntity<;T>;>;

执行给定的请求,将响应内容解码为声明的返回类型,并返回带有状态、Header和解码的Body的ResponseEntity

Mono<;ResponseEntity<;Flux<;T>;>;

执行给定的请求,将响应内容解码为声明的元素类型的流,并返回一个ResponseEntity,其中包含状态、Header和解码的响应主体流。

You can also use any other async or reactive types registered in the ReactiveAdapterRegistry.

1.3.3. Exception Handling

默认情况下,WebClient引发4xx和5xx HTTP状态码的WebClientResponseException。要对此进行自定义,您可以注册一个响应状态处理程序,该处理程序适用于通过客户端执行的所有响应:

WebClient webClient = WebClient.builder()
        .defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
        .build();

WebClientAdapter clientAdapter = WebClientAdapter.forClient(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory
        .builder(clientAdapter).build();

             

有关更多详细信息和选项,如隐藏错误状态码,请参阅WebClient.BuilderdefaultStatusHandler的Javadoc。

2. JMS (Java Message Service)

Spring提供了一个JMS集成框架,它简化了JMS API的使用,其方式与Spring对JDBC API的集成非常相似。

JMS可以大致分为两个功能领域,即消息的生产和消费。JmsTemplate类用于消息生成和同步消息接收。对于类似于Jakarta EE的消息驱动Bean风格的异步接收,Spring提供了许多消息侦听器容器,您可以使用它们来创建消息驱动POJO(MDP)。Spring还提供了一种声明性方式来创建消息侦听器。

org.springFrawork.jms.core包提供了使用JMS的核心功能。它包含JMS模板类,这些模板类通过处理资源的创建和发布来简化JMS的使用,就像JdbcTemplate为JDBC所做的那样。Spring模板类的共同设计原则是提供帮助器方法来执行常见操作,对于更复杂的用法,将处理任务的本质委托给用户实现的回调接口。JMS模板遵循相同的设计。这些类为发送消息、同步使用消息以及向用户公开JMS会话和消息生成器提供了各种方便的方法。

org.springFrawork.jms.support包提供了JMSException转换功能。转换将选中的JMSException层次结构转换为未检查异常的镜像层次结构。如果存在选中的jakarta.jms.JMSException的任何特定于提供程序的子类,则此异常包装在未选中的UncategizedJmsException中。

org.springframework.jms.support.converter包提供了一个MessageConverter抽象来在Java对象和JMS消息之间进行转换。

org.springframework.jms.support.destination包提供了用于管理JMS目的地的各种策略,例如为存储在JNDI中的目的地提供服务定位器。

通过使用<org.springframework.jms.annotation>@JmsListener,JmsListener包提供了必要的基础设施来支持注释驱动的侦听器端点。

org.springFrawork.jms.config包提供jms名称空间的解析器实现,以及配置侦听器容器和创建侦听器端点的Java配置支持。

最后,org.springframework.jms.connection包提供了适合在独立应用程序中使用的ConnectionFactory的实现。它还包含Spring的PlatformTransactionManagerfor JMS的实现(巧妙地命名为JmsTransactionManager)。这允许将JMS作为事务性资源无缝集成到Spring的事务管理机制中。

从Spring Framework5开始,Spring的JMS包完全支持JMS 2.0,并要求在运行时出现JMS 2.0 API。我们建议使用与JMS 2.0兼容的提供程序。

如果您碰巧在系统中使用较旧的Message Broker,您可以尝试升级到与现有Broker代兼容的JMS 2.0驱动程序。或者,您也可以尝试针对基于JMS 1.1的驱动程序运行,只需将JMS 2.0 API JAR放在类路径上,而只对您的驱动程序使用与JMS 1.1兼容的API。默认情况下,Spring的JMS支持遵循JMS 1.1约定,因此通过相应的配置,它确实支持这样的场景。但是,请仅针对过渡方案考虑这一点。

2.1. Using Spring JMS

本节介绍如何使用Spring的JMS组件。

2.1.1. Using JmsTemplate

JmsTemplate类是JMS核心包中的核心类。它简化了JMS的使用,因为它在发送或同步接收消息时处理资源的创建和释放。

使用JmsTemplate的代码只需要实现回调接口,这些接口为它们提供了一个明确定义的高级约定。MessageCreator回调接口在提供由JmsTemplate中的调用代码提供的会话时创建一条消息。为了允许更复杂地使用JMS API,SessionCallback提供了JMS会话,ProducerCallback公开了一个会话MessageProducer对。

JMS API公开了两种类型的发送方法,一种采用交付模式、优先级和生存时间作为服务质量(QOS)参数,另一种不采用QOS参数并使用缺省值。由于JmsTemplate有许多发送方法,设置QOS参数被公开为Bean属性,以避免发送方法的数量重复。同样,同步接收调用的超时值是使用setReceiveTimeout属性设置的。

一些JMS提供程序允许通过配置ConnectionFactory管理性地设置默认QOS值。这样做的效果是,调用MessageProducer实例的Send方法(Send(Destination,Message Message))使用与JMS规范中指定的值不同的QOS缺省值。为了提供QOS值的一致管理,JmsTemplate必须专门启用以使用其自己的QOS值,方法是将布尔属性isEXPLICTICTQosEnabled设置为true

为方便起见,JmsTemplate还公开了一个基本的请求-回复操作,该操作允许在作为操作的一部分创建的临时队列上发送消息并等待回复。

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.

从Spring Framework4.1开始,JmsMessagingTemplate构建在JmsTemplate之上,并提供了与消息传递抽象org.springframework.messaging.Message.- - 的集成这使您可以创建要以通用方式发送的消息。

2.1.2. Connections

JmsTemplate需要引用ConnectionFactoryConnectionFactory是JMS规范的一部分,是使用JMS的入口点。客户端应用程序使用它作为工厂来创建与JMS提供程序的连接,并封装各种配置参数,其中许多参数是特定于供应商的,例如SSL配置选项。

在EJB中使用JMS时,供应商提供JMS接口的实现,以便它们可以参与声明性事务管理并执行连接和会话的池化。为了使用此实现,Jakarta EE容器通常要求您将JMS连接工厂声明为EJB或Servlet部署描述符内的resource-ref。为了确保将这些功能与EJB中的JmsTemplate一起使用,客户端应用程序应该确保它引用ConnectionFactory的托管实现。

Caching Messaging Resources

标准API涉及创建许多中间对象。要发送消息,需要执行以下‘API’遍历:

ConnectionFactory->Connection->Session->MessageProducer->send

ConnectionFactorySend操作之间,创建和销毁了三个中间对象。为了优化资源使用并提高性能,Spring提供了两个ConnectionFactory实现。

Using SingleConnectionFactory

Spring提供了ConnectionFactory接口SingleConnectionFactory的实现,该接口对所有createConnection()调用返回相同的连接,并忽略对Close()的调用。这对于测试和独立环境非常有用,这样可以将同一连接用于可能跨越任意数量的事务的多个JmsTemplate调用。SingleConnectionFactory引用通常来自JNDI的标准ConnectionFactory

Using CachingConnectionFactory

CachingConnectionFactory扩展了SingleConnectionFactory的功能,增加了SessionMessageProducerMessageConsumer实例的缓存。初始缓存大小设置为1。您可以使用essionCacheSize属性来增加缓存会话的数量。请注意,实际缓存的会话数量多于该数量,因为会话是根据其确认模式进行缓存的,因此当sessionCacheSize设置为1时,最多可以有四个缓存的会话实例(每个确认模式对应一个)。MessageProducerMessageConsumer实例在其拥有的会话中缓存,并在缓存时考虑生产者和消费者的唯一属性。MessageProducers根据其目的地进行缓存。MessageConsumer基于由目的地、选择器、noLocal传递标志和持久订阅名称(如果创建持久消费者)组成的键进行缓存。

临时队列和主题(TemporaryQueue/TemporaryTheme)的MessageProducers和MessageConsumer不会被缓存。不幸的是,WebLogic JMS碰巧在其常规目的地实现上实现了临时队列/主题接口,这错误地表明它的任何目的地都不能被缓存。请在WebLogic上使用其他连接池/缓存,或者为WebLogic自定义CachingConnectionFactory

2.1.3. Destination Management

作为ConnectionFactory实例的目的地是可以在JNDI中存储和检索的JMS管理对象。在配置Spring应用程序上下文时,您可以使用JNDIJndiObjectFactoryBean工厂类或<;jee:jndi-lookup>;在对象对JMS目的地的引用上执行依赖项注入。但是,如果应用程序中有大量目的地,或者如果存在JMS提供程序独有的高级目的地管理功能,则此策略通常很麻烦。这种高级目的地管理的示例包括创建动态目的地或支持目的地的分层命名空间。JmsTemplate将目标名称的解析委托给实现DestinationResolver接口的JMS目标对象。DynamicDestinationResolverJmsTemplate使用的默认实现,支持解析动态目标。还提供了JndiDestinationResolver,以充当JNDI中包含的目的地的服务定位器,并且可以选择回退到DynamicDestinationResolver中包含的行为。

通常,JMS应用程序中使用的目的地只有在运行时才知道,因此不能在部署应用程序时以管理方式创建。这通常是因为在交互系统组件之间存在共享的应用程序逻辑,这些组件根据众所周知的命名约定在运行时创建目的地。尽管动态目的地的创建不是JMS规范的一部分,但大多数供应商都提供了此功能。动态目的地是使用用户定义的名称创建的,这将它们与临时目的地区分开来,并且通常不在JNDI中注册。用于创建动态目的地的API因提供商而异,因为与目的地关联的属性是特定于供应商的。然而,供应商有时会做出一个简单的实现选择,那就是忽略JMS规范中的警告,并使用方法TopicSessioncreateTheme(字符串topicName)QueueSessioncreateQueue(字符串队列名称)方法来创建具有默认目的地属性的新目的地。根据供应商实现的不同,DynamicDestinationResolver还可以创建物理目标,而不仅仅是解析一个。

布尔属性pubSubDomain用于使用所使用的JMS域来配置JmsTemplate。默认情况下,该属性的值为FALSE,表示要使用点对点域Queues。此属性(由JmsTemplate使用)通过DestinationResolver接口的实现确定动态目标解析的行为。

您还可以通过属性defaultDestinationJmsTemplate配置为默认目的地。默认目标是不指向特定目标的发送和接收操作。

2.1.4. Message Listener Containers

JMS消息在EJB世界中最常见的用途之一是驱动消息驱动的Bean(MDB)。Spring提供了一种创建消息驱动POJO(MDP)的解决方案,这种方法不会将用户绑定到EJB容器。(有关Spring的MDP支持的详细报道,请参阅异步接收:消息驱动POJO。)从Spring Framework4.1开始,端点方法可以使用@JmsListener - 进行注释。有关更多详细信息,请参阅注释驱动的侦听器端点。

消息侦听器容器用于从JMS消息队列接收消息,并驱动注入其中的MessageListener。侦听器容器负责消息接收的所有线程处理,并将其分派到侦听器进行处理。消息侦听器容器是MDP和消息传递提供程序之间的中介,负责注册以接收消息、参与事务、资源获取和释放、异常转换等。这允许您编写与接收消息(并可能响应消息)相关联的(可能复杂的)业务逻辑,并将样板JMS基础设施关注委托给框架。

有两个标准的JMS消息侦听器容器打包在Spring中,每个容器都有自己的专用功能集。

Using SimpleMessageListenerContainer

此消息侦听器容器是两种标准风格中较简单的一种。它在启动时创建固定数量的JMS会话和使用者,使用标准的JMSMessageConsumer.setMessageListener()方法注册侦听器,并让JMS提供者来执行侦听器回调。此变体不允许动态适应运行时需求或参与外部管理的事务。在兼容性方面,它非常接近独立JMS规范的精神,但通常与Jakarta EE的JMS限制不兼容。

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

此消息侦听器容器在大多数情况下使用。与SimpleMessageListenerContainer不同,此容器变体允许动态适应运行时需求,并能够参与外部管理的事务。当使用JtaTransactionManager配置时,每个接收到的消息都注册到一个XA事务。因此,处理可能会利用XA事务语义。此侦听器容器在对JMS提供程序的低要求、高级功能(如参与外部管理的事务)和与Jakarta EE环境的兼容性之间取得了很好的平衡。

您可以自定义容器的缓存级别。请注意,当没有启用缓存时,将为每个消息接收创建一个新连接和一个新会话。将其与具有高负载的非持久订阅相结合可能会导致消息丢失。在这种情况下,请确保使用适当的高速缓存级别。

当代理关闭时,该容器还具有可恢复的功能。默认情况下,简单的Backoff实现每五秒重试一次。您可以指定一个自定义的退避实现,以获得更细粒度的恢复选项。有关示例,请参阅ExponentialBackOff

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提供了一个JmsTransactionManager,它管理单个JMSConnectionFactory的事务。这使JMS应用程序能够利用Spring的托管事务特性,如数据访问一章的事务管理部分所述。JmsTransactionManager执行本地资源事务,将JMS连接/会话对从指定的ConnectionFactory绑定到线程。JmsTemplate自动检测此类事务性资源并相应地对其进行操作。

在Jakarta EE环境中,ConnectionFactory共享连接和会话实例,因此这些资源可以跨事务高效地重用。在独立环境中,使用Spring的SingleConnectionFactory会产生一个共享的JMS连接,每个事务都有自己独立的会话。或者,考虑使用特定于提供程序的池适配器,例如ActiveMQ的PooledConnectionFactory类。

您还可以将JmsTemplateJtaTransactionManager和支持XA的JMSConnectionFactory一起使用来执行分布式事务。请注意,这需要使用JTA事务管理器以及正确配置XA的ConnectionFactory。(请查看您的Jakarta EE服务器或JMS提供商的文档。)

在使用JMS API从连接创建会话时,跨托管和非托管事务环境重用代码可能会令人困惑。这是因为JMS API只有一个工厂方法来创建会话,并且它需要事务和确认模式的值。在托管环境中,设置这些值是环境的事务基础设施的责任,因此供应商的JMS连接包装器会忽略这些值。当您在非托管环境中使用JmsTemplate时,可以通过使用属性会话事务会话确认模式来指定这些值。当您将PlatformTransactionManagerJmsTemplate一起使用时,模板始终被赋予事务性JMS会话

2.2. Sending a Message

JmsTemplate包含许多方便的发送消息的方法。Send方法通过使用jakarta.jms.Destination对象指定目的地,而其他方法通过在JNDI查找中使用字符串指定目的地。不接受目标参数的Send方法使用默认目标。

下面的示例使用MessageCreator回调从提供的会话对象创建一条文本消息:

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"); } }); } } 
            

在前面的示例中,JmsTemplate是通过传递对ConnectionFactory的引用来构造的。作为替代方案,提供了一个零参数构造函数和ConnectionFactory,并可用于以JavaBean样式(使用BeanFactory或纯Java代码)构造实例。或者,考虑派生自Spring的JmsGatewaySupport便利基类,它为JMS配置提供了预构建的Bean属性。

Send(String estinationName,MessageCreator creator)方法允许您使用目的地的字符串名称发送消息。如果这些名称是在JNDI中注册的,则应该将模板的estinationResolver属性设置为JndiDestinationResolver的实例。

如果您创建了JmsTemplate并指定了默认目标,Send(MessageCreator C)将向该目标发送一条消息。

2.2.1. Using Message Converters

为了促进域模型对象的发送,JmsTemplate有各种以Java对象作为消息数据内容参数的发送方法。JmsTemplate中的重载方法ConvertAndSend()ReceiveAndConvert()方法将转换过程委托给MessageConverter接口的一个实例。该接口定义了一个简单的协定来在Java对象和JMS消息之间进行转换。默认实现(SimpleMessageConverter)支持字符串TextMessagebyte[]BytesMessage以及java.util.MapMapMessage之间的转换。通过使用转换器,您和您的应用程序代码可以专注于通过JMS发送或接收的业务对象,而不必关心如何将其表示为JMS消息的细节。

沙箱当前包括MapMessageConverter,它使用反射在JavaBean和MapMessage之间进行转换。您可能自己实现的其他流行的实现选择是使用现有的XML编组包(如JAXB或XStream)来创建表示对象的TextMessage的转换器。

为了适应不能一般封装在转换器类中的消息属性、头和正文的设置,MessagePostProcessor接口允许您在消息转换之后但在消息发送之前访问消息。以下示例说明如何在将java.util.Map转换为消息后修改消息标头和属性:

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;
        }
    });
}

             

这将产生以下形式的消息:

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

虽然发送操作涵盖许多常见的使用场景,但有时您可能希望对JMS会话MessageProducer执行多个操作。SessionCallbackProducerCallback分别公开JMS会话会话/MessageProducer对。JmsTemplate上的Execute()方法运行这些回调方法。

2.3. Receiving a Message

本文描述了如何在Spring中使用JMS接收消息。

2.3.1. Synchronous Reception

虽然JMS通常与异步处理相关联,但您也可以同步使用消息。重载的Receive(..)方法提供此功能。在同步接收期间,调用线程会阻塞,直到有消息可用。这可能是一个危险的操作,因为调用线程可能会被无限期地阻止。ReceiveTimeout属性指定接收方在放弃等待消息之前应等待多长时间。

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.

与EJB世界中的消息驱动Bean(MDB)类似,消息驱动POJO(MDP)充当JMS消息的接收器。MDP上的一个限制(但请参阅UsingMessageListenerAdapter)是它必须实现jakarta.jms.MessageListener接口。请注意,如果您的POJO在多个线程上接收消息,确保您的实现是线程安全的非常重要。

以下示例显示了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"); } } } 
             

一旦实现了MessageListener,就可以创建消息侦听器容器了。

以下示例显示如何定义和配置Spring附带的一个消息侦听器容器(在本例中为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>
             

有关每个实现所支持的功能的完整描述,请参阅各种消息侦听器容器(所有这些容器都实现了MessageListenerContainer)的Spring javadoc。

2.3.3. Using the SessionAwareMessageListener Interface

SessionAwareMessageListener接口是一个特定于Spring的接口,它提供了与JMSMessageListener接口类似的约定,但也使消息处理方法能够访问从其接收消息的JMS会话。下面的清单显示了SessionAwareMessageListener接口的定义:

package org.springframework.jms.listener;

public interface SessionAwareMessageListener {

    void onMessage(Message message, Session session) throws JMSException;
}

             

如果希望MDP能够响应任何接收到的消息(通过使用onMessage(Message,Session)方法中提供的Session),您可以选择让MDP实现此接口(优先于标准JMSMessageListener接口)。Spring附带的所有消息侦听器容器实现都支持实现MessageListenerSessionAwareMessageListener接口的MDP。实现SessionAwareMessageListener的类附带一个警告,即它们随后通过接口绑定到Spring。是否使用它的选择完全由应用程序开发人员或架构师决定。

请注意,SessionAwareMessageListener接口的onMessage(..)方法抛出JMSException。与标准JMSMessageListener接口不同,当使用SessionAwareMessageListener接口时,客户端代码负责处理任何抛出的异常。

2.3.4. Using MessageListenerAdapter

MessageListenerAdapter类是Spring的异步消息传递支持中的最后一个组件。简而言之,它允许您将几乎任何类公开为MDP(尽管有一些限制)。

请考虑以下接口定义:

public interface MessageDelegate {

    void handleMessage(String message);

    void handleMessage(Map message);

    void handleMessage(byte[] message);

    void handleMessage(Serializable message);
}

             

请注意,尽管该接口既没有扩展MessageListener也没有扩展SessionAwareMessageListener接口,但是您仍然可以通过使用MessageListenerAdapter类将其用作MDP。还要注意各种消息处理方法是如何根据它们可以接收和处理的各种消息类型的内容进行强类型化的。

现在考虑MessageDelegate接口的以下实现:

public class DefaultMessageDelegate implements MessageDelegate {
    // implementation elided for clarity...
}

             

特别要注意的是,MessageDelegate接口的前面实现(DefaultMessageDelegate类)根本没有JMS依赖项。它确实是一个POJO,我们可以通过以下配置将其制成MDP:

<!-- 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>
             

下一个示例显示另一个MDP,它只能处理接收JMSTextMessage消息。注意消息处理方法实际上是如何被称为Receive的(MessageListenerAdapter中的消息处理方法的名称默认为handleMessage),但它是可配置的(您可以在本节后面看到)。还要注意Receive(..)方法如何被强类型化为只接收和响应JMSTextMessage消息。下面的清单显示了TextMessageDelegate接口的定义:

public interface TextMessageDelegate {

    void receive(TextMessage message);
}

             

下面的清单显示了一个实现TextMessageDelegate接口的类:

public class DefaultTextMessageDelegate implements TextMessageDelegate {
    // implementation elided for clarity...
}

             

助理MessageListenerAdapter的配置如下所示:

<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>
             

请注意,如果MessageListener接收到非TextMessage类型的JMS消息,则抛出IllegalStateException异常(并随后被吞噬)。MessageListenerAdapter类的另一个功能是,如果处理程序方法返回非空值,则自动返回响应Message。请考虑以下接口和类:

public interface ResponsiveTextMessageDelegate {

    // notice the return type...
    String receive(TextMessage message);
}

             
public class DefaultResponsiveTextMessageDelegate implements ResponsiveTextMessageDelegate {
    // implementation elided for clarity...
}

             

如果将DefaultResponsiveTextMessageDelegateMessageListenerAdapter结合使用,则从‘Receive(..)’方法的执行中返回的任何非空值都将(在默认配置中)转换为TextMessage。然后将得到的TextMessage发送到在原始消息的JMS回复属性中定义的目标(如果存在),或在MessageListenerAdapter上设置的默认目标(如果已经配置了一个)。如果未找到Destination,则抛出InvalidDestinationException(请注意,此异常不会被吞下并向上传播到调用堆栈)。

2.3.5. Processing Messages Within Transactions

在事务中调用消息侦听器只需要重新配置侦听器容器。

您可以通过监听器容器定义上的sessionTransated标志来激活本地资源事务。然后,每个消息侦听器调用在活动的JMS事务中操作,在侦听器执行失败的情况下回滚消息接收。发送响应消息(通过SessionAwareMessageListener)是同一本地事务的一部分,但任何其他资源操作(如数据库访问)都是独立运行的。这通常需要侦听器实现中的重复消息检测,以涵盖数据库处理已提交但消息处理未能提交的情况。

考虑以下Bean定义:

<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>
             

要参与外部管理的事务,您需要配置事务管理器并使用支持外部管理事务的监听器容器(通常为DefaultMessageListenerContainer).

要为XA事务参与配置消息侦听器容器,您需要配置JtaTransactionManager(缺省情况下,它委托给Jakarta EE服务器的事务子系统)。请注意,底层JMSConnectionFactory需要支持XA,并向JTA事务协调器正确注册。(检查Jakarta EE服务器的JNDI资源配置。)这使得消息接收和(例如)数据库访问成为同一事务的一部分(使用统一的提交语义,代价是XA事务日志开销)。

下面的Bean定义创建了一个事务管理器:

<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
             

然后,我们需要将其添加到先前的容器配置中。集装箱负责其余的工作。以下示例显示了如何执行此操作:

<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

从2.5版开始,Spring还支持基于JCA的MessageListener容器。JmsMessageEndpointManager尝试从提供程序的ResourceAdapter类名中自动确定ActivationSpec类名。因此,通常可以提供Spring的泛型JmsActivationspecConfig,如下面的示例所示:

<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>
            

或者,您可以使用给定的ActivationSpec对象设置JmsMessageEndpointManagerActivationSpec对象也可能来自JNDI查找(使用<;jee:jndi-lookup&>)。以下示例显示了如何执行此操作:

<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>
            

使用Spring的ResourceAdapterFactoryBean,可以在本地配置目标ResourceAdapter,如下例所示:

<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>
            

指定的WorkManager还可以指向环境特定的线程池 - ,通常通过SimpleTaskWorkManager实例的asyncTaskExecutor属性。如果您碰巧使用多个适配器,请考虑为所有ResourceAdapter实例定义一个共享线程池。

在某些环境中(例如WebLogic 9或更高版本),您可以从JNDI(通过使用<;jee:jndi-lookup>;)获取整个ResourceAdapter对象。然后,基于Spring的消息侦听器可以与服务器托管的ResourceAdapter交互,后者也使用服务器的内置WorkManager

有关详细信息,请参阅JmsMessageEndpointManagerJmsActivationSpecConfig,和ResourceAdapterFactoryBean的javadoc。

Spring还提供了一个没有绑定到jms:org.springframework.jca.endpoint.GenericMessageEndpointManager.的通用JCA消息端点管理器该组件允许使用任何消息侦听器类型(如JMSMessageListener)和任何特定于提供者的ActivationSpec对象。请参阅JCA提供者的文档以了解连接器的实际功能,有关特定于href=“0”>GenericMessageEndpointManager的配置细节,请参阅

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

异步接收消息的最简单方法是使用带注释的侦听器端点基础设施。简而言之,它允许您将托管Bean的方法公开为JMS侦听器端点。以下示例显示如何使用它:

@Component
public class MyService {

    @JmsListener(destination = "myDestination")
    public void processOrder(String data) { ... }
}

            

前面示例的思想是,只要jakarta.jms.DestinationMyDestination上有消息可用,就会相应地调用cessOrder方法(在本例中,使用jms消息的内容,类似于MessageListenerAdapter提供的内容)。

带注释的端点基础设施通过使用JmsListenerContainerFactory在幕后为每个带注释的方法创建一个消息侦听器容器。这样的容器不是针对应用程序上下文注册的,但可以使用JmsListenerEndpointRegistryBean轻松定位以用于管理目的。

@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

要启用对@JmsListener注释的支持,您可以将@EnableJms添加到您的一个@Configuration类中,如下面的示例所示:

@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;
    }
}

             

默认情况下,基础设施查找名为jmsListenerContainerFactory的Bean作为工厂用来创建消息侦听器容器的源。在本例中(忽略JMS基础设施设置),您可以使用3个线程的核心轮询大小和10个线程的最大池大小来调用process Order方法。

您可以定制用于每个注释的侦听器容器工厂,也可以通过实现JmsListenerConfigurer接口来配置显式缺省值。仅当至少有一个终结点在没有特定容器工厂的情况下注册时,才需要默认设置。有关详细信息和示例,请参阅实现JmsListenerConfigurer的类的javadoc。

如果您喜欢XML配置,可以使用<;jms:Annotation-Driven>;元素,如下例所示:

<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提供JMS终结点的模型,并负责为该模型配置容器。除了JmsListener注释检测到的端点之外,基础设施还允许您以编程方式配置端点。以下示例显示了如何执行此操作:

@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);
    }
}

             

在前面的示例中,我们使用了SimpleJmsListenerEndpoint,它提供了要调用的实际MessageListener。但是,您也可以构建自己的端点变量来描述自定义调用机制。

注意,您可以完全跳过@JmsListener的使用,通过JmsListenerConfigurer以编程方式只注册您的端点。

2.5.3. Annotated Endpoint Method Signature

到目前为止,我们在端点中注入了一个简单的字符串,但它实际上可以有一个非常灵活的方法签名。在下面的示例中,我们重写它以使用自定义标头注入订单

@Component
public class MyService {

    @JmsListener(destination = "myDestination")
    public void processOrder(Order order, @Header("order_type") String orderType) {
        ...
    }
}

             

可以在JMS监听程序端点中注入的主要元素如下:

  • 原始jakarta.jms.Message或其任何子类(假设它与传入消息类型匹配)。

  • jakarta.jms.Session,用于可选地访问本机JMS API(例如,用于发送自定义回复)。

  • 表示传入的JMS消息的org.springframework.messaging.Message。请注意,此消息同时包含自定义和标准标头(由JmsHeaders定义)。

  • @Header-带注释的方法参数,用于提取特定的标头值,包括标准JMS标头。

  • @Headers-带注释的参数,还必须分配给java.util.Map才能访问所有标头。

  • 不属于受支持类型(消息会话)的未注释元素被视为有效负载。您可以通过使用@PayLoad注释参数来明确说明这一点。您还可以通过添加额外的@valid来打开验证。

注入Spring的消息抽象的能力对于受益于存储在特定于传输的消息中的所有信息特别有用,而不依赖于特定于传输的API。以下示例显示了如何执行此操作:

@JmsListener(destination = "myDestination")
public void processOrder(Message<Order> order) { ... }

             

方法参数的处理由DefaultMessageHandlerMethodFactory,提供,您可以进一步定制它以支持其他方法参数。您还可以在那里自定义转换和验证支持。

例如,如果我们希望在处理订单之前确保它有效,我们可以用@Valid注释负载并配置必要的验证器,如下例所示:

@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

MessageListenerAdapter中的现有支持已经允许您的方法具有非<代码>空 返回类型。在这种情况下,调用的结果被封装在jakarta.jms.Message中,在原始消息的JMSReplyTo头中指定的目的地中发送,或者在侦听器上配置的默认目的地中发送。现在,您可以通过使用消息传递抽象的@SendTo注释来设置默认目的地。

假设我们的cessOrder方法现在应该返回OrderStatus,我们可以编写它以自动发送响应,如下面的示例所示:

@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.

如果您需要以与传输无关的方式设置额外的Header,您可以返回一个消息,方法如下:

@JmsListener(destination = "myDestination")
@SendTo("status")
public Message<OrderStatus> processOrder(Order order) {
    // order processing
    return MessageBuilder
            .withPayload(status)
            .setHeader("code", 1234)
            .build();
}

             

如果需要在运行时计算响应目的地,可以将响应封装在JmsResponse实例中,该实例也提供运行时使用的目的地。我们可以按如下方式重写前面的示例:

@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");
}

             

最后,如果您需要为响应指定一些QOS值,例如优先级或生存时间,您可以相应地配置JmsListenerContainerFactory,如下例所示:

@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提供了一个用于简化JMS配置的XML命名空间。要使用JMS命名空间元素,您需要引用JMS架构,如下例所示:

<?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.

命名空间由三个顶级元素组成:<;注解驱动/>;<;listener-tainer/>;<;jca-listener-tainer/>;<;批注驱动/允许使用批注驱动的侦听器端点<;listener-tainer/&><;jca-listener-tainer/>;定义共享监听器容器配置,可以包含<;listener/>;子元素。以下示例显示了两个监听程序的基本配置:

<jms:listener-container>

    <jms:listener destination="queue.orders" ref="orderService" method="placeOrder"/>

    <jms:listener destination="queue.confirmations" ref="confirmationLogger" method="log"/>

</jms:listener-container>
            

前面的示例相当于创建两个不同的侦听器容器Bean定义和两个不同的MessageListenerAdapterBean定义,如UsingMessageListenerAdapter中所示。除了前面示例中显示的属性外,侦听器元素还可以包含几个可选的属性。下表描述了所有可用的属性:

Table 3. Attributes of the JMS <listener> element
Attribute Description

id

宿主监听程序容器的Bean名称。如果未指定,则会自动生成一个Bean名称。

目标(必填)

此侦听器的目标名称,通过DestinationResolver策略解析。

参考(必填)

处理程序对象的Bean名称。

方法

要调用的处理程序方法的名称。如果ref属性指向MessageListener或SpringSessionAwareMessageListener,则可以忽略此属性。

响应-目标

要向其发送响应消息的默认响应目标的名称。这适用于请求消息没有携带JMSReplyTo字段的情况。此目的地的类型由侦听器容器的Response-Destination-type属性确定。请注意,这只适用于具有返回值的侦听器方法,对于该方法,每个结果对象都被转换为响应消息。

订阅

持久订阅的名称(如果有)。

选择器

此监听程序的可选消息选择器。

并发

要为此监听程序启动的并发会话或使用者数。该值可以是表示最大值的简单数字(例如,5),也可以是表示下限和上限的范围(例如,3-5)。请注意,指定的最小值只是一个提示,在运行时可能会被忽略。默认值为容器提供的值。

<;listener-tainer/>;元素还接受几个可选属性。这允许定制各种策略(例如,taskExecutorestinationResolver)以及基本的JMS设置和资源引用。通过使用这些属性,您可以定义高度定制的侦听器容器,同时仍然受益于名称空间的便利性。

通过指定要通过Factory-id属性公开的Bean的id,可以将此类设置自动公开为JmsListenerContainerFactory,如下例所示:

<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>
            

下表描述了所有可用的属性。有关各个属性的更多详细信息,请参阅AbstractMessageListenerContainer及其具体子类的类级javadoc。该javadoc还提供了对事务选择和消息重新传递场景的讨论。

Table 4. Attributes of the JMS <listener-container> element
Attribute Description

容器类型

此侦听器容器的类型。可用选项包括默认简单default102imple102(默认选项为默认)。

容器类

作为完全限定类名的自定义侦听器容器实现类。根据容器类型属性,缺省值是Spring的标准DefaultMessageListenerContainerSimpleMessageListenerContainer

工厂ID

将此元素定义的设置公开为具有指定的idJmsListenerContainerFactory,以便可以在其他终结点中重复使用它们。

连接工厂

对JMSConnectionFactoryBean(默认Bean名称为ConnectionFactory)的引用。

任务执行器

对JMS侦听器调用程序的SpringTaskExecutor的引用。

目标解析程序

对用于解析JMS目标实例的DestinationResolver策略的引用。

消息转换器

对将JMS消息转换为侦听器方法参数的MessageConverter策略的引用。默认值为SimpleMessageConverter

错误处理程序

对用于处理在MessageListener执行期间可能发生的任何未捕获异常的ErrorHandler策略的引用。

目标类型

此侦听器的JMS目标类型:队列主题耐久主题共享主题共享耐久主题。这可能会启用容器的pubSubDomain订阅耐久订阅共享属性。缺省值为Queue(禁用这三个属性)。

响应-目的地-类型

响应的JMS目的地类型:队列主题。缺省值是目标类型属性的值。

客户端ID

此监听程序容器的JMS客户端ID。当您使用持久订阅时,必须指定它。

缓存

JMS资源的缓存级别:连接会话消费者自动。默认情况下(AUTO),缓存级别实际上是消费者,除非已经指定了外部事务管理器 - ,在这种情况下,有效的缺省值将是NONE(假设Jakarta EE风格的事务管理,其中给定的ConnectionFactory是一个支持XA的池)。

确认

本机JMS确认模式:自动客户端dups-ok已处理Transated值激活本地事务会话。作为替代方法,您可以指定事务管理器属性,稍后将在表中进行说明。默认为自动

事务管理器

对外部PlatformTransactionManager(通常是基于XA的事务协调器,如Spring的JtaTransactionManager)的引用。如果未指定,则使用本机确认(请参阅确认属性)。

并发

为每个监听程序启动的并发会话数或使用者数。它可以是表示最大值的简单数字(例如,5),也可以是表示下限和上限的范围(例如,3-5)。请注意,指定的最小值只是一个提示,在运行时可能会被忽略。默认值为1。如果是主题监听器或队列顺序很重要,则应将并发数限制为1。考虑将其提高到一般队列。

预取

要加载到单个会话中的最大消息数。请注意,增加这个数字可能会导致并发消费者的资源匮乏。

接收超时

用于接收呼叫的超时时间(毫秒)。默认值为1000(一秒)。-1表示无超时。

后退

指定用于计算恢复尝试之间的间隔的退避实例。如果BackOffExecution实现返回BackOffExecution#Stop,则侦听器容器不会进一步尝试恢复。设置此属性时将忽略恢复间隔值。默认值为FixedBackOff,间隔为5000毫秒(即5秒)。

恢复间隔

指定恢复尝试之间的间隔,以毫秒为单位。它提供了一种便捷的方式来创建具有指定间隔的FixedBackOff。要获得更多恢复选项,请考虑改为指定回退实例。默认值为5000毫秒(即5秒)。

阶段

此容器应在其间启动和停止的生命周期阶段。该值越低,该容器开始的时间越早,停止的时间越晚。默认为Integer.MAX_VALUE,表示容器启动越晚,停止越快。

使用jms模式支持配置基于JCA的监听器容器非常相似,如下例所示:

<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>
            

下表介绍了JCA变体的可用配置选项:

Table 5. Attributes of the JMS <jca-listener-container/> element
Attribute Description

工厂ID

将此元素定义的设置公开为具有指定的idJmsListenerContainerFactory,以便可以在其他终结点中重复使用它们。

资源适配器

对JCAResourceAdapterBean的引用(默认Bean名称为resource ceAdapter)。

激活规范工厂

JmsActivationspecFactory的引用。缺省设置是自动检测JMS提供程序及其 DefaultJmsActivationSpecFactory).>ActionationSpec类(请参阅

目标解析程序

对用于解析JMS目标的DestinationResolver策略的引用。

消息转换器

对将JMS消息转换为侦听器方法参数的MessageConverter策略的引用。默认值为SimpleMessageConverter

目标类型

此侦听器的JMS目标类型:队列主题经久耐用共享主题。或sharedDurableTheme。这可能会启用容器的pubSubDomain订阅耐久订阅共享属性。缺省值为Queue(禁用这三个属性)。

响应-目的地-类型

响应的JMS目的地类型:队列主题。缺省值是目标类型属性的值。

客户端ID

此监听程序容器的JMS客户端ID。在使用持久订阅时需要指定它。

确认

本机JMS确认模式:自动客户端dups-ok已处理Transated值激活本地事务会话。或者,您可以指定后面介绍的事务管理器属性。默认为自动

事务管理器

对Spring<jakarta.transaction.TransactionManager>JtaTransactionManager或为每个传入消息启动XA事务的代码的引用。如果未指定,则使用本机确认(请参阅确认属性)。

并发

为每个监听程序启动的并发会话数或使用者数。它可以是表示最大值的简单数字(例如5),也可以是表示下限和上限的范围(例如3-5)。请注意,当您使用JCA侦听器容器时,指定的最小值只是一个提示,通常在运行时会被忽略。默认值为1。

预取

要加载到单个会话中的最大消息数。请注意,增加这个数字可能会导致并发消费者的资源匮乏。

3. JMX

Spring中的JMX(Java管理扩展)支持提供了一些功能,使您可以轻松、透明地将Spring应用程序集成到JMX基础设施中。

JMX?

本章不是对JMX的介绍。它不会试图解释您可能想要使用JMX的原因。如果您不熟悉JMX,请参阅本章末尾的更多参考资料

具体来说,Spring的JMX支持提供了四个核心特性:

  • 自动将任何Spring Bean注册为JMX MBean。

  • 一种灵活的机制,用于控制Bean的管理接口。

  • 通过远程JSR-160连接器声明公开MBean。

  • 本地和远程MBean资源的简单代理。

这些功能设计为在不将应用程序组件耦合到Spring或JMX接口和类的情况下工作。实际上,在很大程度上,您的应用程序类不需要知道Spring或JMX,就可以利用Spring JMX特性。

3.1. Exporting Your Beans to JMX

Spring的JMX框架中的核心类是MBeanExporter。这个类负责获取您的Spring Bean并将它们注册到JMXMBeanServer。例如,考虑以下类:

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();
    }
}

            

要将此Bean的属性和方法公开为MBean的属性和操作,可以在配置文件中配置MBeanExporter类的实例并传入该Bean,如下面的示例所示:

<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>
            

前面配置片段中的相关Bean定义是ExporterBean。Bean属性告诉MBeanExporter必须将哪些Bean导出到JMXMBeanServer。在默认配置中,BeanMap中每个条目的键被用作相应条目值引用的Bean的ObjectName。您可以更改此行为,如控制您的Bean的对象名称实例中所述。

使用此配置,testBeanBean作为ObjectNameBean:name=testBean1下的MBean公开。默认情况下,Bean的所有公共属性都公开为属性,所有公共方法(从对象类继承的方法除外)都公开为操作。

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

前面部分中显示的配置假定应用程序正在运行的环境中已经运行了一个(且只有一个)MBeanServer。在本例中,Spring尝试定位正在运行的MBeanServer,并将您的Bean注册到该服务器(如果有的话)。当您的应用程序在具有自己的MBeanServer的容器(如Tomcat或IBM WebSphere)中运行时,此行为非常有用。

但是,这种方法在独立环境中或在不提供MBeanServer的容器中运行时毫无用处。要解决这个问题,您可以通过将org.springframework.jmx.support.MBeanServerFactoryBean类的实例添加到您的配置中,以声明方式创建一个MBeanServer实例。还可以通过将MBeanExporter实例的服务器属性的值设置为由MBeanServerFactoryBean返回的MBeanServer值,以确保使用特定的MBeanServer,如下面的示例所示:

<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>
             

在前面的示例中,MBeanServer的一个实例由MBeanServerFactoryBean创建,并通过服务器属性提供给MBeanExporter。当您提供自己的MBeanServer实例时,MBeanExporter不会尝试查找正在运行的MBeanServer,而是使用提供的MBeanServer实例。要使其正常工作,您的类路径上必须有一个JMX实现。

3.1.2. Reusing an Existing MBeanServer

如果未指定服务器,MBeanExporter会尝试自动检测正在运行的MBeanServer。这适用于大多数环境,其中只使用一个MBeanServer实例。但是,当存在多个实例时,导出器可能会选择错误的服务器。在这种情况下,您应该使用MBeanServeragentID来指示要使用哪个实例,如下例所示:

<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>
             

对于现有MBeanServer具有通过查找方法检索的动态(或未知)agentID的平台或情况,您应该使用Factory-method,如下例所示:

<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

如果使用也配置为延迟初始化的MBeanExporter配置Bean,则MBeanExporter不会破坏此约定,并避免实例化该Bean。相反,它向MBeanServer注册了一个代理,并推迟从容器中获取Bean,直到对代理的第一次调用发生。

3.1.4. Automatic Registration of MBeans

通过MBeanExporter导出并且已经是有效的MBean的任何Bean都按原样注册到MBeanServer,而不需要来自Spring的进一步干预。通过将自动检测属性设置为true,可以使MBeanExporter自动检测MBean,如下面的示例所示:

<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"/>
             

在前面的示例中,名为Spring:mbean=true的Bean已经是一个有效的JMX MBean,并由Spring自动注册。默认情况下,自动检测用于JMX注册的Bean将其Bean名称用作对象名。您可以覆盖此行为,如控制您的Bean的对象名称实例中所述。

3.1.5. Controlling the Registration Behavior

考虑这样一个场景,一个SpringMBeanExporter试图使用对象名Bean:name=testBean1MBeanServer注册一个MBean。如果已经在同一对象名下注册了MBean实例,则默认行为是失败(并抛出InstanceAlreadyExistsException)。

您可以准确地控制当MBean注册到MBeanServer时会发生什么。当注册过程发现MBean已经在相同的对象名下注册时,Spring的JMX支持允许三种不同的注册行为来控制注册行为。下表总结了这些注册行为:

Table 6. Registration Behaviors
Registration behavior Explanation

FAIL_ON_EXISTING

这是默认的注册行为。如果MBean实例已在同一ObjectName下注册,则不会注册正在注册的MBean,并引发InstanceAlreadyExistsException。现有的MBean不受影响。

Ignore_Existing

如果MBean实例已在同一ObjectName下注册,则未注册正在注册的MBean。现有的MBean不受影响,不会引发异常。这在多个应用程序希望在共享的MBeanServer中共享一个公共MBean的设置中很有用。

Replace_Existing

如果MBean实例已经在同一ObjectName下注册,则取消注册先前注册的现有MBean,并在其位置注册新的MBean(新的MBean实际上替换了以前的实例)。

上表中的值被定义为RegistrationPolicy类上的枚举。如果要更改默认注册行为,则需要将MBeanExporter定义上的registrationPolicy属性的值设置为这些值之一。

以下示例显示如何从默认注册行为更改为REPLACE_EXISTING行为:

<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

3.2.1. Using the MBeanInfoAssembler Interface

在幕后,MBeanExporter委托org.springframework.jmx.export.assembler.MBeanInfoAssembler接口的实现,负责定义公开的每个Bean的管理接口。默认实现org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler,定义了一个公开所有公共属性和方法的管理接口(如前面几节中的示例所示)。Spring提供了两个额外的MBeanInfoAssembly接口实现,允许您通过使用源代码级别的元数据或任何任意接口来控制生成的管理接口。

3.2.2. Using Source-level Metadata: Java Annotations

通过使用MetadataMBeanInfoAssembly,您可以使用源代码级别的元数据来定义Bean的管理接口。元数据的读取由org.springframework.jmx.export.metadata.JmxAttributeSource接口封装。Spring JMX提供了一个使用Java注解的默认实现,即org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource.必须使用JmxAttributeSource接口的实现实例配置MetadataMBeanInfoAssembly,才能使其正常工作(没有默认设置)。

要标记一个要导出到JMX的Bean,您应该用ManagedResource注释来注释该Bean类。您必须使用托管操作注释将希望公开的每个方法标记为操作,并使用托管属性注释标记希望公开的每个属性。标记属性时,可以省略getter或setter的注释,以分别创建只写或只读属性。

A ManagedResource-annotated bean must be public, as must the methods exposing an operation or an attribute.

下面的示例显示了我们在创建MBeanServer中使用的JmxTestBean类的注释版本:

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(); } } 
             

在前面的示例中,您可以看到JmxTestBean类使用托管资源注释进行了标记,并且该托管资源注释配置了一组属性。这些属性可用于配置由MBeanExporter生成的MBean的各个方面,稍后将在源代码级别的元数据类型中进行更详细的说明。

agename属性都使用托管属性批注,但对于age属性,只标记了getter。这会导致这两个属性都作为属性包含在管理界面中,但age属性是只读的。

最后,Add(int,int)方法用托管操作属性标记,而dontExposeMe()方法没有标记。这导致当您使用MetadataMBeanInfoAssembly时,管理接口只包含一个操作(Add(int,int))。

以下配置显示了如何将MBeanExporter配置为使用MetadataMBeanInfoAssembly

<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>
             

在前面的示例中,已使用AnnotationJmxAttributeSource类的实例配置了MetadataMBeanInfoAssemblyBean,并通过Assembly er属性将其传递给MBeanExporter。这就是为您的Spring公开的MBean利用元数据驱动的管理接口所需的全部功能。

3.2.3. Source-level Metadata Types

下表描述了可在Spring JMX中使用的源级元数据类型:

Table 7. Source-level metadata types
Purpose Annotation Annotation Type

的所有实例标记为JMX托管资源。

@托管资源

班级

将方法标记为JMX操作。

@托管操作

方法

将一个getter或setter标记为JMX属性的一半。

@托管属性

方法(仅限getters和setters)

定义操作参数的说明。

@管理操作参数@管理操作参数

方法

下表介绍了可用于这些源级元数据类型的配置参数:

Table 8. Source-level metadata parameters
Parameter Description Applies to

对象名称

MetadataNamingStrategy用于确定托管资源的对象名称

托管资源

说明

设置资源、属性或操作的友好描述。

托管资源托管属性托管操作托管操作参数

CurrencyTimeLimit

设置CurrencyTimeLimit描述符字段的值。

托管资源托管属性

defaultValue

设置defaultValue描述符字段的值。

托管属性

日志

设置日志描述符字段的值。

托管资源

日志文件

设置日志文件描述符字段的值。

托管资源

持久化策略

设置PersistPolicy描述符字段的值。

托管资源

持久期

设置Period描述符字段的值。

托管资源

持久化位置

设置持久化位置描述符字段的值。

托管资源

持久化名称

设置持久性名称描述符字段的值。

托管资源

名称

设置操作参数的显示名称。

托管操作参数

索引

设置操作参数的索引。

托管操作参数

3.2.4. Using the AutodetectCapableMBeanInfoAssembler Interface

为了进一步简化配置,Spring包含了AutodetectCapableMBeanInfoAssembler接口,该接口扩展了MBeanInfoAssembly接口,以添加对MBean资源自动检测的支持。如果您使用AutodetectCapableMBeanInfoAssembler,的实例配置MBeanExporter,它将被允许“投票”是否包含暴露给JMX的Bean。

AutoDetectCapableMBeanInfo接口的唯一实现是MetadataMBeanInfoAssembly,它投票选择包含任何标记有ManagedResource属性的Bean。本例中的默认方法是使用Bean名称作为对象名称,这将产生类似于以下内容的配置:

<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>
             

请注意,在前面的配置中,没有将任何Bean传递给MBeanExporter。但是,JmxTestBean仍被注册,因为它用托管资源属性进行标记,并且MetadataMBeanInfoAssembly检测到这一点并投票将其包括在内。这种方法的唯一问题是JmxTestBean的名称现在具有业务意义。您可以通过更改在控制Bean的对象名称实例中定义的对象名称创建的默认行为来解决此问题。

3.2.5. Defining Management Interfaces by Using Java Interfaces

除了MetadataMBeanInfoAssembly,Spring还包括InterfaceBasedMBeanInfoAssembler,,它允许您约束基于接口集合中定义的方法集公开的方法和属性。

尽管公开MBean的标准机制是使用接口和简单的命名方案,但InterfaceBasedMBeanInfoAssembly通过消除命名约定的需要扩展了这一功能,允许您使用多个接口,并消除了您的Bean实现MBean接口的需要。

考虑以下接口,该接口用于为我们前面展示的JmxTestBean类定义管理接口:

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();

}

             

该接口定义了作为JMX MBean上的操作和属性公开的方法和属性。以下代码显示如何配置Spring JMX以使用此接口作为管理接口的定义:

<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>
             

在前面的示例中,InterfaceBasedMBeanInfoAssembly被配置为在构造任何Bean的管理接口时使用IJmxTestBean接口。重要的是要理解,由InterfaceBasedMBeanInfoAssembly处理的Bean并不是实现用于生成JMX管理接口的接口所必需的。

在前面的案例中,IJmxTestBean接口用于构造所有Bean的所有管理接口。在许多情况下,这不是所需的行为,您可能希望为不同的Bean使用不同的接口。在本例中,您可以通过interfaceMappings属性传递InterfaceBasedMBeanInfoAssembly一个Properties实例,其中每个条目的键是Bean名称,每个条目的值是用于该Bean的接口名称的逗号分隔列表。

如果没有通过ManagedInterFacesinterfaceMappings属性指定管理接口,则InterfaceBasedMBeanInfoAssembly会反映到Bean上,并使用该Bean实现的所有接口来创建管理接口。

3.2.6. Using MethodNameBasedMBeanInfoAssembler

MethodNameBasedMBeanInfoAssembler允许您指定作为属性和操作公开给JMX的方法名列表。以下代码显示了一个示例配置:

<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>
             

在前面的示例中,您可以看到AddmyOperation方法被公开为JMX操作,而getName()setName(字符串)getAge()方法被公开为JMX属性的相应部分。在前面的代码中,方法映射应用于公开给JMX的Bean。要逐个Bean地控制方法公开,您可以使用MethodNameMBeanInfoAssemblymethod Mappings属性将Bean名称映射到方法名称列表。

3.3. Controlling ObjectName Instances for Your Beans

在幕后,MBeanExporter委托ObjectNamingStrategy的实现来获取它注册的每个Bean的ObjectName实例。默认情况下,默认实现KeyNamingStrategy使用BeanMap的键作为对象名。此外,KeyNamingStrategy可以将Bean映射的键映射到属性文件中的条目,以解析对象名称。除了KeyNamingStrategy之外,Spring还提供了另外两个ObjectNamingStrategy实现:IdentityNamingStrategy(它基于Bean的JVM标识构建ObjectName)和MetadataNamingStrategy(它使用源代码级别的元数据来获取对象名称)。

3.3.1. Reading ObjectName Instances from Properties

您可以配置您自己的KeyNamingStrategy实例,并将其配置为从Properties实例读取ObjectName实例,而不是使用Bean密钥。KeyNamingStrategy尝试使用与Bean键对应的键在Properties中查找条目。如果未找到条目,或者Properties实例为NULL,则使用Bean密钥本身。

以下代码显示了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>
             

上面的示例使用Properties实例配置KeyNamingStrategy的实例,该实例是由映射属性定义的Properties实例和位于mappings属性定义的路径中的属性文件合并而成的。在此配置中,testBeanBean被赋予ObjectNameBean:name=testBean1,因为这是Properties实例中具有与Bean关键字对应的关键字的条目。

如果在Properties实例中找不到任何条目,则将Bean密钥名称用作对象名称

3.3.2. Using MetadataNamingStrategy

MetadataNamingStrategy使用每个Bean的托管资源属性的对象名称属性创建对象名称。以下代码显示了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>
             

如果没有为<[fully-qualified-package-name]:type=[short-classname],>托管资源属性提供对象名称,则使用以下格式创建对象名称:Bean名称=[Bean名称]。例如,为以下Bean生成的ObjectName将是com.例如:type=MyClass,name=myBean

<bean id="myBean" class="com.example.MyClass"/>
             

3.3.3. Configuring Annotation-based MBean Export

如果您更喜欢使用基于注释的方法来定义管理接口,可以使用MBeanExporter的一个方便的子类:AnnotationMBeanExporter。在定义这个子类的实例时,您不再需要namingStrategy汇编程序属性源配置,因为它总是使用标准的基于Java注释的元数据(也总是启用自动检测)。事实上,@EnableMBeanExport@configuration注释支持更简单的语法,而不是定义MBeanExporterBean,如下面的示例所示:

@Configuration
@EnableMBeanExport
public class AppConfig {

}

             

如果您更喜欢基于XML的配置,<;Context:mean-EXPORT/>;元素具有相同的用途,如下面的清单所示:

<context:mbean-export/>
             

如果需要,您可以提供对特定MBean服务器的引用,并且DefaultDomain属性(AnnotationMBeanExporter的属性)接受生成的MBean对象名称域的备用值。它用于替换上一节中关于MetadataNamingStrategy的全限定包名,如下例所示:

@EnableMBeanExport(server="myMBeanServer", defaultDomain="myDomain")
@Configuration
ContextConfiguration {

}

             

下面的示例显示了前面基于注释的示例的XML等效项:

<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

对于远程访问,Spring JMX模块在org.springFramework.jmx.support包中提供了两个FactoryBean实现,用于创建服务器端和客户端连接器。

3.4.1. Server-side Connectors

要让Spring JMX创建、启动和公开JSR-160JMXConnectorServer,您可以使用以下配置:

<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean"/>
             

默认情况下,ConnectorServerFactoryBean创建绑定到service:jmx:jmxmp://localhost:9875.的JMXConnectorServer因此,serverConnectorBean通过本地主机端口9875上的JMXMP协议向客户端公开本地MBeanServer。请注意,JSR160规范将JMXMP协议标记为可选。目前,主要的开源JMX实现MX4J和JDK提供的实现不支持JMXMP。

要指定另一个URL并将JMXConnectorServer本身注册到MBeanServer,您可以分别使用serviceUrlObjectName属性,如下例所示:

<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>
             

如果设置了对象名属性,则Spring会自动向对象名下的MBeanServer注册您的连接器。下面的示例显示了在创建JMXConnector时可以传递给ConnectorServerFactoryBean的完整参数集:

<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>
             

请注意,当您使用基于RMI的连接器时,您需要启动查找服务(tname ervrmiRegister)才能完成名称注册。

3.4.2. Client-side Connectors

要创建到启用JSR160的远程<代码>MBeanServerConnection的MBeanServerConnection,您可以使用MBeanServerConnectionFactoryBean,,如下例所示:

<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允许扩展客户端和服务器之间的通信方式。前面几节中显示的示例使用JSR-160规范(IIOP和JRMP)和(可选的)JMXMP所要求的基于RMI的强制实现。通过使用其他提供程序或JMX实现(如MX4J),您可以利用简单HTTP或SSL上的SOAP或Hessian等协议,如以下示例所示:

<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>
             

在前面的示例中,我们使用了MX4J 3.0.0。有关更多信息,请参阅官方MX4J文档。

3.5. Accessing MBeans through Proxies

Spring JMX允许您创建代理,将调用重新路由到在本地或远程MBeanServer中注册的MBean。这些代理为您提供了一个标准的Java接口,您可以通过该接口与MBean进行交互。以下代码显示如何为在本地MBeanServer中运行的MBean配置代理:

<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>
            

在前面的示例中,您可以看到为在Bean:name=testBeanObjectName下注册的MBean创建了一个代理。代理实现的接口集由proxyInterFaces属性控制,将这些接口上的方法和属性映射到MBean上的操作和属性的规则与InterfaceBasedMBeanInfoAssembler.使用的规则相同

MBeanProxyFactoryBean可以创建可通过MBeanServerConnection访问的任何MBean的代理。默认情况下,本地MBeanServer位于并使用,但您可以覆盖它并提供指向远程MBeanServerMBeanServerConnection,以满足指向远程MBean的代理:

<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>
            

在前面的示例中,我们创建了指向使用MBeanServerConnectionFactoryBean.的远程计算机的<代码>MBeanServerConnection 然后,此MBeanServerConnection通过服务器属性传递给MBeanProxyFactoryBean。创建的代理通过这个MBeanServerConnection将所有调用转发到MBeanServer

3.6. Notifications

Spring的JMX产品包括对JMX通知的全面支持。

3.6.1. Registering Listeners for Notifications

Spring的JMX支持使得向任意数量的MBean(包括由Spring的MBeanExporter导出的MBean和通过某种其他机制注册的MBean)注册任意数量的NotificationListeners变得很容易。例如,考虑这样一个场景,每次目标MBean的属性发生变化时,都希望得到通知(通过通知)。以下示例将通知写入控制台:

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()); } } 
             

下面的示例将ConsoleLoggingNotificationListener(在前面的示例中定义)添加到通知ListenerMappings

<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>
             

有了前面的配置,每次从目标MBean(<代码>Bean:NAME=ConsoleLoggingNotificationListenerBean1)广播JMX通知时,通过通知ListenerMappings属性注册为侦听器的Bean就会收到通知。然后,代码Bean可以采取它认为适当的任何操作来响应<ConsoleLoggingNotificationListener>通知。

您还可以使用纯Bean名称作为导出的Bean和监听程序之间的链接,如下例所示:

<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>
             

如果要为封闭的MBeanExporter导出的所有Bean注册一个NotificationListener实例,则可以使用特殊通配符(*)作为通知ListenerMappings属性映射中条目的键,如下面的示例所示:

<property name="notificationListenerMappings">
    <map>
        <entry key="*">
            <bean class="com.example.ConsoleLoggingNotificationListener"/>
        </entry>
    </map>
</property>
             

如果您需要进行相反的操作(即,针对一个MBean注册许多不同的侦听器),则必须改用NotifationListenersList属性(优先于通知ListenerMappings属性)。这一次,我们没有为单个MBean配置NotificationListener,而是配置了NotificationListenerBean实例。NotificationListenerBean封装了NotificationListener对象名称(或对象名称),它将在MBeanServer中注册。NotificationListenerBean还封装了许多其他属性,如NotificationFilter和可在高级JMX通知方案中使用的任意Handback对象。

使用NotificationListenerBean实例时的配置与前面提供的配置没有太大差别,如下例所示:

<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>
             

前面的示例等同于第一个通知示例。然后,假设我们希望在每次引发通知时都获得一个Handback对象,并且我们还希望通过提供NotificationFilter来过滤掉无关的通知。下面的示例实现了这些目标:

<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>
             

(有关什么是Handback对象以及什么是NotificationFilter的完整讨论,请参阅JMX规范(1.2)中标题为“JMX通知模型”的部分。)

3.6.2. Publishing Notifications

Spring不仅支持注册接收通知,还支持发布通知

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.

SpringJMX通知发布支持中的关键接口是NotificationPublisher接口(在org.springframework.jmx.export.notification包中定义)。任何要通过MBeanExporter实例作为MBean导出的Bean都可以实现相关的NotificationPublisherAware接口以访问NotificationPublisher实例。NotificationPublisherAware接口通过一个简单的setter方法向实现Bean提供NotificationPublisher的实例,然后该Bean可以使用该实例发布通知

NotificationPublisher接口的Java代码中所述,通过<代码>通知发布程序 机制发布事件的托管Bean不负责通知侦听器的状态管理。Spring的JMX支持负责处理所有的JMX基础设施问题。作为应用程序开发人员,您所需要做的就是实现NotificationPublisherAware接口,并使用提供的NotificationPublisher实例开始发布事件。请注意,NotificationPublisher是在托管Bean注册到MBeanServer之后设置的。

使用NotificationPublisher实例非常简单。创建一个JMXNotification实例(或相应的Notification子类的实例),用与要发布的事件相关的数据填充通知,并调用NotificationPublisher实例上的sendNotification(Notification),传入Notification

在下面的示例中,JmxTestBean的导出实例在每次调用Add(int,int)操作时发布一个NotificationEvent

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; } } 
             

NotificationPublisher接口和使其正常工作的机制是Spring的JMX支持的较好特性之一。然而,它确实带来了将类耦合到Spring和JMX的代价。一如既往,这里的建议是务实。如果您需要NotificationPublisher提供的功能,并且您可以接受与Spring和JMX的耦合,那么就这样做。

3.7. Further Resources

本节包含指向有关JMX的更多资源的链接:

4. Email

本节介绍如何使用Spring框架发送电子邮件。

Library dependencies

以下JAR需要位于应用程序的类路径中,才能使用Spring框架的电子邮件库:

  • Java Mail/Jakarta Mail 1.6库

这个库可以在Web - 上免费获得,例如,在Maven Central中以com.sun.mail:jakarta.mail的形式提供。请确保使用最新的1.6.x版本,而不是Jakarta Mail 2.0(带有不同的包命名空间)。

Spring框架提供了一个有用的实用程序库,用于发送电子邮件,使您不受底层邮件系统细节的影响,并负责代表客户端进行低级资源处理。

org.springmework.mail包是用于Spring框架的电子邮件支持的根级包。发送电子邮件的中央界面是MailSender界面。一个简单的值对象封装了简单邮件的属性,例如fromto(以及许多其他属性)是SimpleMailMessage类。该包还包含已检查异常的层次结构,这些异常提供了比较低级别的邮件系统异常更高级别的抽象,根异常是MailException。有关丰富邮件异常层次结构的更多信息,请参阅javadoc。

org.springframework.mail.javamail.JavaMailSender接口向MailSender接口(它继承自该接口)添加了专门的Java Mail特性,例如MIME消息支持。Java MailSender还提供了一个名为org.springframework.mail.javamail.MimeMessagePreparator的回调接口,用于准备MimeMessage

4.1. Usage

假设我们有一个名为OrderManager的业务接口,如下例所示:

public interface OrderManager {

    void placeOrder(Order order);

}

            

进一步假设我们有一个需求,说明需要生成一封带有订单号的电子邮件消息,并将其发送给下相关订单的客户。

4.1.1. Basic MailSender and SimpleMailMessage Usage

以下示例说明如何在有人下单时使用MailSenderSimpleMailMessage发送电子邮件:

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()); } } } 
             

下面的示例显示了前面代码的Bean定义:

<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

本节介绍OrderManager的另一个实现,该实现使用MimeMessagePreparator回调接口。在下面的示例中,mailSender属性的类型为Java MailSender,因此我们可以使用Java MailMimeMessage类:

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.

Spring框架的邮件支持随标准的JavaMail实现一起提供。有关更多信息,请参阅相关的javadoc。

4.2. Using the JavaMail MimeMessageHelper

在处理JavaMail消息时非常方便的一个类是org.springframework.mail.javamail.MimeMessageHelper,,它使您不必使用冗长的JavaMail API。使用MimeMessageHelper创建MimeMessage非常容易,如下面的示例所示:

// 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

多部分电子邮件同时支持附件和内联资源。内联资源的示例包括要在邮件中使用但不希望显示为附件的图像或样式表。

Attachments

下面的示例说明如何使用MimeMessageHelper发送带有单个JPEG图像附件的电子邮件:

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

下面的示例显示如何使用MimeMessageHelper发送带有内联图像的电子邮件:

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

前面几节所示示例中的代码通过使用方法调用(如Message.setText(..))显式地创建了电子邮件消息的内容。这对于简单的情况很好,在上述示例的上下文中也是可以的,其目的是向您展示API的最基本的知识。

然而,在您的典型企业应用程序中,开发人员通常不会使用前面所示的方法来创建电子邮件内容,原因如下:

  • 用Java代码创建基于HTML的电子邮件内容既繁琐又容易出错。

  • 显示逻辑和业务逻辑之间没有明确的划分。

  • 更改电子邮件内容的显示结构需要编写Java代码、重新编译、重新部署等等。

通常,解决这些问题的方法是使用模板库(如FreeMarker)来定义电子邮件内容的显示结构。这使得您的代码只负责创建要在电子邮件模板中呈现的数据并发送电子邮件。当您的电子邮件消息的内容变得稍微复杂时,这绝对是一种最佳实践,而且,有了Spring框架对FreeMarker的支持类,这变得非常容易做到。

5. Task Execution and Scheduling

Spring框架分别使用TaskExecutorTaskScheduler接口为任务的异步执行和调度提供抽象。Spring还提供了这些接口的实现,这些接口支持应用服务器环境中的线程池或委托给CommonJ。最终,在公共接口后面使用这些实现消除了Java SE 5、Java SE 6和Jakarta EE环境之间的差异。

Spring还提供集成类来支持使用计时器 (从1.3开始是JDK的一部分)和Quartz Scheduler(https://www.quartz-scheduler.org/).)的调度您可以通过使用FactoryBean以及对Timer触发器实例的可选引用来设置这两个调度程序。此外,Quartz Scheduler和Timer都有一个方便的类,它允许您调用现有目标对象的方法(类似于正常的MethodInvokingFactoryBean操作)。

5.1. The Spring TaskExecutor Abstraction

Executor是线程池概念的JDK名称。之所以命名为“Executor”,是因为不能保证底层实现实际上是池。执行器可以是单线程的,甚至可以是同步的。Spring的抽象隐藏了Java SE和Jakarta EE环境之间的实现细节。

Spring的TaskExecutor接口与java.util.concurent.Executor接口相同。事实上,最初,它存在的主要原因是为了在使用线程池时抽象出对Java 5的需求。该接口有一个方法(Execute(Runnable TASK)),该方法根据线程池的语义和配置接受要执行的任务。

TaskExecutor最初是为了在需要的地方为其他Spring组件提供线程池的抽象。ApplicationEventMulticaster、JMS的AbstractMessageListenerContainer,和Quartz集成等组件都使用TaskExecutor抽象来池化线程。但是,如果您的Bean需要线程池行为,您也可以使用此抽象来满足您自己的需要。

5.1.1. TaskExecutor Types

Spring包括许多预构建的TaskExecutor实现。很可能,您永远不需要实现您自己的。Spring提供的变体如下:

  • SyncTaskExecutor:此实现不异步运行调用。相反,每次调用都发生在调用线程中。它主要用于不需要多线程的情况,例如在简单的测试用例中。

  • SimpleAsyncTaskExecutor:此实现不重用任何线程。相反,它为每个调用启动一个新线程。但是,它确实支持一个并发限制,该限制会阻止任何超过限制的调用,直到一个槽被释放。如果您正在寻找真正的池化,请参阅本列表后面的ThreadPoolTaskExecutor

  • ConCurrentTaskExecutor:此实现是java.util.conCurrent.Executor实例的适配器。还有一种替代方法(ThreadPoolTaskExecutor)将Executor配置参数公开为Bean属性。很少需要直接使用ConCurrentTaskExecutor。但是,如果ThreadPoolTaskExecutor不能满足您的需要,ConcurentTaskExecutor是一个替代方案。

  • ThreadPoolTaskExecutor:这个实现是最常用的。它公开用于配置代码的Bean属性,并将其包装在<java.util.concurrent.ThreadPoolExecutor>TaskExecutor中。如果您需要适应不同类型的java.util.concurrent.Executor,我们建议您改用ConCurrentTaskExecutor

  • DefaultManagedTaskExecutor:此实现在兼容JSR-236的运行时环境(如Jakarta EE应用服务器)中使用JNDI获取的ManagedExecutorService,为此取代了CommonJ WorkManager。

5.1.2. Using a TaskExecutor

Spring的TaskExecutor实现用作简单的JavaBean。在下面的示例中,我们定义了一个使用ThreadPoolTaskExecutor来异步打印出一组消息的Bean:

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)); } } } 
             

如您所见,您可以将Runnable添加到队列中,而不是从池中检索线程并自己执行它。然后TaskExecutor使用其内部规则来决定任务何时运行。

要配置TaskExecutor使用的规则,我们公开简单的Bean属性:

<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

除了TaskExecutor抽象之外,Spring3.0还引入了一个TaskScheduler,它具有多种方法来调度任务在将来某个时候运行。下面的清单显示了TaskScheduler接口定义:

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);

            

最简单的方法是名为Schedule的方法,它只接受RunnableInstant。这会导致任务在指定时间之后运行一次。所有其他方法都能够安排任务重复运行。固定速率和固定延迟方法用于简单的周期性执行,但接受触发器的方法要灵活得多。

5.2.1. Trigger Interface

Trigger接口基本上是受JSR-236的启发,而JSR-236在Spring3.0之前还没有正式实现。触发器的基本思想是,可以根据过去的执行结果甚至任意条件来确定执行时间。如果这些确定确实考虑了前一次执行的结果,则该信息在触发器上下文中可用。触发器接口本身非常简单,如下面的清单所示:

public interface Trigger {

    Date nextExecutionTime(TriggerContext triggerContext);
}

             

触发器上下文是最重要的部分。它封装了所有相关数据,如果需要,可以在将来进行扩展。TriggerContext是一个接口(默认使用SimpleTriggerContext实现)。下面的清单显示了触发器实现的可用方法。

public interface TriggerContext {

    Date lastScheduledExecutionTime();

    Date lastActualExecutionTime();

    Date lastCompletionTime();
}

             

5.2.2. Trigger Implementations

Spring提供了触发器接口的两个实现。最有趣的是CronTrigger。它支持基于cron表达式的任务调度。例如,以下任务计划在每小时15分钟后运行,但仅在工作日的朝九晚五的“营业时间”内运行:

scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));

             

另一个实现是接受固定周期的PeriodicTrigger、可选的初始延迟值,以及指示该周期应该被解释为固定速率还是固定延迟的布尔值。由于TaskScheduler接口已经定义了以固定速率或固定延迟调度任务的方法,因此应尽可能直接使用这些方法。PeriodicTrigger实现的价值在于,您可以在依赖触发器抽象的组件中使用它。例如,允许周期性触发器、基于cron的触发器甚至定制触发器实现互换使用可能很方便。这样的组件可以利用依赖注入,这样您就可以在外部配置这样的触发器,因此可以很容易地修改或扩展它们。

5.2.3. TaskScheduler implementations

与Spring的TaskExecutor抽象一样,TaskScheduler安排的主要好处是将应用程序的调度需求与部署环境分离。当部署到不应由应用程序本身直接创建线程的应用程序服务器环境时,此抽象级别尤其相关。对于这样的场景,Spring提供了一个TimerManager TaskScheduler,它委托WebLogic或WebSphere上的CommonJTimerManager,以及一个更新的DefaultManagedTaskScheduler,它委托Jakarta EE环境中的JSR-236托管ScheduledExecutorService。两者通常都配置了JNDI查找。

当不需要外部线程管理时,一个更简单的替代方案是在应用程序中进行本地ScheduledExecutorService设置,可以通过Spring的ConCurrentTaskScheduler进行调整。为了方便起见,Spring还提供了ThreadPoolTaskScheduler,它在内部委托ScheduledExecutorService按照ThreadPoolTaskExecutor提供公共的Bean风格的配置。这些变体非常适合宽松应用服务器环境中的本地嵌入线程池设置,尤其是在Tomcat和Jetty上的 - 。

5.3. Annotation Support for Scheduling and Asynchronous Execution

Spring为任务调度和异步方法执行提供了注释支持。

5.3.1. Enable Scheduling Annotations

要启用对@Scheduled@async批注的支持,您可以将@EnableScheduling@EnableAsync添加到您的一个@Configuration类中,如下面的示例所示:

@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}

             

您可以为您的应用程序挑选相关的注释。例如,如果您只需要支持@Scheduled,则可以省略@EnableAsync。对于更细粒度的控制,您可以另外实现SchedulingConfigurer接口、AsyncConfigurer接口,或者同时实现这两个接口。有关详细信息,请参阅SchedulingConfigurer<代码>AsyncConfigurer

如果您更喜欢XML配置,可以使用<;TASK:Annotation-Driven>;元素,如下例所示:

<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="5"/>
<task:scheduler id="myScheduler" pool-size="10"/>
             

注意,在前面的XML中,提供了一个执行器引用来处理那些对应于带有@async注释的方法的任务,并且提供了一个调度器引用来管理那些带有@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

您可以将@Scheduled注释与触发元数据一起添加到方法中。例如,以下方法以固定延迟每五秒(5000毫秒)调用一次,这意味着该周期是从前一次调用的完成时间开始测量的。

@Scheduled(fixedDelay = 5000)
public void doSomething() {
    // something that should run periodically
}

             

默认情况下,毫秒将用作固定延迟、固定速率和初始延迟值的时间单位。如果您想使用不同的时间单位,如秒或分钟,您可以通过@Scheduled中的timeUnit属性进行配置。

例如,前面的示例也可以按如下方式编写。

@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)
public void doSomething() {
    // something that should run periodically
}

                  

如果需要固定速率执行,可以在注释中使用fix edrate属性。以下方法每五秒调用一次(在每次调用的连续开始时间之间测量)。

@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS)
public void doSomething() {
    // something that should run periodically
}

             

对于固定延迟和固定速率任务,您可以通过指示在第一次执行方法之前等待的时间量来指定初始延迟,如下面的fix edrate示例所示。

@Scheduled(initialDelay = 1000, fixedRate = 5000)
public void doSomething() {
    // something that should run periodically
}

             

如果简单的定期调度不够有表现力,您可以提供cron表达式。以下示例仅在工作日运行:

@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.

请注意,要调度的方法必须具有无效的返回,并且不能接受任何参数。如果该方法需要与应用程序上下文中的其他对象交互,则通常会通过依赖项注入提供这些对象。

从Spring Framework4.3开始,任何作用域的Bean都支持@Scheduled方法。

确保您没有在运行时初始化同一个@Scheduled批注类的多个实例,除非您确实希望安排对每个此类实例的回调。与此相关的是,请确保不要对使用@Scheduled注释并在容器中注册为常规的SpringBean的Bean类使用@Configable。否则,您将获得双重初始化(一次通过容器,一次通过@Configable方面),结果是每个@Scheduled方法被调用两次。

5.3.3. The @Async annotation

您可以在方法上提供@async注释,以便异步调用该方法。换句话说,调用方在调用后立即返回,而方法的实际执行发生在已提交给SpringTaskExecutor的任务中。在最简单的情况下,您可以将注释应用于返回void的方法,如下面的示例所示:

@Async
void doSomething() {
    // this will be run asynchronously
}

             

与使用@Scheduled注释的方法不同,这些方法可以使用参数,因为它们是由调用者在运行时以“正常”方式调用的,而不是从容器管理的计划任务调用的。例如,以下代码是@async批注的合法应用:

@Async
void doSomething(String s) {
    // this will be run asynchronously
}

             

即使返回值的方法也可以被异步调用。但是,此类方法需要有Future类型的返回值。这仍然提供了异步执行的好处,因此调用者可以在对Future调用Get()之前执行其他任务。以下示例显示如何在返回值的方法上使用@async

@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.

@async不能和@PostConstruct这样的生命周期回调一起使用。要异步初始化Spring Bean,您当前必须使用单独的初始化Spring Bean,然后在目标上调用带注释的@async方法,如下面的示例所示:

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

@Async("otherExecutor")
void doSomething(String s) {
    // this will be run asynchronously by "otherExecutor"
}

             

在本例中,“therExecutor”可以是Spring容器中任何ExecutorBean的名称,也可以是与任何Executor关联的限定符的名称(例如,由<;限定符>;元素或Spring的@限定符注释指定)。

5.3.5. Exception Management with @Async

@async方法具有Future类型的返回值时,很容易管理在方法执行期间引发的异常,因为当对Future结果调用Get时会引发此异常。但是,对于void返回类型,异常未被捕获且无法传输。您可以提供AsyncUncaughtExceptionHandler来处理此类异常。以下示例显示了如何执行此操作:

public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        // handle exception
    }
}

             

默认情况下,仅记录异常。您可以使用AsyncConfigurer<;TASK:Annotation-Driven/>;XML元素定义自定义AsyncUncaughtExceptionHandler

5.4. The task Namespace

从3.0版开始,Spring包含一个用于配置TaskExecutorTaskScheduler实例的XML命名空间。它还提供了一种方便的方法来配置要使用触发器调度的任务。

5.4.1. The 'scheduler' Element

以下元素创建具有指定线程池大小的ThreadPoolTaskScheduler实例:

<task:scheduler id="scheduler" pool-size="10"/>
             

id属性提供的值用作池中线程名称的前缀。Scheduler元素相对简单。如果不提供池大小属性,则默认线程池只有一个线程。调度器没有其他配置选项。

5.4.2. The executor Element

下面创建一个ThreadPoolTaskExecutor实例:

<task:executor id="executor" pool-size="10"/>
             

上一节中所示的调度程序一样,为id属性提供的值用作池中线程名称的前缀。就池大小而言,Executor元素比Scheduler元素支持更多的配置选项。首先,ThreadPoolTaskExecutor的线程池本身更易于配置。执行器的线程池可以具有不同的核心值和最大大小值,而不是只有一个大小。如果您提供单个值,则Executor具有固定大小的线程池(核心大小和最大大小相同)。但是,Executor元素的池大小属性也接受min-max形式的范围。下面的示例设置的最小值为5,最大值为25

<task:executor id="executorWithPoolSizeRange" pool-size="5-25" queue-capacity="100"/>
             

在前面的配置中,还提供了Queue-Capacity值。线程池的配置也应该根据执行器的队列容量来考虑。有关池大小和队列容量之间关系的完整描述,请参阅ThreadPoolExecutor的文档。其主要思想是,当提交任务时,如果活动线程的数量当前小于核心大小,则执行器首先尝试使用空闲线程。如果已达到核心大小,则只要尚未达到其容量,就会将任务添加到队列中。只有在达到队列的容量时,执行器才会创建一个超出核心大小的新线程。如果也已达到最大大小,则执行器拒绝该任务。

默认情况下,队列是无界的,但这很少是所需的配置,因为如果在所有池线程繁忙的情况下向该队列添加足够多的任务,则可能会导致OutOfMemoyErrors。此外,如果队列是无界的,则最大大小根本不起作用。由于执行器总是在创建超出核心大小的新线程之前尝试队列,因此队列必须具有有限的容量,线程池才能增长到超出核心大小(这就是为什么在使用无限队列时,固定大小的池是唯一合理的情况)。

考虑如上所述,当任务被拒绝时的情况。默认情况下,当任务被拒绝时,线程池执行器抛出TaskRejectedException。但是,拒绝策略实际上是可配置的。使用默认拒绝策略时抛出异常,该策略是AbortPolicy实现。对于在高负载下可以跳过某些任务的应用,您可以配置DiscardPolicyDiscardOldestPolicy。对于需要在高负载下限制提交任务的应用程序来说,另一个很好的选择是Celler RunsPolicy。该策略不是引发异常或丢弃任务,而是强制正在调用Submit方法线程运行任务本身。其想法是这样的调用者在运行该任务时很忙,并且不能立即提交其他任务。因此,它提供了一种简单的方法来限制传入负载,同时维护线程池和队列的限制。通常,这允许执行器“追上”它正在处理的任务,从而释放队列或池中的一些容量,或者两者都释放。您可以从Executor元素上的rejection-policy属性可用的值的枚举中选择这些选项中的任何一个。

下面的示例显示了一个Executor元素,该元素具有多个用于指定各种行为的属性:

<task:executor id="executorWithCallerRunsPolicy" pool-size="5-25" queue-capacity="100" rejection-policy="CALLER_RUNS"/>
             

最后,Keep-Alive设置确定线程在停止之前可以保持空闲的时间限制(以秒为单位)。如果池中当前的线程数量超过核心数量,则在没有处理任务的情况下等待了这段时间后,会停止多余的线程。时间值为零会导致多余的线程在执行任务后立即停止,而不会在任务队列中保留后续工作。下面的示例将Keep-Alive值设置为两分钟:

<task:executor id="executorWithKeepAlive" pool-size="5-25" keep-alive="120"/>
             

5.4.3. The 'scheduled-tasks' Element

Spring的任务命名空间最强大的功能是支持将任务配置为在Spring应用程序上下文中调度。这遵循了类似于Spring中的其他“方法调用器”的方法,例如JMS命名空间提供的用于配置消息驱动POJO的方法。基本上,ref属性可以指向任何由Spring管理的对象,而方法属性提供要在该对象上调用的方法的名称。下面的清单显示了一个简单的示例:

<task:scheduled-tasks scheduler="myScheduler">
    <task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/>
</task:scheduled-tasks>

<task:scheduler id="myScheduler" pool-size="10"/>
             

调度器由外部元素引用,每个单独的任务都包括其触发元数据的配置。在前面的示例中,该元数据定义了一个具有固定延迟的周期性触发器,该延迟指示每个任务执行完成后等待的毫秒数。另一个选项是固定速率,指示无论以前的任何执行花费多长时间,都应该运行该方法的频率。此外,对于固定延迟固定速率任务,您都可以指定一个‘Initial-Delay’参数,指示在第一次执行方法之前等待的毫秒数。为了获得更多控制,您可以改为提供cron属性来提供cron表达式。以下示例显示了其他选项:

<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

所有Spring cron表达式都必须遵循相同的格式,无论您是在@Scheduled批注TASK:Scheduled-Tasks元素中使用它们,还是在其他地方使用它们。格式良好的cron表达式(如***)由六个空格分隔的时间和日期字段组成,每个字段都有自己的有效值范围:

 ┌───────────── 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)
 │ │ │ │ │ │
 * * * * * *

以下是一些适用的规则:

  • 字段可以是星号(*),它始终代表“第一个-最后一个”。对于月日或星期几字段,可以使用问号()而不是星号。

  • 逗号()用于分隔列表中的项目。

  • 用连字符(-)分隔的两个数字表示一个数字范围。指定的范围包括在内。

  • 在一个范围(或*)后面加上/指定该数字的值在该范围内的间隔。

  • 英文名称也可用于月份和星期几字段。使用特定日期或月份的前三个字母(大小写不重要)。

  • 月份日和星期几字段可以包含L字符,该字符具有不同的含义

    • 在日期字段中,L代表月份的最后一天。如果后面跟一个负偏移量(即L-n),则表示n该月的第几天

    • 在星期几字段中,L表示一周的最后一天。如果以数字或三个字母的名称(dLddl)作为前缀,则表示月份的最后一天(dddd)。

  • 月份字段可以是nw,表示与月份中最接近的工作日n。如果n落在星期六,则生成它之前的星期五。如果n落在星期天,则生成之后的星期一,如果n1并且落在星期六(即:1W代表月的第一个工作日),也会发生这种情况。

  • 如果日期字段为lw,则表示该月的最后一个工作日

  • 星期几字段可以是d#n(或ddd#n),表示月份的第n天d(或ddd)。

以下是一些例子:

Cron Expression Meaning

0 0***

每天的每个小时都在顶端

*/10***

每隔十秒

0 0 8-10***

每天8点、9点和10点

0 0 6 19***

每天早上6点和晚上7点

0 0/30 8-10***

每天8:00、8:30、9:00、9:30、10:00和10:30

0 0 9-17**星期一至星期五

工作日朝九晚五的工作时间

0 0 0 25 DEC?

每年圣诞节的午夜

0 0 0 L**

一个月的最后一天午夜

0 0 0 L-3**

月中倒数第三天午夜

0 0 0**5L

这个月的上周五午夜

0 0 0**星期四

这个月的上周四午夜

0 0 0 1W**

一个月的第一个工作日午夜

0 0 LW**

一个月中最后一个工作日的午夜

0 0?*5#2

月中第二个星期五午夜

0 0 0?*MON#1

一个月的第一个星期一午夜

5.5.1. Macros

0 0***这样的表达式很难被人类解析,因此在出现错误的情况下也很难修复。为了提高可读性,Spring支持以下宏,这些宏代表常用的序列。您可以使用这些宏而不是六位数值,例如:@Scheduled(cron=“@hourly”)

Macro Meaning

@每年(或@每年)

每年一次(0 0 1 1*)

@每月

每月一次(0 0 0 1**)

@每周

每周一次(0 0 0**0)

@Daily(或@午夜)

一天一次(0 0 0**),或

@每小时

每小时一次(0 0***)

5.6. Using the Quartz Scheduler

Quartz使用触发器作业JobDetail对象实现对各种作业的调度。有关Quartz背后的基本概念,请参阅https://www.quartz-scheduler.org/.为了方便起见,Spring提供了几个类来简化在基于Spring的应用程序中使用Quartz。

5.6.1. Using the JobDetailFactoryBean

QuartzJobDetail对象包含运行作业所需的所有信息。Spring提供了一个JobDetailFactoryBean,它为XML配置提供了Bean样式的属性。请考虑以下示例:

<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>
             

作业详细信息配置包含运行作业所需的所有信息(ExampleJob)。超时在作业数据映射中指定。作业数据映射可通过JobExecutionContext(在执行时传递给您)获得,但JobDetail也从映射到作业实例属性的作业数据中获取其属性。因此,在下面的示例中,ExampleJob包含名为Timeout的Bean属性,并且JobDetail已自动应用该属性:

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
    }
}

             

作业数据地图中的所有其他属性也可供您使用。

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

通常,您只需要调用特定对象上的方法。通过使用MethodInvokingJobDetailFactoryBean,,您完全可以做到这一点,如下例所示:

<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="exampleBusinessObject"/>
    <property name="targetMethod" value="doIt"/>
</bean>
             

前面的示例导致在exampleBusinessObject方法上调用doit方法,如下面的示例所示:

public class ExampleBusinessObject {

    // properties and collaborators

    public void doIt() {
        // do the actual work
    }
}

             
<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>
             

通过使用MethodInvokingJobDetailFactoryBean,,您不需要创建仅仅调用方法的一行作业。您只需要创建实际的业务对象并连接细节对象。

默认情况下,Quartz作业是无状态的,因此作业可能会相互干扰。如果为同一JobDetail指定两个触发器,则第一个作业完成之前,第二个作业可能会启动。如果JobDetail类实现有状态接口,则不会发生这种情况。在第一个作业完成之前,第二个作业不会开始。要使MethodInvokingJobDetailFactoryBean产生的作业是非并发的,请将CODE>标志设置为FALSE,如下例所示:

<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

我们已经创建了作业详细信息和作业。我们还回顾了方便Bean,它允许您调用特定对象上的方法。当然,我们仍然需要对作业本身进行调度。这是通过使用触发器和SchedulerFactoryBean完成的。Quartz中有几个触发器,而Spring提供了两个QuartzFactoryBean实现,它们具有方便的默认设置:CronTriggerFactoryBeanSimpleTriggerFactoryBean

需要计划触发器。Spring提供了SchedulerFactoryBean,它公开了要设置为属性的触发器。SchedulerFactoryBean使用这些触发器调度实际作业。

下面的清单同时使用SimpleTriggerFactoryBeanCronTriggerFactoryBean

<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>
             

前面的示例设置了两个触发器,一个每50秒运行一次,起始延迟为10秒,另一个在每天早上6点运行要完成所有操作,我们需要设置SchedulerFactoryBean,如下面的示例所示:

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="cronTrigger"/>
            <ref bean="simpleTrigger"/>
        </list>
    </property>
</bean>
             

SchedulerFactoryBean有更多属性可用,例如作业详细信息使用的日历、用于定制Quartz的属性以及Spring提供的JDBC DataSource。有关更多信息,请参阅SchedulerFactoryBeanjavadoc。

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

从3.1版开始,Spring框架支持透明地将缓存添加到现有的Spring应用程序中。与事务支持类似,缓存抽象允许在对代码影响最小的情况下一致使用各种缓存解决方案。

在Spring Framework4.1中,缓存抽象得到了显著扩展,支持JSR-107批注和更多定制选项。

6.1. Understanding the Cache Abstraction

Cache vs Buffer

术语“缓冲区”和“缓存”往往可以互换使用。然而,请注意,它们代表的是不同的东西。传统上,缓冲区用作快速实体和慢速实体之间数据的中间临时存储。由于一方必须等待另一方(这会影响性能),缓冲区通过允许整个数据块一次移动而不是以小块的形式移动来缓解这一问题。数据仅从缓冲区写入和读取一次。此外,缓冲区至少对知道它的一方可见。

另一方面,根据定义,缓存是隐藏的,任何一方都不知道缓存发生了。它还提高了性能,但这是通过允许以快速方式多次读取相同的数据来实现的。

在其核心,缓存抽象将缓存应用于Java方法,从而根据缓存中的可用信息减少执行次数。也就是说,每次调用目标方法时,抽象都会应用一个缓存行为,以检查该方法是否已针对给定的参数被调用。如果它已被调用,则返回缓存结果,而不必调用实际的方法。如果尚未调用该方法,则会调用该方法,并且缓存结果并将其返回给用户,以便在下次调用该方法时返回缓存的结果。这样,对于给定的一组参数,昂贵的方法(无论是CPU绑定的还是IO绑定的)只能被调用一次,并且结果可以重复使用,而不必实际再次调用该方法。缓存逻辑被透明地应用,而不会对调用者造成任何干扰。

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.

缓存抽象提供了其他与缓存相关的操作,例如更新缓存内容或删除一个或所有条目的能力。如果缓存处理在应用程序过程中可能更改的数据,则这些参数非常有用。

与Spring框架中的其他服务一样,缓存服务是抽象的(而不是缓存实现),并且需要使用实际存储来存储缓存数据 - ,也就是说,抽象使您不必编写缓存逻辑,但不提供实际的数据存储。这种抽象是通过org.springFrawork.cache.Cacheorg.springframework.cache.CacheManager接口实现的。

Spring提供了一些抽象实现:基于jdkjava.util.concurrent.ConcurrentMap的缓存、Gemfire缓存、咖啡因和符合JSR-107的缓存(如Ehcache3.x)。有关插入其他缓存存储和提供程序的详细信息,请参阅插入不同后端缓存

The caching abstraction has no special handling for multi-threaded and multi-process environments, as such features are handled by the cache implementation.

如果您有一个多进程环境(即部署在多个节点上的应用程序),则需要相应地配置缓存提供程序。根据您的用例,在多个节点上复制相同的数据就足够了。但是,如果您在应用程序过程中更改数据,则可能需要启用其他传播机制。

缓存特定项直接等同于通过编程缓存交互找到的典型get-if-not-found-then-proceed-and-put-eventually代码块。不会应用任何锁,并且多个线程可能会尝试同时加载同一项。这同样适用于驱逐。如果多个线程同时尝试更新或收回数据,则可以使用过时数据。某些缓存提供商提供该领域的高级功能。有关更多详细信息,请参阅您的缓存提供商的文档。

要使用缓存抽象,您需要注意两个方面:

  • 缓存声明:确定需要缓存的方法及其策略。

  • 高速缓存配置:存储数据和从中读取数据的后备高速缓存。

6.2. Declarative Annotation-based Caching

对于缓存声明,Spring的缓存抽象提供了一组Java注释:

  • @cacheable:触发缓存填充。

  • @CacheEvict:触发缓存逐出。

  • @CachePut:更新缓存而不干扰方法执行。

  • @缓存:对要应用于一个方法的多个缓存操作进行重新分组。

  • @CacheConfig:在类级别共享一些与缓存相关的常见设置。

6.2.1. The @Cacheable Annotation

顾名思义,您可以使用< >@cacheable 来划分可缓存的方法,即结果存储在缓存中的方法,以便在后续调用(使用相同的参数)时返回缓存中的值,而不必实际调用该方法。在其最简单的形式中,注释声明需要与带注释的方法关联的缓存的名称,如下面的示例所示:

@Cacheable("books")
public Book findBook(ISBN isbn) {...}

             

在前面的代码片段中,findBook方法与名为book的缓存相关联。每次调用该方法时,都会检查缓存以查看调用是否已经运行,并且不必重复。虽然在大多数情况下,只声明一个缓存,但注释允许指定多个名称,以便使用多个缓存。在这种情况下,在调用方法 - 之前检查每个缓存。如果至少命中一个缓存,则返回关联值。

All the other caches that do not contain the value are also updated, even though the cached method was not actually invoked.

下面的示例在具有多个缓存的findBook方法上使用@cacheable

@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}

             
Default Key Generation

由于缓存本质上是键值存储,因此需要将缓存方法的每次调用转换为适合缓存访问的键。缓存抽象使用基于以下算法的简单KeyGenerator

  • 如果没有给定参数,则返回SimpleKey.EMPTY

  • 如果只给出一个参数,则返回该实例。

  • 如果给定了多个参数,则返回包含所有参数的SimpleKey

这种方法适用于大多数用例,只要参数具有自然键并实现有效的hashCode()equals()方法即可。如果情况并非如此,你需要改变策略。

要提供不同的默认密钥生成器,您需要实现org.springframework.cache.interceptor.KeyGenerator接口。

随着Spring4.0的发布,默认密钥生成策略发生了变化。早期版本的Spring使用键生成策略,对于多个键参数,只考虑参数的hashCode(),而不考虑equals()。这可能导致意外的键冲突(有关背景,请参阅spr-10237)。新的SimpleKeyGenerator针对此类场景使用复合键。

如果您想继续使用前面的键策略,可以配置不推荐使用的org.springframework.cache.interceptor.DefaultKeyGenerator类或创建一个定制的基于散列的KeyGenerator实现。

Custom Key Generation Declaration

因为缓存是通用的,所以目标方法很可能具有各种签名,这些签名不能很容易地映射到缓存结构的顶部。当目标方法有多个参数时,这一点往往会变得很明显,其中只有一些参数适合缓存(而其余参数只由方法逻辑使用)。请考虑以下示例:

@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) 
              

乍一看,虽然两个布尔参数会影响查找图书的方式,但它们对缓存没有任何用处。此外,如果两者中只有一个是重要的,而另一个不重要,那该怎么办?

对于这种情况,@cacheable注释允许您指定如何通过key属性生成键。您可以使用Spel选择感兴趣的参数(或它们的嵌套属性)、执行操作,甚至调用任意方法,而不必编写任何代码或实现任何接口。这是比默认生成器更推荐的方法,因为随着代码库的增长,方法在签名方面往往会有很大的不同。虽然默认策略可能适用于某些方法,但很少适用于所有方法。

以下示例使用了各种Spel声明(如果您不熟悉Spel,请阅读Spring表达式语言):

@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) 
              

前面的代码片段显示了选择某个参数、其属性之一,甚至任意(静态)方法是多么容易。

如果负责生成密钥的算法太具体或者需要共享,可以在操作上定义一个自定义的keyGenerator。为此,请指定要使用的KeyGeneratorBean实现的名称,如下例所示:

@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

缓存抽象使用一个简单的CacheResolver,它通过使用配置的CacheManager检索在操作级定义的缓存。

要提供不同的默认缓存解析器,您需要实现org.springframework.cache.interceptor.CacheResolver接口。

Custom Cache Resolution

默认缓存解析非常适合使用单个CacheManager且没有复杂缓存解析要求的应用程序。

对于使用多个缓存管理器的应用程序,您可以设置用于每个操作的cacheManager,如下例所示:

@Cacheable(cacheNames="books", cacheManager="anotherCacheManager") (1)
public Book findBook(ISBN isbn) {...}

              
1 Specifying anotherCacheManager.

您还可以用与替换密钥生成类似的方式完全替换CacheResolver。为每个缓存操作请求解析,让实现根据运行时参数实际解析要使用的缓存。下面的示例显示如何指定CacheResolver

@Cacheable(cacheResolver="runtimeCacheResolver") (1)
public Book findBook(ISBN isbn) {...}

              
1 Specifying the CacheResolver.

从Spring4.1开始,缓存注释的属性不再是必需的,因为无论注释的内容如何,此特定信息都可以由CacheResolver提供。

keykeyGenerator类似,cacheManagercacheResolver参数是互斥的,如果同时指定这两个参数会导致异常,CacheResolver实现会忽略自定义的CacheManager。这可能不是您所期望的。

Synchronized Caching

在多线程环境中,可能会为相同的参数并发调用某些操作(通常在启动时)。默认情况下,缓存抽象不会锁定任何内容,并且可能会多次计算相同的值,从而违背缓存的目的。

对于那些特定的情况,您可以使用sync属性来指示底层缓存提供程序在计算值时锁定缓存条目。结果,只有一个线程忙于计算值,而其他线程被阻塞,直到缓存中的条目被更新。以下示例显示如何使用sync属性:

@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

有时,某个方法可能不适合始终进行缓存(例如,它可能取决于给定的参数)。缓存注释通过条件参数支持这样的用例,该参数接受一个Spel表达式,该表达式的计算结果为trueFALSE。如果为True,则缓存该方法。如果不是,它的行为就像该方法没有被缓存一样(也就是说,无论缓存中有什么值或使用了什么参数,每次都会调用该方法)。例如,只有当参数name的长度小于32时,才会缓存以下方法:

@Cacheable(cacheNames="book", condition="#name.length() < 32") (1)
public Book findBook(String name) 
              
1 Setting a condition on @Cacheable.

除了条件参数之外,您还可以使用除非参数来否决将值添加到缓存。与条件不同,除非表达式是在调用方法之后计算的。为了扩展前面的示例,我们可能只想缓存平装书,如下面的示例所示:

@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback") (1)
public Book findBook(String name) 
              
1 Using the unless attribute to block hardbacks.

缓存抽象支持java.util.Optional返回类型。如果可选存在,则它将存储在关联的缓存中。如果可选值不存在,NULL将存储在关联的缓存中。#Result始终引用业务实体,从不是受支持的包装器,因此前面的示例可以重写如下:

@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback")
public Optional<Book> findBook(String name) 
              

请注意,#Result仍然引用Book,而不是可选<;Book&>。因为它可能是,所以我们使用Spel的安全导航操作符

Available Caching SpEL Evaluation Context

每个Spel表达式针对专用上下文求值。除了内置参数外,该框架还提供专用的与缓存相关的元数据,如参数名称。下表介绍了可用于上下文的项,以便您可以将它们用于键和条件计算:

Table 9. Cache SpEL available metadata
Name Location Description Example

方法名称

根对象

被调用的方法的名称

#root.method名称

方法

根对象

被调用的方法

#root.method.name

目标

根对象

正在调用的目标对象

#root.Target

Target Class

根对象

被调用的目标的类

#root.Target Class

参数

根对象

用于调用目标的参数(作为数组)

#root.args[0]

缓存

根对象

对其运行当前方法的缓存的集合

#root.cach[0].name

参数名称

评估背景

任何方法参数的名称。如果名称不可用(可能是因为没有调试信息),参数名称也可以在#a<;#arg&>;下找到,其中#arg代表参数索引(从0开始)。

#iban#a0(您也可以使用#p0#p<;#arg&>;表示法作为别名)。

结果

评估背景

方法调用的结果(要缓存的值)。仅在除非表达式、缓存PUT表达式(用于计算)或缓存逐出表达式(当bepreInvotionFALSE时)中才可用。对于受支持的包装器(如可选),#Result指的是实际的对象,而不是包装器。

#结果

6.2.2. The @CachePut Annotation

当需要在不干扰方法执行的情况下更新缓存时,可以使用@CachePut注释。也就是说,始终调用该方法并将其结果放入缓存中(根据@CachePut选项)。它支持与@cacheable相同的选项,并且应该用于缓存填充,而不是方法流优化。下面的示例使用@CachePut批注:

@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

高速缓存抽象不仅允许填充高速缓存存储,还允许驱逐。此过程对于从缓存中删除过时或未使用的数据非常有用。与@cacheable不同,@CacheEvict划分执行缓存逐出的方法(即充当从缓存中删除数据的触发器的方法)。与其同级类似,@CacheEvict需要指定一个或多个受操作影响的缓存,允许指定自定义缓存和键解析或条件,并具有一个额外的参数(allEntry),该参数指示是否需要执行缓存范围的逐出,而不仅仅是条目逐出(基于键)。下面的示例从book缓存中逐出所有条目:

@CacheEvict(cacheNames="books", allEntries=true) (1)
public void loadBooks(InputStream batch) 
             
1 Using the allEntries attribute to evict all entries from the cache.

当需要清空整个缓存区时,此选项非常有用。不是逐出每个条目(这将花费很长时间,因为效率低下),而是在一次操作中删除所有条目,如前面的示例所示。请注意,框架忽略此场景中指定的任何键,因为它不适用(整个缓存被逐出,而不只是一个条目)。

您还可以使用beForeInocation属性来指示是应该在调用方法之后(默认设置)还是在调用方法之前执行逐出。前者提供了与其余注释相同的语义:一旦方法成功完成,就会在缓存上运行一个操作(在本例中是驱逐)。如果该方法不运行(因为它可能被缓存)或引发异常,则不会发生驱逐。后一种方法(beForeInocation=true)导致在调用该方法之前始终发生逐出。在收回不需要绑定到方法结果的情况下,这很有用。

注意,void方法可以与@CacheEvict一起使用--因为这些方法充当触发器,所以返回值被忽略(因为它们不与缓存交互)。@cacheable则不是这样,它将数据添加到缓存中或更新缓存中的数据,因此需要一个结果。

6.2.4. The @Caching Annotation

例如,有时需要指定相同类型的多个注释(如<代码>@CacheEvict 或<代码>@CachePut ) - ,因为不同缓存的条件或键表达式不同。@caching允许在同一方法上使用多个嵌套的@cacheable@CachePut@CacheEvict批注。下面的示例使用两个@CacheEvict批注:

@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date) 
             

6.2.5. The @CacheConfig annotation

到目前为止,我们已经看到缓存操作提供了许多定制选项,并且您可以为每个操作设置这些选项。但是,如果某些定制选项应用于类的所有操作,则它们的配置可能会很繁琐。例如,指定用于类的每个缓存操作的缓存名称可以由单个类级定义代替。这就是@CacheConfige进入播放的地方。以下示例使用@CacheConfig设置缓存的名称:

@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是一个类级批注,允许共享缓存名称、自定义KeyGenerator、自定义CacheManager和自定义CacheResolver。将此注释放置在类上不会打开任何缓存操作。

操作级自定义始终覆盖@CacheConfig上设置的自定义。因此,这为每个缓存操作提供了三个级别的定制:

  • 全局配置,可用于CacheManagerKeyGenerator

  • 在类级别,使用@CacheConfig

  • 在操作层面上。

6.2.6. Enabling Caching Annotations

需要注意的是,尽管声明缓存注释不会自动触发它们的操作--就像Spring中的许多东西一样,该功能必须以声明方式启用(这意味着如果您怀疑缓存是罪魁祸首,您可以通过只删除一个配置行来禁用它,而不是删除代码中的所有注释)。

要启用缓存批注,请将批注@EnableCering添加到您的@Configuration类中:

@Configuration
@EnableCaching
public class AppConfig {
}

             

或者,对于XML配置,您可以使用缓存:注释驱动的元素:

<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>
             

缓存:注释驱动的元素和@EnableCering注释都允许您指定各种选项,这些选项会影响通过AOP将缓存行为添加到应用程序的方式。配置有意类似于@Transaction

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.
Table 10. Cache annotation settings
XML Attribute Annotation Attribute Default Description

缓存管理器

N/A(请参阅CachingConfigurerjavadoc)

cacheManager

要使用的缓存管理器的名称。使用此缓存管理器在后台初始化默认CacheResolver(如果未设置,则初始化cacheManager)。要对缓存解析进行更细粒度的管理,请考虑设置“缓存解析器”属性。

缓存解析器

N/A(请参阅CachingConfigurerjavadoc)

使用配置的cacheManagerSimpleCacheResolver

用于解析支持缓存的CacheResolver的Bean名称。此属性不是必需的,只需将其指定为“缓存管理器”属性的替代属性。

密钥生成器

N/A(请参阅CachingConfigurerjavadoc)

SimpleKeyGenerator

要使用的自定义密钥生成器的名称。

错误处理程序

N/A(请参阅CachingConfigurerjavadoc)

SimpleCacheErrorHandler

要使用的自定义缓存错误处理程序的名称。默认情况下,在与缓存相关的操作期间引发的任何异常都会在客户端抛回。

模式

模式

代理

默认模式(Proxy)使用Spring的AOP框架处理要代理的带注释的Bean(遵循代理语义,如前所述,仅适用于通过代理传入的方法调用)。替代模式(AspectJ)用Spring的AspectJ缓存方面编织受影响的类,修改目标类字节码以应用于任何类型的方法调用。AspectJ编织需要类路径中的Spring-aspects.jar,并启用加载时编织(或编译时编织)。(有关如何设置加载时编织的详细信息,请参阅Spring配置。)

代理目标类

proxyTargetClass

FALSE

仅适用于代理模式。控制为使用@cacheable@CacheEvict批注的类创建哪种类型的缓存代理。如果代理-目标-类属性设置为true,则会创建基于类的代理。如果代理-目标类FALSE,或者如果该属性被省略,则创建标准的基于JDK接口的代理。(有关不同代理类型的详细检查,请参阅代理机制。)

订单

订单

Ordered.LOWEST_PROCENTENCE

定义应用于使用@cacheable@CacheEvict注释的Bean的缓存建议的顺序。(有关AOP建议排序规则的更多信息,请参阅建议排序。)没有指定的顺序意味着AOP子系统决定通知的顺序。

<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.
Method visibility and cache annotations

使用代理时,应仅将缓存批注应用于具有公共可见性的方法。如果您确实使用这些批注来批注受保护的、私有的或包可见的方法,则不会引发错误,但批注的方法不会显示已配置的缓存设置。如果需要注释非公共方法,请考虑使用AspectJ(请参阅本节的其余部分),因为它会更改字节码本身。

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

Custom annotation and AspectJ

此功能仅适用于基于代理的方法,但可以通过使用AspectJ来启用。

Spring-Aspects模块仅为标准批注定义一个方面。如果您已经定义了自己的注释,则还需要为这些注释定义一个方面。有关示例,请查看AnnotationCacheAspect

缓存抽象允许您使用自己的注释来识别触发缓存填充或驱逐的方法。作为一种模板机制,这非常方便,因为它消除了复制缓存注释声明的需要,如果指定了键或条件,或者如果代码库中不允许外来导入(org.springframework),这一点尤其有用。与构造型的其余注释类似,您可以使用@cacheable@CachePut@CacheEvict@CacheConfig作为元注释(即可以批注其他批注的批注)。在下面的示例中,我们用自己的自定义批注替换了通用的@cacheable声明:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Cacheable(cacheNames="books", key="#isbn")
public @interface SlowService {
}

             

在前面的示例中,我们定义了自己的SlowService注释,它本身用@cacheable注释。现在,我们可以替换以下代码:

@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) 
             

下面的示例显示了我们可以用来替换前面代码的自定义批注:

@SlowService
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) 
             

尽管@SlowService不是一个Spring注释,但容器会在运行时自动获取其声明并理解其含义。注意,正如前面所述,需要启用注释驱动行为。

6.3. JCache (JSR-107) Annotations

从4.1版开始,Spring的缓存抽象完全支持JCache标准(JSR-107)注释:@CacheResult@CachePut@CacheRemove@CacheRemoveAll以及@CacheDefaults@CacheKey@CacheValue。即使不将缓存存储迁移到JSR-107,您也可以使用这些注释。内部实现使用Spring的缓存抽象,并提供符合规范的默认CacheResolverKeyGenerator实现。换句话说,如果您已经在使用Spring的缓存抽象,则可以切换到这些标准注释,而无需更改缓存存储(或配置)。

6.3.1. Feature Summary

对于熟悉Spring缓存批注的人来说,下表描述了Spring批注与JSR-107对应批注之间的主要区别:

Table 11. Spring vs. JSR-107 caching annotations
Spring JSR-107 Remark

@cacheable

@CacheResult

非常相似。@CacheResult可以缓存特定的异常并强制执行方法,而不考虑缓存的内容。

@CachePut

@CachePut

当Spring使用方法调用的结果更新缓存时,JCache要求将其作为参数传递,该参数用@CacheValue进行注释。由于这种差异,JCache允许在实际方法调用之前或之后更新缓存。

@CacheEvict

@CacheRemove

非常相似。@CacheRemove当方法调用导致异常时,支持条件驱逐。

@CacheEvict(allEntry=True)

@CacheRemoveAll

请参见@CacheRemove

@CacheConfig

@CacheDefaults

允许您以类似的方式配置相同的概念。

javax.cache.annotation.CacheResolver,的概念与Spring的CacheResolver接口相同,不同之处在于它只支持单个缓存。默认情况下,一个简单的实现根据注释上声明的名称检索要使用的缓存。应该注意的是,如果没有在注释上指定缓存名称,则会自动生成缺省值。有关更多信息,请参阅@CacheResult#cacheName()的javadoc。

CacheResolver实例由CacheResolverFactory检索。可以为每个缓存操作自定义工厂,如下例所示:

@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.

密钥由与Spring的<javax.cache.annotation.CacheKeyGenerator>KeyGenerator相同用途的代码生成。默认情况下,将考虑所有方法参数,除非至少有一个参数使用@CacheKey进行注释。这类似于Spring的定制键生成声明。例如,以下是相同的操作,一个使用Spring的抽象,另一个使用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) 
             

您还可以在操作上指定CacheKeyResolver,类似于指定CacheResolverFactory

JCache可以管理由带注释的方法引发的异常。这可以防止更新缓存,但它也可以缓存异常作为失败的指示器,而不是再次调用该方法。假设如果ISBN的结构无效,则抛出InvalidIsbnNotFoundException。这是一个永久性的故障(没有一本书可以用这样的参数检索到)。下面的代码缓存异常,以便以后调用相同、无效的ISBN时直接抛出缓存的异常,而不是再次调用该方法:

@CacheResult(cacheName="books", exceptionCacheName="failures" cachedExceptions = InvalidIsbnNotFoundException.class)
public Book findBook(ISBN isbn) 
             

6.3.2. Enabling JSR-107 Support

除了Spring的声明性注释支持之外,您不需要做任何特定的事情来启用JSR-107支持。如果类路径中同时存在JSR-107API和Spring-Context-Support模块,@EnableCaching缓存:注释驱动的XML元素都会自动启用JCache支持。

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

如果不能选择注释(可能是因为无法访问源代码或没有外部代码),则可以使用XML进行声明性缓存。因此,您可以在外部指定目标方法和缓存指令,而不是注释缓存方法(类似于声明性事务管理通知)。上一节中的示例可以转换为以下示例:

<!-- 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 -->
            

在前面的配置中,bookService被设置为可缓存。要应用的缓存语义封装在缓存:通知定义中,这使得findBooks方法用于将数据放入缓存,而loadBooks方法用于清除数据。这两个定义都对book缓存起作用。

aop:config定义通过使用AspectJ切入点表达式将缓存建议应用到程序中的相应点(有关更多信息,请参阅面向方面的Spring编程)。在前面的示例中,考虑了来自BookService的所有方法,并将缓存建议应用于它们。

声明性XML缓存支持所有基于注释的模型,因此在两者之间移动应该相当容易。此外,两者都可以在同一应用程序内使用。基于XML的方法不涉及目标代码。然而,从本质上讲,它更冗长。当处理具有针对缓存的重载方法的类时,识别适当的方法确实需要付出额外的努力,因为方法参数不是一个很好的鉴别器。在这些情况下,您可以使用AspectJ切入点来挑选目标方法并应用适当的缓存功能。然而,通过XML,可以更容易地应用包、组或界面范围的缓存(同样是由于AspectJ切入点)和创建类似模板的定义(就像我们在前面的示例中通过缓存:定义缓存属性定义目标缓存所做的那样)。

6.5. Configuring the Cache Storage

缓存抽象提供了多个存储集成选项。要使用它们,您需要声明一个适当的CacheManager(控制和管理缓存实例的实体,可用于检索这些实例以进行存储)。

6.5.1. JDK ConcurrentMap-based Cache

基于jdk的缓存实现驻留在org.springframework.cache.concurrent包下。它允许您使用ConcurentHashMap作为后备缓存存储。以下示例显示如何配置两个缓存:

<!-- 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>
             

前面的代码片段使用SimpleCacheManager为名为Defaultbook的两个嵌套的ConcurentMapCache实例创建一个CacheManager。请注意,名称是直接为每个缓存配置的。

因为缓存是由应用程序创建的,所以它与其生命周期绑定在一起,使其适合基本用例、测试或简单应用程序。缓存的可扩展性很好,速度非常快,但它不提供任何管理、持久性功能或驱逐合同。

6.5.2. Ehcache-based Cache

Ehcache3.x完全兼容JSR-107,不需要专门的支持。有关详细信息,请参阅JSR-107缓存。

6.5.3. Caffeine Cache

Caffeine是对Guava缓存的Java8重写,其实现位于org.springframework.cache.caffeine包中,并提供对Caffeine的几个特性的访问。

以下示例配置按需创建缓存的CacheManager

<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager"/>
             

您还可以提供要显式使用的缓存。在这种情况下,经理只会提供这些服务。以下示例显示了如何执行此操作:

<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
    <property name="cacheNames">
        <set>
            <value>default</value>
            <value>books</value>
        </set>
    </property>
</bean>
             

CaffeineCacheManager还支持自定义CacheManagerCacheLoader。有关这些内容的更多信息,请参阅咖啡因文档。

6.5.4. GemFire-based Cache

GemFire是一个面向内存、磁盘备份、弹性可扩展、连续可用、活动(具有内置的基于模式的订阅通知)、全局复制的数据库,并提供功能齐全的边缘缓存。有关如何将GemFire用作CacheManager(以及更多)的更多信息,请参阅Spring data GemFire参考文档

6.5.5. JSR-107 Cache

Spring的缓存抽象也可以使用符合JSR-107的缓存。JCache实现位于org.springFrawork.cache.jcache包中。

同样,要使用它,您需要声明适当的CacheManager。以下示例显示了如何执行此操作:

<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

有时,在切换环境或进行测试时,您可能会在没有配置实际备份缓存的情况下进行缓存声明。由于这是一个无效的配置,因此在运行时引发异常,因为缓存基础结构无法找到合适的存储。在这样的情况下,您可以连接一个不执行缓存 - 的简单虚拟缓存,而不是删除缓存声明(这可能被证明是乏味的),也就是说,它强制每次都调用缓存的方法。以下示例显示了如何执行此操作:

<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>
             

前面所述的CompositeCacheManager链接多个CacheManager实例,并通过Fallback ToNoOpCache标志为配置的缓存管理器未处理的所有定义添加一个无操作缓存。也就是说,在jdkCachegeFireCache(在前面的示例中配置)中找不到的每个缓存定义都由无操作缓存处理,该缓存不存储任何信息,从而导致每次都调用目标方法。

6.6. Plugging-in Different Back-end Caches

显然,市面上有很多缓存产品可以用作后备存储。对于那些不支持JSR-107的代码,您需要提供一个CacheManager和一个缓存实现。这听起来可能比实际情况更难,因为在实践中,类往往是简单的适配器,它们将缓存抽象框架映射到存储API之上,就像Caffeine类所做的那样。大多数CacheManager类都可以使用org.springframework.cache.support包中的类(例如AbstractCacheManager,它负责样板代码,只留下实际的映射需要完成)。

6.7. How can I Set the TTL/TTI/Eviction policy/XXX feature?

直接通过您的缓存提供商。缓存抽象是一种抽象,而不是一种缓存实现。您使用的解决方案可能支持其他解决方案不支持的各种数据策略和不同的拓扑(例如,在缓存抽象中公开的jdk并发HashMap - 将是无用的,因为没有后备支持)。这种功能应该直接通过后备缓存(在配置它时)或通过其本机API进行控制。

7. Appendix

7.1. XML Schemas

附录的这一部分列出了与集成技术相关的XML模式。

7.1.1. The jee Schema

jee元素处理与Jakarta EE(企业版)配置相关的问题,例如查找JNDI对象和定义EJB引用。

要使用jee模式中的元素,您需要在您的Spring XML配置文件的顶部有以下前言。以下代码段中的文本引用了正确的架构,以便您可以使用jee命名空间中的元素:

<?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)

以下示例显示如何使用JNDI查找没有JEE架构的数据源:

<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>
              

以下示例显示如何使用JNDI查找具有JEE架构的数据源:

<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)

以下示例说明如何使用JNDI查找没有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>
              

以下示例说明如何使用JNDI通过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)

以下示例说明如何使用JNDI查找多个环境变量,而不使用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>
              

以下示例说明如何使用JNDI通过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)

以下示例显示如何使用JNDI在不使用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>
              

以下示例显示如何使用JNDI通过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)

<;jee:local-slsb/>;元素用于配置对本地EJB无状态会话Bean的引用。

以下示例显示如何在不使用JEE的情况下配置对本地EJB无状态会话Bean的引用:

<bean id="simple" class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
    <property name="jndiName" value="ejb/RentalServiceBean"/>
    <property name="businessInterface" value="com.foo.service.RentalService"/>
</bean>
              

以下示例显示如何使用JEE配置对本地ejb无状态会话Bean的引用:

<jee:local-slsb id="simpleSlsb" jndi-name="ejb/RentalServiceBean" business-interface="com.foo.service.RentalService"/>
              
<jee:local-slsb/> (Complex)

<;jee:local-slsb/>;元素用于配置对本地EJB无状态会话Bean的引用。

以下示例显示如何配置对本地ejb无状态会话Bean的引用和许多没有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>
              

以下示例显示如何使用JEE配置对本地ejb无状态会话Bean和许多属性的引用:

<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/>

<;jee:emote-slsb/>;元素用于配置对远程EJB无状态会话Bean的引用。

以下示例显示如何配置对不带JEE的远程ejb无状态会话Bean的引用:

<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>
              

以下示例显示如何使用JEE配置对远程ejb无状态会话Bean的引用:

<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

jms元素处理配置与JMS相关的Bean,比如Spring的消息侦听器容器。这些元素在JMS章节标题为JMS命名空间支持一节中有详细介绍。有关该支持和jms元素本身的完整详细信息,请参阅该章。

为了完整起见,要使用jms模式中的元素,您需要在您的Spring XML配置文件的顶部有以下前言。以下代码片段中的文本引用了正确的架构,以便您可以使用jms命名空间中的元素:

<?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/>

配置基于注释的MBean导出中详细介绍了该元素。

7.1.4. The cache Schema

您可以使用缓存元素来启用对Spring的@CacheEvict@CachePut@缓存批注的支持。它还支持基于XML的声明式缓存。有关详细信息,请参阅启用缓存注释基于声明性XML的缓存

要使用缓存模式中的元素,您需要在您的Spring XML配置文件的顶部有以下前言。以下代码段中的文本引用了正确的架构,以便您可以使用缓存命名空间中的元素:

<?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>