这部分参考文档涵盖了 Spring Framework 与多种技术的集成。

1. REST 端点

Spring Framework 提供了两种调用 REST 端点的选择:

  • RestTemplate:具有同步模板方法 API 的原始 Spring REST 客户端。

  • WebClient:一种非阻塞、反应式的替代方案,支持同步和异步以及流式场景。

从 5.0 开始,RestTemplate它处于维护模式,只有较小的更改请求和错误被接受。请考虑使用 提供更现代 API 并支持同步、异步和流式传输方案 的WebClient 。

1.1。RestTemplate

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

表 1. RestTemplate 方法
方法组 描述

getForObject

通过 GET 检索表示。

getForEntity

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

headForHeaders

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

postForLocation

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

postForObject

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

postForEntity

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

put

使用 PUT 创建或更新资源。

patchForObject

使用 PATCH 更新资源并从响应中返回表示。请注意,JDKHttpURLConnection不支持PATCH,但 Apache HttpComponents 和其他支持。

delete

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

optionsForAllow

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

exchange

上述方法的更通用(且不那么固执)版本,可在需要时提供额外的灵活性。它接受 a RequestEntity(包括 HTTP 方法、URL、标头和正文作为输入)并返回一个ResponseEntity.

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

execute

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

1.1.1。初始化

默认构造函数用于java.net.HttpURLConnection执行请求。您可以切换到具有ClientHttpRequestFactory. 有对以下内容的内置支持:

  • Apache HttpComponents

  • 网状

  • OkHttp

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

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

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

请注意,java.net当访问表示错误的响应状态(例如 401)时,HTTP 请求的实现可能会引发异常。如果这是一个问题,请切换到另一个 HTTP 客户端库。
URI

许多RestTemplate方法接受 URI 模板和 URI 模板变量,可以作为String变量参数,也可以作为Map<String,String>.

以下示例使用String变量参数:

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

以下示例使用Map<String, String>:

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

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

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

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

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

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

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

标头

您可以使用这些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();

RestTemplate您可以通过许多返回的方法变体 获取响应标头ResponseEntity

1.1.2。身体

传入和从RestTemplate方法返回的对象在HttpMessageConverter.

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

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

您无需显式设置请求的 Content-Type 标头。在大多数情况下,您可以根据源Object类型找到兼容的消息转换器,并且选择的消息转换器会相应地设置内容类型。如有必要,您可以使用这些 exchange方法显式提供Content-Type请求标头,这反过来会影响选择的消息转换器。

在 GET 上,响应的主体被反序列化为 output Object,如以下示例所示:

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

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

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

1.1.3。消息转换

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

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

的实现HttpMessageConverter将在以下部分中描述。对于所有转换器,都使用默认媒体类型,但您可以通过设置 supportedMediaTypesbean 属性来覆盖它。下表描述了每个实现:

表 2. HttpMessageConverter 实现
消息转换器 描述

StringHttpMessageConverter

可以从 HTTP 请求和响应HttpMessageConverter读取和写入实例的实现。String默认情况下,此转换器支持所有文本媒体类型 ( ) 并使用oftext/*写入。Content-Typetext/plain

FormHttpMessageConverter

一种HttpMessageConverter可以从 HTTP 请求和响应中读取和写入表单数据的实现。默认情况下,此转换器读取和写入 application/x-www-form-urlencoded媒体类型。表单数据被读取并写入到 MultiValueMap<String, String>. 转换器还可以写入(但不能读取)从MultiValueMap<String, Object>. 默认情况下,multipart/form-data支持。从 Spring Framework 5.2 开始,可以支持额外的多部分子类型来编写表单数据。有关更多详细信息,请参阅 javadoc FormHttpMessageConverter

ByteArrayHttpMessageConverter

一种HttpMessageConverter可以从 HTTP 请求和响应中读取和写入字节数组的实现。默认情况下,此转换器支持所有媒体类型 ( ) 并使用of*/*写入。您可以通过设置属性和覆盖来覆盖它。Content-Typeapplication/octet-streamsupportedMediaTypesgetContentType(byte[])

MarshallingHttpMessageConverter

HttpMessageConverter可以通过使用 Spring Marshaller和包中的Unmarshaller抽象来读取和写入 XML的org.springframework.oxm实现。此转换器需要一个MarshallerandUnmarshaller才能使用。您可以通过构造函数或 bean 属性注入这些。默认情况下,此转换器支持 text/xmlapplication/xml

MappingJackson2HttpMessageConverter

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

MappingJackson2XmlHttpMessageConverter

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

SourceHttpMessageConverter

可以从 HTTP 请求和响应HttpMessageConverter读取和写入 的实现。javax.xml.transform.Source仅支持DOMSourceSAXSourceStreamSource。默认情况下,此转换器支持 text/xmlapplication/xml

BufferedImageHttpMessageConverter

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

1.1.4。杰克逊 JSON 视图

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

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);
多部分

要发送多部分数据,您需要提供一个MultiValueMap<String, Object>其值可能是一个Object用于部分内容、一个Resource用于文件部分或一个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选择的序列化自动确定的,或者在Resource基于文件扩展名的情况下。如有必要,您可以显式提供MediaType包装HttpEntity器。

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

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

如果MultiValueMap包含至少一个非String值,则由Content-Type设置为。如果具有 值,则默认为。如有必要,也可以显式设置。multipart/form-dataFormHttpMessageConverterMultiValueMapStringContent-Typeapplication/x-www-form-urlencodedContent-Type

1.2. 使用AsyncRestTemplate(已弃用)

AsyncRestTemplate弃用。对于您可能考虑使用 AsyncRestTemplate的所有用例,请改用WebClient

2. 远程处理和 Web 服务

Spring 支持使用各种技术进行远程处理。远程支持简化了远程服务的开发,这些服务通过 Java 接口和对象作为输入和输出来实现。目前,Spring 支持以下远程技术:

  • Java Web 服务:Spring 通过 JAX-WS 为 Web 服务提供远程支持。

  • AMQP:独立的 Spring AMQP 项目支持通过 AMQP 作为底层协议进行远程处理。

从 Spring Framework 5.3 开始,出于安全原因和更广泛的行业支持,现在已弃用对多种远程处理技术的支持。支持基础设施将从 Spring Framework 中删除,以用于其下一个主要版本。

以下远程处理技术现已弃用且不会被替换:

  • 远程方法调用(RMI):通过使用RmiProxyFactoryBeanand RmiServiceExporter,Spring 支持传统的 RMI(带有java.rmi.Remote 接口 和java.rmi.RemoteException)和通过 RMI 调用程序(带有任何 Java 接口)的透明远程处理。

  • Spring HTTP Invoker (Deprecated):Spring 提供了一种特殊的远程处理策略,允许通过 HTTP 进行 Java 序列化,支持任何 Java 接口(就像 RMI 调用程序一样)。对应的支持类是HttpInvokerProxyFactoryBeanHttpInvokerServiceExporter

  • Hessian:通过使用 SpringHessianProxyFactoryBeanHessianServiceExporter,您可以通过 Caucho 提供的基于 HTTP 的轻量级二进制协议透明地公开您的服务。

  • JMS(已弃用) :通过模块中的JmsInvokerServiceExporterJmsInvokerProxyFactoryBean类 支持通过 JMS 作为底层协议进行远程处理 。spring-jms

在讨论 Spring 的远程处理能力时,我们使用以下领域模型和相应的服务:

public class Account implements Serializable {

    private String name;

    public String getName(){
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public interface AccountService {

    public void insertAccount(Account account);

    public List<Account> getAccounts(String name);
}
// the implementation doing nothing at the moment
public class AccountServiceImpl implements AccountService {

    public void insertAccount(Account acc) {
        // do something...
    }

    public List<Account> getAccounts(String name) {
        // do something...
    }
}

本节首先使用 RMI 将服务公开给远程客户端,并讨论一下使用 RMI 的缺点。然后继续使用 Hessian 作为协议的示例。

2.1。AMQP

Spring AMQP 项目支持通过 AMQP 作为底层协议进行远程处理。有关详细信息,请访问 Spring AMQP 参考的Spring Remoting 部分。

远程接口没有实现自动检测。

远程接口不会自动检测已实现接口的主要原因是避免为远程调用者打开太多门。目标对象可能实现内部回调接口,例如InitializingBeanDisposableBean 希望向调用者公开的接口。

在本地情况下,提供具有由目标实现的所有接口的代理通常无关紧要。但是,当您导出远程服务时,您应该公开一个特定的服务接口,以及用于远程使用的特定操作。除了内部回调接口,目标可能实现多个业务接口,其中只有一个用于远程公开。由于这些原因,我们需要指定这样的服务接口。

这是配置便利性和内部方法意外暴露风险之间的权衡。总是指定一个服务接口并不费力,并且在特定方法的受控公开方面让您处于安全的一边。

2.2. 选择技术时的注意事项

这里介绍的每一项技术都有其缺点。在选择技术时,您应该仔细考虑您的需求、您公开的服务以及您通过网络发送的对象。

使用 RMI 时,您无法通过 HTTP 协议访问对象,除非您通过隧道传输 RMI 流量。RMI 是一个相当重量级的协议,因为它支持全对象序列化,这在您使用需要通过网络进行序列化的复杂数据模型时非常重要。但是,RMI-JRMP 与 Java 客户端相关联。它是一种 Java 到 Java 的远程解决方案。

如果您需要基于 HTTP 的远程处理但又依赖于 Java 序列化,那么 Spring 的 HTTP 调用程序是一个不错的选择。它与 RMI 调用程序共享基本基础架构,但使用 HTTP 作为传输。请注意,HTTP 调用程序不仅限于 Java 到 Java 远程处理,还包括客户端和服务器端的 Spring。(后者也适用于 Spring 的非 RMI 接口的 RMI 调用程序。)

在异构环境中运行时,Hessian 可能会提供重要的价值,因为它们明确允许非 Java 客户端。但是,非 Java 支持仍然有限。已知问题包括 Hibernate 对象的序列化与延迟初始化的集合相结合。如果您有这样的数据模型,请考虑使用 RMI 或 HTTP 调用程序而不是 Hessian。

JMS 可用于提供服务集群并让 JMS 代理负责负载平衡、发现和自动故障转移。默认情况下,Java 序列化用于 JMS 远程处理,但 JMS 提供者可以使用不同的机制进行有线格式化,例如 XStream 以让服务器在其他技术中实现。

最后但同样重要的是,EJB 优于 RMI,因为它支持标准的基于角色的身份验证和授权以及远程事务传播。也可以让 RMI 调用程序或 HTTP 调用程序支持安全上下文传播,尽管核心 Spring 不提供这一点。Spring 仅提供适当的钩子用于插入第三方或自定义解决方案。

2.3. Java 网络服务

Spring 提供对标准 Java Web 服务 API 的全面支持:

  • 使用 JAX-WS 公开 Web 服务

  • 使用 JAX-WS 访问 Web 服务

除了 Spring Core 中对 JAX-WS 的库存支持之外,Spring 产品组合还具有Spring Web Services,这是一种合同优先、文档驱动的 Web 服务的解决方案——强烈推荐用于构建现代、面向未来的 Web 服务。

2.3.1。使用 JAX-WS 公开基于 Servlet 的 Web 服务

Spring 为 JAX-WS servlet 端点实现提供了一个方便的基类: SpringBeanAutowiringSupport. 为了公开我们的AccountService,我们扩展了 Spring 的 SpringBeanAutowiringSupport类并在这里实现了我们的业务逻辑,通常将调用委托给业务层。我们使用 Spring 的@Autowired 注解来表达对 Spring 管理的 bean 的这种依赖关系。下面的例子展示了我们扩展的类SpringBeanAutowiringSupport

/**
 * JAX-WS compliant AccountService implementation that simply delegates
 * to the AccountService implementation in the root web application context.
 *
 * This wrapper class is necessary because JAX-WS requires working with dedicated
 * endpoint classes. If an existing service needs to be exported, a wrapper that
 * extends SpringBeanAutowiringSupport for simple Spring bean autowiring (through
 * the @Autowired annotation) is the simplest JAX-WS compliant way.
 *
 * This is the class registered with the server-side JAX-WS implementation.
 * In the case of a Java EE server, this would simply be defined as a servlet
 * in web.xml, with the server detecting that this is a JAX-WS endpoint and reacting
 * accordingly. The servlet name usually needs to match the specified WS service name.
 *
 * The web service engine manages the lifecycle of instances of this class.
 * Spring bean references will just be wired in here.
 */
import org.springframework.web.context.support.SpringBeanAutowiringSupport;

@WebService(serviceName="AccountService")
public class AccountServiceEndpoint extends SpringBeanAutowiringSupport {

    @Autowired
    private AccountService biz;

    @WebMethod
    public void insertAccount(Account acc) {
        biz.insertAccount(acc);
    }

    @WebMethod
    public Account[] getAccounts(String name) {
        return biz.getAccounts(name);
    }
}

我们AccountServiceEndpoint需要在与 Spring 上下文相同的 Web 应用程序中运行,以允许访问 Spring 的设施。在 Java EE 环境中默认情况下就是这种情况,使用 JAX-WS servlet 端点部署的标准协定。有关详细信息,请参阅各种 Java EE Web 服务教程。

2.3.2. 使用 JAX-WS 导出独立 Web 服务

Oracle JDK 附带的内置 JAX-WS 提供程序支持通过使用 JDK 中也包含的内置 HTTP 服务器公开 Web 服务。Spring 在 Spring 应用程序上下文中SimpleJaxWsServiceExporter检测所有带注释的@WebServicebean,并通过默认的 JAX-WS 服务器(JDK HTTP 服务器)将它们导出。

在这种情况下,端点实例作为 Spring bean 本身定义和管理。它们在 JAX-WS 引擎中注册,但它们的生命周期取决于 Spring 应用程序上下文。这意味着您可以将 Spring 功能(例如显式依赖注入)应用于端点实例。注释驱动的注入也可以通过@Autowired作品进行。以下示例显示了如何定义这些 bean:

<bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter">
    <property name="baseAddress" value="http://localhost:8080/"/>
</bean>

<bean id="accountServiceEndpoint" class="example.AccountServiceEndpoint">
    ...
</bean>

...

AccountServiceEndpoint可以但不必从 Spring 派生SpringBeanAutowiringSupport,因为此示例中的端点是完全由 Spring 管理的 bean 。这意味着端点实现可以如下(没有声明任何超类 - 并且@Autowired仍然遵循 Spring 的配置注释):

@WebService(serviceName="AccountService")
public class AccountServiceEndpoint {

    @Autowired
    private AccountService biz;

    @WebMethod
    public void insertAccount(Account acc) {
        biz.insertAccount(acc);
    }

    @WebMethod
    public List<Account> getAccounts(String name) {
        return biz.getAccounts(name);
    }
}

2.3.3。使用 JAX-WS RI 的 Spring 支持导出 Web 服务

Oracle 的 JAX-WS RI 是作为 GlassFish 项目的一部分开发的,将 Spring 支持作为其 JAX-WS Commons 项目的一部分提供。这允许将 JAX-WS 端点定义为 Spring 管理的 bean,类似于上 一节中讨论的独立模式 ——但这次是在 Servlet 环境中。

这在 Java EE 环境中是不可移植的。它主要用于将 JAX-WS RI 作为 Web 应用程序的一部分嵌入的非 EE 环境,例如 Tomcat。

与导出基于 servlet 的端点的标准样式的不同之处在于端点实例本身的生命周期由 Spring 管理,并且在web.xml. 使用标准 Java EE 样式(如前所示),每个服务端点都有一个 servlet 定义,每个端点通常委托给 Spring bean(通过使用@Autowired,如前所示)。

有关设置和使用方式的详细信息,请参阅https://jax-ws-commons.java.net/spring/

2.3.4。使用 JAX-WS 访问 Web 服务

LocalJaxWsServiceFactoryBeanSpring 提供了两个工厂 bean 来创建 JAX-WS Web 服务代理,即 JaxWsPortProxyFactoryBean. 前者只能返回一个 JAX-WS 服务类供我们使用。后者是可以返回实现我们业务服务接口的代理的完整版本。在以下示例中,我们使用(再次)JaxWsPortProxyFactoryBeanAccountService端点创建代理:

<bean id="accountWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
    <property name="serviceInterface" value="example.AccountService"/> (1)
    <property name="wsdlDocumentUrl" value="http://localhost:8888/AccountServiceEndpoint?WSDL"/>
    <property name="namespaceUri" value="https://example/"/>
    <property name="serviceName" value="AccountService"/>
    <property name="portName" value="AccountServiceEndpointPort"/>
</bean>
1 serviceInterface客户使用的我们的业务接口在哪里。

wsdlDocumentUrl是 WSDL 文件的 URL。Spring 在启动时需要它来创建 JAX-WS 服务。namespaceUri对应targetNamespace于 .wsdl 文件中的 。serviceName对应于 .wsdl 文件中的服务名称。portName 对应于 .wsdl 文件中的端口名称。

访问 Web 服务很容易,因为我们有一个 bean 工厂,将其公开为一个名为AccountService. 以下示例显示了我们如何在 Spring 中将其连接起来:

<bean id="client" class="example.AccountClientImpl">
    ...
    <property name="service" ref="accountWebService"/>
</bean>

从客户端代码中,我们可以像访问普通类一样访问 Web 服务,如以下示例所示:

public class AccountClientImpl {

    private AccountService service;

    public void setService(AccountService service) {
        this.service = service;
    }

    public void foo() {
        service.insertAccount(...);
    }
}
上面稍微简化了一点,因为 JAX-WS 要求端点接口和实现类使用 、 等注释进行@WebService注释@SOAPBinding。这意味着您不能(轻松地)使用纯 Java 接口和实现类作为 JAX-WS 端点工件;您需要先相应地注释它们。查看 JAX-WS 文档以获取有关这些要求的详细信息。

2.4. RMI(已弃用)

从 Spring Framework 5.3 开始,RMI 支持已被弃用并且不会被替换。

通过使用 Spring 对 RMI 的支持,您可以通过 RMI 基础架构透明地公开您的服务。完成此设置后,您基本上拥有类似于远程 EJB 的配置,除了没有对安全上下文传播或远程事务传播的标准支持这一事实。当您使用 RMI 调用程序时,Spring 确实为此类额外的调用上下文提供了挂钩,因此您可以插入安全框架或自定义安全凭证。

2.4.1。通过使用导出服务RmiServiceExporter

使用RmiServiceExporter,我们可以将 AccountService 对象的接口公开为 RMI 对象。可以使用 访问接口RmiProxyFactoryBean,或者在传统 RMI 服务的情况下通过普通 RMI 访问该接口。明确支持通过RmiServiceExporterRMI 调用程序公开任何非 RMI 服务。

我们首先必须在 Spring 容器中设置我们的服务。以下示例显示了如何执行此操作:

<bean id="accountService" class="example.AccountServiceImpl">
    <!-- any additional properties, maybe a DAO? -->
</bean>

接下来,我们必须使用RmiServiceExporter. 以下示例显示了如何执行此操作:

<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
    <!-- does not necessarily have to be the same name as the bean to be exported -->
    <property name="serviceName" value="AccountService"/>
    <property name="service" ref="accountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
    <!-- defaults to 1099 -->
    <property name="registryPort" value="1199"/>
</bean>

在前面的示例中,我们覆盖了 RMI 注册表的端口。通常,您的应用程序服务器还维护一个 RMI 注册表,明智的做法是不要干扰该注册表。此外,服务名称用于绑定服务。因此,在前面的示例中,服务绑定在'rmi://HOST:1199/AccountService'。我们稍后会使用此 URL 链接客户端的服务。

servicePort属性已被省略(默认为 0)。这意味着使用匿名端口与服务进行通信。

2.4.2. 在客户端链接服务

我们的客户端是一个简单的对象,它使用AccountService来管理帐户,如以下示例所示:

public class SimpleObject {

    private AccountService accountService;

    public void setAccountService(AccountService accountService) {
        this.accountService = accountService;
    }

    // additional methods using the accountService
}

为了在客户端链接服务,我们创建了一个单独的 Spring 容器,以包含以下简单对象和服务链接配置位:

<bean class="example.SimpleObject">
    <property name="accountService" ref="accountService"/>
</bean>

<bean id="accountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
    <property name="serviceUrl" value="rmi://HOST:1199/AccountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
</bean>

这就是我们在客户端支持远程帐户服务所需要做的一切。Spring 透明地创建一个调用者并通过 RmiServiceExporter. 在客户端,我们使用RmiProxyFactoryBean.

2.5. 使用 Hessian 通过 HTTP 远程调用服务(已弃用)

从 Spring Framework 5.3 开始,不推荐使用 Hessian 支持,并且不会被替换。

Hessian 提供了一个基于 HTTP 的二进制远程协议。它由 Caucho 开发,您可以在https://www.caucho.com/找到有关 Hessian 本身的更多信息。

2.5.1。黑森州

Hessian 通过 HTTP 进行通信,并使用自定义 servlet 进行通信。通过使用 Spring 的 DispatcherServlet原理(参见webmvc.html),我们可以连接这样一个 servlet 来公开您的服务。首先,我们必须在我们的应用程序中创建一个新的 servlet,如以下摘录所示web.xml

<servlet>
    <servlet-name>remoting</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>remoting</servlet-name>
    <url-pattern>/remoting/*</url-pattern>
</servlet-mapping>

如果您熟悉 Spring 的DispatcherServlet原理,您可能知道现在您必须在目录中创建一个名为 remoting-servlet.xml(在您的 servlet 名称之后)的Spring 容器配置资源WEB-INF。应用程序上下文将在下一节中使用。

或者,考虑使用 Spring 更简单的HttpRequestHandlerServlet. 这样做可以让您将远程导出器定义嵌入到您的根应用程序上下文中(默认情况下,在 中WEB-INF/applicationContext.xml),其中各个 servlet 定义指向特定的导出器 bean。在这种情况下,每个 servlet 名称都需要与其目标导出器的 bean 名称相匹配。

2.5.2. 通过使用暴露你的 BeanHessianServiceExporter

在新创建的名为 的应用程序上下文中remoting-servlet.xml,我们创建一个 HessianServiceExporter来导出我们的服务,如以下示例所示:

<bean id="accountService" class="example.AccountServiceImpl">
    <!-- any additional properties, maybe a DAO? -->
</bean>

<bean name="/AccountService" class="org.springframework.remoting.caucho.HessianServiceExporter">
    <property name="service" ref="accountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
</bean>

现在我们准备在客户端链接服务。没有指定显式处理程序映射(将请求 URL 映射到服务),所以我们使用BeanNameUrlHandlerMapping used. 因此,服务在包含DispatcherServlet实例的映射(如前所述)中通过其 bean 名称指示的 URL 导出: https://HOST:8080/remoting/AccountService

或者,您可以HessianServiceExporter在根应用程序上下文中创建一个(例如,在 中WEB-INF/applicationContext.xml),如以下示例所示:

<bean name="accountExporter" class="org.springframework.remoting.caucho.HessianServiceExporter">
    <property name="service" ref="accountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
</bean>

在后一种情况下,您应该在 中为该导出器定义一个相应的 servlet,web.xml最终结果相同:导出器被映射到在 中的请求路径 /remoting/AccountService。请注意,servlet 名称需要与目标导出器的 bean 名称匹配。以下示例显示了如何执行此操作:

<servlet>
    <servlet-name>accountExporter</servlet-name>
    <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>accountExporter</servlet-name>
    <url-pattern>/remoting/AccountService</url-pattern>
</servlet-mapping>

2.5.3. 在客户端连接服务

通过使用HessianProxyFactoryBean,我们可以在客户端链接服务。与 RMI 示例相同的原则适用。SimpleObject我们创建一个单独的 bean 工厂或应用程序上下文,并通过使用管理帐户来提及以下 bean AccountService,如以下示例所示:

<bean class="example.SimpleObject">
    <property name="accountService" ref="accountService"/>
</bean>

<bean id="accountService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
    <property name="serviceUrl" value="https://remotehost:8080/remoting/AccountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
</bean>

2.5.4。将 HTTP 基本身份验证应用于通过 Hessian 公开的服务

Hessian 的优点之一是我们可以轻松应用 HTTP 基本身份验证,因为这两种协议都是基于 HTTP 的。例如,您可以通过使用web.xml安全功能来应用您正常的 HTTP 服务器安全机制。通常,您不需要在此处使用每个用户的安全凭证。相反,您可以使用在HessianProxyFactoryBean级别定义的共享凭证(类似于 JDBC DataSource),如以下示例所示:

<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
    <property name="interceptors" ref="authorizationInterceptor"/>
</bean>

<bean id="authorizationInterceptor"
        class="org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor">
    <property name="authorizedRoles" value="administrator,operator"/>
</bean>

在前面的示例中,我们明确提到了BeanNameUrlHandlerMappingand 设置了一个拦截器,以便只有管理员和操作员才能调用此应用程序上下文中提到的 bean。

前面的例子没有展示一种灵活的安全基础设施。有关安全性的更多选项,请查看https://spring.io/projects/spring-security/上的 Spring Security 项目。

2.6. Spring HTTP 调用程序(已弃用)

从 Spring Framework 5.3 开始,HTTP Invoker 支持已弃用且不会被替换。

与 Hessian 不同,Spring HTTP 调用程序都是轻量级协议,它们使用自己的苗条序列化机制,并使用标准的 Java 序列化机制通过 HTTP 公开服务。如果您的参数和返回类型是无法通过使用 Hessian 使用的序列化机制进行序列化的复杂类型,这将具有巨大的优势(当您选择远程处理技术时,请参阅下一节以了解更多注意事项)。

在底层,Spring 使用 JDK 或 Apache 提供的标准工具HttpComponents来执行 HTTP 调用。如果您需要更高级和更易于使用的功能,请使用后者。有关更多信息,请参阅 hc.apache.org/httpcomponents-client-ga/

请注意由于不安全的 Java 反序列化导致的漏洞:在反序列化步骤期间,被操纵的输入流可能会导致服务器上不需要的代码执行。因此,不要将 HTTP 调用程序端点暴露给不受信任的客户端。相反,仅在您自己的服务之间公开它们。通常,我们强烈建议使用任何其他消息格式(例如 JSON)。

如果您担心 Java 序列化导致的安全漏洞,请考虑核心 JVM 级别的通用序列化过滤器机制,该机制最初是为 JDK 9 开发的,但同时向后移植到 JDK 8、7 和 6。请参阅 https://blogs.oracle.com/java-platform-group/entry/incoming_filter_serialization_data_ahttps://openjdk.java.net/jeps/290

2.6.1。暴露服务对象

为服务对象设置 HTTP 调用程序基础结构与使用 Hessian 执行相同操作的方式非常相似。正如 Hessian 支持提供 的那样HessianServiceExporter,Spring 的 HttpInvoker 支持提供 org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter.

要在 Spring Web MVC 中公开AccountService(前面提到的) DispatcherServlet,需要在调度程序的应用程序上下文中进行以下配置,如以下示例所示:

<bean name="/AccountService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
    <property name="service" ref="accountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
</bean>

如Hessian 部分所述,此类导出器定义通过DispatcherServlet实例的标准映射工具公开。

或者,您可以在根应用程序上下文中创建一个HttpInvokerServiceExporter(例如, in 'WEB-INF/applicationContext.xml'),如以下示例所示:

<bean name="accountExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
    <property name="service" ref="accountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
</bean>

此外,您可以在 中为这个导出器定义一个对应web.xml的 servlet,servlet 名称与目标导出器的 bean 名称匹配,如下例所示:

<servlet>
    <servlet-name>accountExporter</servlet-name>
    <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>accountExporter</servlet-name>
    <url-pattern>/remoting/AccountService</url-pattern>
</servlet-mapping>

2.6.2. 在客户端链接服务

同样,从客户端链接服务与使用 Hessian 时的操作方式非常相似。通过使用代理,Spring 可以将您对 HTTP POST 请求的调用转换为指向导出服务的 URL。以下示例显示了如何配置此排列:

<bean id="httpInvokerProxy" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
    <property name="serviceUrl" value="https://remotehost:8080/remoting/AccountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
</bean>

如前所述,您可以选择要使用的 HTTP 客户端。默认情况下, HttpInvokerProxy使用 JDK 的 HTTP 功能,但您也可以 HttpComponents通过设置httpInvokerRequestExecutor属性来使用 Apache 客户端。以下示例显示了如何执行此操作:

<property name="httpInvokerRequestExecutor">
    <bean class="org.springframework.remoting.httpinvoker.HttpComponentsHttpInvokerRequestExecutor"/>
</property>

2.7. JMS(已弃用)

从 Spring Framework 5.3 开始,JMS 远程处理支持已弃用且不会被替换。

您还可以使用 JMS 作为底层通信协议透明地公开服务。Spring Framework 中的 JMS 远程处理支持非常基础。same thread它在同一非事务性的 和 上发送和接收Session。因此,吞吐量取决于实现。请注意,这些单线程和非事务性约束仅适用于 Spring 的 JMS 远程处理支持。有关Spring 对基于 JMS 的消息传递的丰富支持的信息,请参阅JMS(Java 消息服务) 。

服务器端和客户端都使用以下接口:

package com.foo;

public interface CheckingAccountService {

    public void cancelAccount(Long accountId);
}

在服务器端使用上述接口的以下简单实现:

package com.foo;

public class SimpleCheckingAccountService implements CheckingAccountService {

    public void cancelAccount(Long accountId) {
        System.out.println("Cancelling account [" + accountId + "]");
    }
}

以下配置文件包含在客户端和服务器上共享的 JMS 基础结构 bean:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://ep-t43:61616"/>
    </bean>

    <bean id="queue" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg value="mmm"/>
    </bean>

</beans>

2.7.1。服务器端配置

在服务器上,您需要公开使用 的服务对象 JmsInvokerServiceExporter,如以下示例所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="checkingAccountService"
            class="org.springframework.jms.remoting.JmsInvokerServiceExporter">
        <property name="serviceInterface" value="com.foo.CheckingAccountService"/>
        <property name="service">
            <bean class="com.foo.SimpleCheckingAccountService"/>
        </property>
    </bean>

    <bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destination" ref="queue"/>
        <property name="concurrentConsumers" value="3"/>
        <property name="messageListener" ref="checkingAccountService"/>
    </bean>

</beans>
package com.foo;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Server {

    public static void main(String[] args) throws Exception {
        new ClassPathXmlApplicationContext("com/foo/server.xml", "com/foo/jms.xml");
    }
}

2.7.2. 客户端配置

客户端只需要创建一个客户端代理来实现约定的接口(CheckingAccountService)。

以下示例定义了可以注入其他客户端对象的 bean(代理负责通过 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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="checkingAccountService"
            class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean">
        <property name="serviceInterface" value="com.foo.CheckingAccountService"/>
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="queue" ref="queue"/>
    </bean>

</beans>
package com.foo;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Client {

    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("com/foo/client.xml", "com/foo/jms.xml");
        CheckingAccountService service = (CheckingAccountService) ctx.getBean("checkingAccountService");
        service.cancelAccount(new Long(10));
    }
}

3. 企业 JavaBeans (EJB) 集成

作为轻量级容器,Spring 通常被认为是 EJB 的替代品。我们确实相信,对于许多(如果不是大多数)应用程序和用例,Spring 作为一个容器,结合其在事务、ORM 和 JDBC 访问领域的丰富支持功能,是比通过 EJB 实现等效功能更好的选择容器和 EJB。

但是,重要的是要注意使用 Spring 并不会阻止您使用 EJB。事实上,Spring 使得访问 EJB 和在其中实现 EJB 和功能变得更加容易。此外,使用 Spring 访问 EJB 提供的服务允许这些服务的实现稍后在本地 EJB、远程 EJB 或 POJO(普通旧 Java 对象)变体之间透明地切换,而无需更改客户端代码。

在本章中,我们将了解 Spring 如何帮助您访问和实现 EJB。Spring 在访问无状态会话 bean (SLSB) 时提供了特殊的价值,因此我们从讨论这个主题开始。

3.1。访问 EJB

本节介绍如何访问 EJB。

3.1.1。概念

要调用本地或远程无状态会话 bean 上的方法,客户端代码通常必须执行 JNDI 查找以获取(本地或远程)EJB Home 对象,然后使用create该对象上的方法调用来获取实际的(本地或远程) EJB 对象。然后在 EJB 上调用一个或多个方法。

为了避免重复的低级代码,许多 EJB 应用程序使用 Service Locator 和 Business Delegate 模式。这些比在整个客户端代码中喷洒 JNDI 查找要好,但它们通常的实现有明显的缺点:

  • 通常,使用 EJB 的代码依赖于 Service Locator 或 Business Delegate 单例,因此很难进行测试。

  • 在不使用业务委托的情况下使用服务定位器模式的情况下,应用程序代码最终仍然必须调用create()EJB 主目录上的方法并处理产生的异常。因此,它仍然与 EJB API 和 EJB 编程模型的复杂性相关联。

  • 实现业务委托模式通常会导致大量代码重复,我们必须编写大量方法来调用 EJB 上的相同方法。

Spring 方法是允许创建和使用代理对象(通常配置在 Spring 容器中),它们充当无代码业务委托。您无需编写另一个服务定位器、另一个 JNDI 查找或在手动编码的业务委托中重复方法,除非您实际上在此类代码中添加了真正的价值。

3.1.2。访问本地 SLSB

假设我们有一个需要使用本地 EJB 的 Web 控制器。我们遵循最佳实践并使用 EJB 业务方法接口模式,以便 EJB 的本地接口扩展非 EJB 特定的业务方法接口。我们称之为业务方法接口MyComponent。下面的例子展示了这样一个界面:

public interface MyComponent {
    ...
}

使用业务方法接口模式的主要原因之一是确保本地接口和 bean 实现类中的方法签名之间的同步是自动的。另一个原因是,如果这样做有意义的话,它使我们更容易切换到服务的 POJO(普通旧 Java 对象)实现。我们还需要实现本地home接口,并提供实现类SessionBeanMyComponent业务方法接口。现在,为了将 Web 层控制器连接到 EJB 实现,我们需要做的唯一 Java 编码就是在控制器上公开一个类型的 setter 方法MyComponent 。这会将引用保存为控制器中的实例变量。以下示例显示了如何执行此操作:

private MyComponent myComponent;

public void setMyComponent(MyComponent myComponent) {
    this.myComponent = myComponent;
}

我们随后可以在控制器中的任何业务方法中使用此实例变量。现在,假设我们从 Spring 容器中获取控制器对象,我们可以(在相同的上下文中)配置一个LocalStatelessSessionProxyFactoryBean实例,它是 EJB 代理对象。我们配置代理并 myComponent使用以下配置条目设置控制器的属性:

<bean id="myComponent"
        class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
    <property name="jndiName" value="ejb/myBean"/>
    <property name="businessInterface" value="com.mycom.MyComponent"/>
</bean>

<bean id="myController" class="com.mycom.myController">
    <property name="myComponent" ref="myComponent"/>
</bean>

许多工作发生在幕后,由 Spring AOP 框架提供,尽管您不必被迫使用 AOP 概念来享受结果。bean 定义为 EJB 创建了一个代理, myComponent它实现了业务方法接口。EJB 本地主目录在启动时被缓存,因此只有一个 JNDI 查找。每次调用 EJB 时,代理都会调用classname本地 EJB 上的方法,并调用 EJB 上相应的业务方法。

myControllerbean 定义将控制器类的属性myComponent设置为 EJB 代理。

或者(并且最好在许多这样的代理定义的情况下),考虑使用<jee:local-slsb>Spring 的“jee”命名空间中的配置元素。以下示例显示了如何执行此操作:

<jee:local-slsb id="myComponent" jndi-name="ejb/myBean"
        business-interface="com.mycom.MyComponent"/>

<bean id="myController" class="com.mycom.myController">
    <property name="myComponent" ref="myComponent"/>
</bean>

这种 EJB 访问机制极大地简化了应用程序代码。Web 层代码(或其他 EJB 客户端代码)不依赖于 EJB 的使用。要用 POJO 或模拟对象或其他测试存根替换此 EJB 引用,我们可以在myComponent不更改一行 Java 代码的情况下更改 bean 定义。此外,作为我们应用程序的一部分,我们不必编写一行 JNDI 查找或其他 EJB 管道代码。

实际应用程序中的基准和经验表明,这种方法(涉及目标 EJB 的反射调用)的性能开销很小,并且在典型使用中无法检测到。请记住,无论如何我们都不想对 EJB 进行细粒度的调用,因为应用服务器中的 EJB 基础结构会产生相关成本。

关于 JNDI 查找有一个警告。在 bean 容器中,此类通常最好用作单例(没有理由将其作为原型)。但是,如果该 bean 容器预先实例化了单例(各种 XML ApplicationContext变体也是如此),如果在 EJB 容器加载目标 EJB 之前加载了 bean 容器,那么您可能会遇到问题。那是因为在init()这个类的方法中进行了JNDI查找,然后缓存了,但是EJB还没有绑定到目标位置。解决方案是不预先实例化这个工厂对象,而是让它在第一次使用时创建。在 XML 容器中,您可以使用lazy-init属性来控制它。

尽管大多数 Spring 用户不感兴趣,但那些使用 EJB 进行编程 AOP 的人可能希望查看LocalSlsbInvokerInterceptor.

3.1.3. 访问远程 SLSB

访问远程 EJB 本质上与访问本地 EJB 相同,只是使用了 SimpleRemoteStatelessSessionProxyFactoryBeanor<jee:remote-slsb>配置元素。当然,不管有没有 Spring,远程调用语义都适用:对另一台计算机中另一个 VM 中的对象上的方法的调用有时必须在使用场景和故障处理方面进行不同的处理。

Spring 的 EJB 客户端支持比非 Spring 方法增加了一项优势。通常,在本地或远程调用 EJB 之间轻松地来回切换 EJB 客户端代码是有问题的。这是因为远程接口方法必须声明它们 throw RemoteException,并且客户端代码必须处理这个,而本地接口方法不需要。为需要移动到远程 EJB 的本地 EJB 编写的客户端代码通常必须进行修改以添加对远程异常的处理,并且为需要移动到本地 EJB 的远程 EJB 编写的客户端代码可以保持不变,但执行许多不必要的远程异常处理或被修改以删除该代码。使用 Spring 远程 EJB 代理,您可以不声明任何抛出的RemoteException在您的业务方法接口和实现 EJB 代码中,有一个相同的远程接口(除了它确实 throw RemoteException),并依靠代理动态地处理这两个接口,就好像它们是相同的一样。也就是说,客户端代码不必处理被检查的RemoteException类。在 EJB 调用期间抛出的任何实际RemoteException值都将作为非检查RemoteAccessException类重新抛出,该类是RuntimeException. 然后,您可以在本地 EJB 或远程 EJB(甚至纯 Java 对象)实现之间随意切换目标服务,而无需客户端代码知道或关心。当然,这是可选的:没有什么能阻止您RemoteException在业务界面中声明。

3.1.4。访问 EJB 2.x SLSB 与 EJB 3 SLSB

通过 Spring 访问 EJB 2.x 会话 Bean 和 EJB 3 会话 Bean 在很大程度上是透明的。Spring 的 EJB 访问器,包括<jee:local-slsb>and <jee:remote-slsb>工具,在运行时透明地适应实际的组件。如果找到主接口(EJB 2.x 样式),它们将处理主接口;如果没有可用的主接口(EJB 3 样式),它们将直接执行组件调用。

注意:对于 EJB 3 会话 Bean,您也可以有效地使用JndiObjectFactoryBean/ <jee:jndi-lookup>,因为在那里公开了完全可用的组件引用以用于普通的 JNDI 查找。定义显式<jee:local-slsb><jee:remote-slsb> 查找提供一致且更显式的 EJB 访问配置。

4. JMS(Java消息服务)

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

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

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

org.springframework.jms.support软件包提供JMSException翻译功能。转换将检查JMSException层次结构转换为未检查异常的镜像层次结构。如果javax.jms.JMSException存在任何已检查的特定于提供程序的子类,则此异常将包装在未检查的UncategorizedJmsException.

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

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

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

org.springframework.jms.config包提供了 jms命名空间的解析器实现以及用于配置侦听器容器和创建侦听器端点的 java config 支持。

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

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

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

4.1。使用 Spring JMS

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

4.1.1。使用JmsTemplate

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

仅使用JmsTemplate需要实现回调接口的代码,这些接口为它们提供了明确定义的高级合同。回调接口在给定调用代码提供的时MessageCreator创建一条消息。为了允许更复杂地使用 JMS API,提供 JMS 会话,并公开一个和 对。SessionJmsTemplateSessionCallbackProducerCallbackSessionMessageProducer

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

一些 JMS 提供程序允许通过ConnectionFactory. 这会导致调用 MessageProducer实例的send方法 ( send(Destination destination, Message message)) 使用与 JMS 规范中指定的不同的 QOS 默认值。因此,为了提供对 QOS 值的一致管理,JmsTemplate必须通过将布尔属性设置为 来专门启用它以使用其自己的 QOS isExplicitQosEnabledtrue

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

一旦配置,该类的实例JmsTemplate是线程安全的。这很重要,因为这意味着您可以配置 a 的单个实例,JmsTemplate 然后安全地将这个共享引用注入到多个协作者中。需要明确的是,JmsTemplate是有状态的,因为它维护对 a 的引用 ConnectionFactory,但这种状态不是会话状态。

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

4.1.2. 连接

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

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

缓存消息资源

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

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

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

使用SingleConnectionFactory

Spring 提供了ConnectionFactory接口 的实现,它在所有 调用SingleConnectionFactory中返回相同的值,并忽略对 的调用。这对于测试和独立环境很有用,因此同一连接可用于 可能跨越任意数量事务的多个调用。 引用通常来自 JNDI 的标准。ConnectioncreateConnection()close()JmsTemplateSingleConnectionFactoryConnectionFactory

使用CachingConnectionFactory

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

临时队列和主题(TemporaryQueue/TemporaryTopic)的 MessageProducers 和 MessageConsumers 永远不会被缓存。不幸的是,WebLogic JMS 恰好在其常规目标实现上实现了临时队列/主题接口,错误地表明它的任何目标都不能被缓存。请在 WebLogic 上使用不同的连接池/缓存,或CachingConnectionFactory针对 WebLogic 目的进行自定义。

4.1.3。目的地管理

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

很多时候,JMS 应用程序中使用的目标仅在运行时才知道,因此在部署应用程序时无法以管理方式创建。这通常是因为交互系统组件之间存在共享应用程序逻辑,这些组件在运行时根据众所周知的命名约定创建目标。尽管创建动态目的地不是 JMS 规范的一部分,但大多数供应商都提供了此功能。动态目的地是使用用户定义的名称创建的,这将它们与临时目的地区分开来,并且通常不会在 JNDI 中注册。用于创建动态目的地的 API 因提供商而异,因为与目的地关联的属性是特定于供应商的。然而,TopicSession createTopic(String topicName)QueueSession createQueue(String queueName)使用默认目标属性创建新目标的方法。根据供应商的实施,DynamicDestinationResolver还可以创建一个物理目标,而不是只解析一个。

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

您还可以JmsTemplate通过属性配置默认目的地defaultDestination。默认目标是不涉及特定目标的发送和接收操作。

4.1.4。消息侦听器容器

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

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

Spring 打包了两个标准的 JMS 消息侦听器容器,每个容器都有其专门的功能集。

使用SimpleMessageListenerContainer

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

虽然SimpleMessageListenerContainer不允许参与外部管理的事务,但它确实支持本机 JMS 事务。要启用此功能,您可以将sessionTransacted标志切换为 ,true或者在 XML 命名空间中,将 acknowledge属性设置为transacted。监听器抛出的异常会导致回滚,消息会重新传递。或者,考虑使用 CLIENT_ACKNOWLEDGE模式,它也提供在异常情况下的重新传递,但不使用事务Session实例,因此不包括 Session事务协议中的任何其他操作(例如发送响应消息)。
默认AUTO_ACKNOWLEDGE模式不提供适当的可靠性保证。当侦听器执行失败(因为提供者在侦听器调用后自动确认每条消息,没有异常传播到提供者)或侦听器容器关闭时(您可以通过设置acceptMessagesWhileStopping标志进行配置),消息可能会丢失。确保在需要可靠性的情况下使用事务会话(例如,用于可靠的队列处理和持久的主题订阅)。
使用DefaultMessageListenerContainer

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

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

当代理出现故障时,此容器还具有可恢复的功能。默认情况下,一个简单的BackOff实现每五秒重试一次。BackOff您可以为更细粒度的恢复选项指定自定义实现。参见 ExponentialBackOff示例。

与其兄弟 ( SimpleMessageListenerContainer) 一样, DefaultMessageListenerContainer支持本机 JMS 事务并允许自定义确认模式。如果对您的方案可行,强烈建议在外部管理的事务上执行此操作 - 也就是说,如果您可以忍受偶尔出现的重复消息,以防 JVM 死机。业务逻辑中的自定义重复消息检测步骤可以涵盖此类情况——例如,以业务实体存在检查或协议表检查的形式。任何这样的安排都比替代方案更有效:用 XA 事务包装你的整个处理(通过配置你 DefaultMessageListenerContainerJtaTransactionManager) 来涵盖 JMS 消息的接收以及消息侦听器中业务逻辑的执行(包括数据库操作等)。
默认AUTO_ACKNOWLEDGE模式不提供适当的可靠性保证。当侦听器执行失败(因为提供者在侦听器调用后自动确认每条消息,没有异常传播到提供者)或侦听器容器关闭时(您可以通过设置acceptMessagesWhileStopping标志进行配置),消息可能会丢失。确保在需要可靠性的情况下使用事务会话(例如,用于可靠的队列处理和持久的主题订阅)。

4.1.5。事务管理

Spring 提供了一个JmsTransactionManager管理单个 JMS 事务的方法 ConnectionFactory。这让 JMS 应用程序可以利用 Spring 的托管事务特性,如 数据访问一章的事务管理部分所述。执行本地资源事务,将JmsTransactionManager指定的 JMS 连接/会话对绑定ConnectionFactory到线程。 JmsTemplate自动检测此类事务资源并相应地对其进行操作。

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

您还可以使用JmsTemplate支持JtaTransactionManagerXA 的 JMS ConnectionFactory来执行分布式事务。请注意,这需要使用 JTA 事务管理器以及正确配置 XA 的 ConnectionFactory。(检查您的 Java EE 服务器或 JMS 提供程序的文档。)

使用 JMS APISessionConnection. 这是因为 JMS API 只有一个工厂方法来创建Session,并且它需要事务和确认模式的值。在托管环境中,设置这些值是环境事务基础架构的责任,因此供应商的 JMS 连接包装器会忽略这些值。在非托管环境中使用 时JmsTemplate ,可以通过使用属性sessionTransacted和来指定这些值sessionAcknowledgeMode。当您使用 PlatformTransactionManagerwithJmsTemplate时,模板总是被赋予一个事务性 JMS Session

4.2. 发送消息

包含许多方便的JmsTemplate方法来发送消息。发送方法通过使用javax.jms.Destination对象来指定目的地,而其他方法通过String在 JNDI 查找中使用 a 来指定目的地。不带目的地参数的send方法使用默认目的地。

以下示例使用MessageCreator回调从提供的Session对象创建文本消息:

import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Queue;
import javax.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通过传递对 a 的引用来构造 ConnectionFactory。作为替代方案,提供了一个零参数构造函数, connectionFactory并可用于以 JavaBean 样式构造实例(使用BeanFactoryJava 代码或纯 Java 代码)。或者,考虑从 Spring 的便利基类派生JmsGatewaySupport,它为 JMS 配置提供预构建的 bean 属性。

send(String destinationName, MessageCreator creator)方法允许您使用目标的字符串名称发送消息。如果这些名称已在 JNDI 中注册,则应将destinationResolver模板的属性设置为 JndiDestinationResolver.

如果您创建JmsTemplate并指定了默认目标, 则会向该目标send(MessageCreator c)发送消息。

4.2.1。使用消息转换器

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

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

为了适应无法在转换器类中一般封装的消息属性、标头和正文的设置,该MessagePostProcessor接口允许您在消息转换之后但在发送之前访问消息。下面的例子展示了在 a 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;
        }
    });
}

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

地图消息={
    标头={
        ... 标准标题 ...
        相关 ID={123-00001}
    }
    属性={
        AccountID={整数:1234}
    }
    字段={
        名称={字符串:标记}
        年龄={整数:47}
    }
}

4.2.2. 使用SessionCallbackProducerCallback

虽然发送操作涵盖了许多常见的使用场景,但您有时可能希望在 JMSSessionMessageProducer. 和 分别公开 JMSSessionCallback和/对。 运行这些回调方法的方法。ProducerCallbackSessionSessionMessageProducerexecute()JmsTemplate

4.3. 接收消息

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

4.3.1。同步接收

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

4.3.2. 异步接收:消息驱动的 POJO

Spring 还通过使用注释来支持带注释的侦听器端点,@JmsListener 并提供一个开放的基础设施来以编程方式注册端点。到目前为止,这是设置异步接收器最方便的方法。有关更多详细信息,请参阅启用侦听器端点注释

以类似于 EJB 世界中的消息驱动 Bean (MDB) 的方式,消息驱动 POJO (MDP) 充当 JMS 消息的接收者。MDP的一个限制(但请参见 UsingMessageListenerAdapter)是它必须实现javax.jms.MessageListener接口。请注意,如果您的 POJO 在多个线程上接收消息,请务必确保您的实现是线程安全的。

以下示例显示了 MDP 的简单实现:

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

public class ExampleListener implements MessageListener {

    public void onMessage(Message message) {
        if (message instanceof TextMessage) {
            try {
                System.out.println(((TextMessage) message).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。

4.3.3. 使用SessionAwareMessageListener界面

SessionAwareMessageListener接口是一个特定于 Spring 的接口,它提供了与 JMS 接口类似的契约,MessageListener但也为消息处理方法提供了对接收消息的 JMS 的访问Session权限Message。以下清单显示了SessionAwareMessageListener接口的定义:

package org.springframework.jms.listener;

public interface SessionAwareMessageListener {

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

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

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

4.3.4. 使用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。还要注意各种消息处理方法如何根据Message它们可以接收和处理的各种类型的内容进行强类型化。

现在考虑接口的以下实现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,它只能处理接收 JMS TextMessage消息。注意消息处理方法的实际调用方式 receive(消息处理方法的名称MessageListenerAdapter 默认为handleMessage),但它是可配置的(如您在本节后面看到的那样)。还要注意该receive(..)方法是如何被强类型化为只接收和响应 JMS TextMessage消息的。以下清单显示了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接收到的 JMSMessage类型不是TextMessageIllegalStateException则抛出 an (并随后吞下)。该类的另一个功能是,如果处理程序方法返回非 void 值,MessageListenerAdapter则能够自动发回响应。Message考虑以下接口和类:

public interface ResponsiveTextMessageDelegate {

    // notice the return type...
    String receive(TextMessage message);
}
public class DefaultResponsiveTextMessageDelegate implements ResponsiveTextMessageDelegate {
    // implementation elided for clarity...
}

如果将DefaultResponsiveTextMessageDelegate与 a 结合使用 MessageListenerAdapter,则从方法执行返回的任何非空值'receive(..)'(在默认配置中)都将转换为 a TextMessage。然后将结果TextMessage发送到Destination(如果存在)在Reply-To原始的 JMS 属性中定义的Message或默认Destination设置的MessageListenerAdapter(如果已配置)。如果没有Destination找到,InvalidDestinationException则抛出 an(请注意,此异常不会被吞没并向上传播调用堆栈)。

4.3.5。处理事务中的消息

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

sessionTransacted您可以通过侦听器容器定义上的标志激活本地资源事务。然后,每个消息侦听器调用在活动的 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(默认情况下,它委托给 Java EE 服务器的事务子系统)。请注意,底层 JMSConnectionFactory需要支持 XA 并在您的 JTA 事务协调器中正确注册。(检查您的 Java 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 我们的交易经理。

4.4. 支持 JCA 消息端点

从 2.5 版开始,Spring 还提供对基于 JCA 的 MessageListener容器的支持。JmsMessageEndpointManager尝试根据提供者的类名自动确定类ActivationSpecResourceAdapter。因此,通常可以提供 Spring 的 generic 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>

JmsMessageEndpointManager或者,您可以使用给定的 ActivationSpec对象设置 a 。该ActivationSpec对象也可能来自 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="javax.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 或更高版本),您可以改为ResourceAdapter从 JNDI 获取整个对象(通过使用<jee:jndi-lookup>)。然后,基于 Spring 的消息侦听器可以与 server-hosted 交互ResourceAdapter,后者也使用服务器的内置WorkManager.

有关更多详细信息,请参阅 、 和的JmsMessageEndpointManagerjavadoc JmsActivationSpecConfigResourceAdapterFactoryBean

Spring 还提供了一个与 JMS 无关的通用 JCA 消息端点管理器: org.springframework.jca.endpoint.GenericMessageEndpointManager. 该组件允许使用任何消息侦听器类型(例如 JMS MessageListener)和任何特定于提供程序的ActivationSpec对象。请参阅您的 JCA 提供者的文档以了解您的连接器的实际功能,并参阅 GenericMessageEndpointManager javadoc 以了解特定于 Spring 的配置详细信息。

基于 JCA 的消息端点管理非常类似于 EJB 2.1 消息驱动 Bean。它使用相同的底层资源提供者合约。与 EJB 2.1 MDB 一样,您也可以在 Spring 上下文中使用 JCA 提供者支持的任何消息侦听器接口。尽管如此,Spring 还是为 JMS 提供了明确的“便利”支持,因为 JMS 是与 JCA 端点管理契约一起使用的最常见的端点 API。

4.5. 注释驱动的侦听器端点

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

@Component
public class MyService {

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

前面示例的想法是,只要消息在 上可用,就会 相应地调用javax.jms.Destination myDestinationprocessOrder方法(在这种情况下,使用 JMS 消息的内容,类似于MessageListenerAdapter 提供的内容)。

带注释的端点基础设施通过使用JmsListenerContainerFactory. 这样的容器没有针对应用程序上下文注册,但可以通过使用JmsListenerEndpointRegistrybean 轻松定位以进行管理。

@JmsListener@JmsListener 是 Java 8 上的可重复注解,因此您可以通过向其添加额外声明将 多个 JMS 目标与同一方法相关联。

4.5.1。启用侦听器端点注释

要启用对@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,以供工厂用来创建消息侦听器容器。processOrder 在这种情况下(并忽略 JMS 基础结构设置),您可以使用三个线程的核心轮询大小和最大池大小为十个线程来调用该方法。

您可以自定义侦听器容器工厂以用于每个注释,也可以通过实现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>

4.5.2. 程序化端点注册

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.

4.5.3. 带注释的端点方法签名

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

@Component
public class MyService {

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

您可以在 JMS 侦听器端点中注入的主要元素如下:

  • rawjavax.jms.Message或其任何子类(前提是它与传入的消息类型匹配)。

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

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

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

  • 一个@Headers-annotated 参数,也必须可分配给以java.util.Map访问所有标题。

  • Message不是受支持类型(或 )之一的未注释元素Session被视为有效负载。您可以通过使用 注释参数来明确这一点@Payload。您还可以通过添加额外的 @Valid.

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

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

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

例如,如果我们想Order在处理它之前确保它是有效的,我们可以使用注释@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;
    }
}

4.5.4. 响应管理

现有的支持MessageListenerAdapter 已经让您的方法具有非void返回类型。在这种情况下,调用的结果被封装在 a 中javax.jms.Message,发送JMSReplyTo到原始消息标头中指定的目标或侦听器上配置的默认目标。您现在可以使用@SendTo消息传递抽象的注释来设置该默认目标。

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

@JmsListener(destination = "myDestination")
@SendTo("status")
public OrderStatus processOrder(Order order) {
    // order processing
    return status;
}
如果您有多个@JmsListener-annotated 方法,您还可以将@SendTo 注释放在类级别以共享默认回复目标。

如果您需要以与传输无关的方式设置其他标头,则可以返回 a Message,方法类似于以下:

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

4.6. JMS 命名空间支持

Spring 提供了一个 XML 命名空间来简化 JMS 配置。要使用 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 引用 JMS 模式。

命名空间由三个顶级元素组成<annotation-driven/><listener-container/><jca-listener-container/><annotation-driven/>启用注释驱动的侦听器端点<listener-container/><jca-listener-container/> 定义共享侦听器容器配置,并且可以包含<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中所示。除了前面示例中显示的属性之外,该listener元素还可以包含几个可选的属性。下表描述了所有可用的属性:

表 3. JMS <listener> 元素的属性
属性 描述

id

托管侦听器容器的 bean 名称。如果未指定,则会自动生成一个 bean 名称。

destination(必需的)

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

ref(必需的)

处理程序对象的 bean 名称。

method

要调用的处理程序方法的名称。如果该ref属性指向 aMessageListener 或 Spring SessionAwareMessageListener,则可以省略该属性。

response-destination

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

subscription

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

selector

此侦听器的可选消息选择器。

concurrency

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

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

JmsListenerContainerFactory您可以通过指定id要通过属性公开的 bean 来自动公开此类设置factory-id,如以下示例所示:

<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 还讨论了事务选择和消息重新传递方案。

表 4. JMS <listener-container> 元素的属性
属性 描述

container-type

此侦听器容器的类型。可用选项为defaultsimpledefault102simple102(默认选项为default)。

container-class

自定义侦听器容器实现类作为完全限定的类名。根据属性,默认是 Spring 的标准DefaultMessageListenerContainer或 。SimpleMessageListenerContainercontainer-type

factory-id

将此元素定义的设置公开为JmsListenerContainerFactory 带有指定的 a ,id以便它们可以与其他端点重用。

connection-factory

对 JMS ConnectionFactorybean 的引用(默认 bean 名称是 connectionFactory)。

task-executor

TaskExecutorJMS 侦听器调用程序对 Spring 的引用。

destination-resolver

DestinationResolver对解析 JMSDestination实例的策略的引用。

message-converter

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

error-handler

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

destination-type

此侦听器的 JMS 目标类型:queuetopicdurableTopicsharedTopicsharedDurableTopic。这可能会启用容器的pubSubDomainsubscriptionDurable 属性subscriptionShared。默认值为queue(禁用这三个属性)。

response-destination-type

响应的 JMS 目标类型:queuetopic. 默认值是 destination-type属性的值。

client-id

此侦听器容器的 JMS 客户端 ID。使用持久订阅时必须指定它。

cache

JMS 资源的缓存级别:noneconnectionsessionconsumerauto. 默认auto情况consumernone

acknowledge

本机 JMS 确认模式:autoclientdups-oktransacted. 的值transacted激活本地交易Session。作为替代方案,您可以指定transaction-manager属性,稍后在表中进行描述。默认值为auto.

transaction-manager

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

concurrency

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

prefetch

加载到单个会话中的最大消息数。请注意,提高此数字可能会导致并发消费者饥饿。

receive-timeout

用于接收呼叫的超时(以毫秒为单位)。默认值为1000(一秒)。-1表示没有超时。

back-off

指定BackOff用于计算恢复尝试间隔的实例。如果BackOffExecution实现返回BackOffExecution#STOP,则侦听器容器不会进一步尝试恢复。设置此属性时忽略该recovery-interval 值。默认值为FixedBackOff5000 毫秒(即 5 秒)的间隔。

recovery-interval

指定恢复尝试之间的时间间隔,以毫秒为单位。它提供了一种方便的方法来创建FixedBackOff具有指定间隔的 a。如需更多恢复选项,请考虑指定一个BackOff实例。默认值为 5000 毫秒(即 5 秒)。

phase

此容器应启动和停止的生命周期阶段。该值越低,该容器启动得越早,停止得越晚。默认值为 Integer.MAX_VALUE,这意味着容器尽可能晚地启动并尽快停止。

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

<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 变体的可用配置选项:

表 5. JMS <jca-listener-container/> 元素的属性
属性 描述

factory-id

将此元素定义的设置公开为JmsListenerContainerFactory 带有指定的 a ,id以便它们可以与其他端点重用。

resource-adapter

对 JCA ResourceAdapterbean 的引用(默认 bean 名称是 resourceAdapter)。

activation-spec-factory

JmsActivationSpecFactory. 默认设置是自动检测 JMS 提供程序及其ActivationSpec类(请参阅 参考资料DefaultJmsActivationSpecFactory)。

destination-resolver

DestinationResolver对解决 JMS的策略的参考Destinations

message-converter

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

destination-type

此侦听器的 JMS 目标类型:queue, topic, durableTopic, sharedTopic. 或sharedDurableTopic。这可能会启用容器的pubSubDomainsubscriptionDurablesubscriptionShared属性。默认值为queue(禁用这三个属性)。

response-destination-type

响应的 JMS 目标类型:queuetopic. 默认值是 destination-type属性的值。

client-id

此侦听器容器的 JMS 客户端 ID。使用持久订阅时需要指定。

acknowledge

本机 JMS 确认模式:autoclientdups-oktransacted. 的值transacted激活本地交易Session。作为替代方案,您可以指定transaction-manager稍后描述的属性。默认值为auto.

transaction-manager

对 Spring 的引用JtaTransactionManagerjavax.transaction.TransactionManager用于为每个传入消息启动 XA 事务的引用。如果未指定,则使用本地确认(请参阅 acknowledge属性)。

concurrency

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

prefetch

加载到单个会话中的最大消息数。请注意,提高此数字可能会导致并发消费者饥饿。

5.JMX

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

JMX?

本章不是对 JMX 的介绍。它没有试图解释为什么您可能想要使用 JMX。如果您是 JMX 新手,请参阅本章末尾的更多资源。

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

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

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

  • MBean 在远程 JSR-160 连接器上的声明性公开。

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

这些功能旨在无需将应用程序组件耦合到 Spring 或 JMX 接口和类即可工作。实际上,在大多数情况下,您的应用程序类无需了解 Spring 或 JMX 即可利用 Spring JMX 特性。

5.1。将 Bean 导出到 JMX

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

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 定义是exporter bean。该beans属性MBeanExporter准确地告诉您哪些 bean 必须导出到 JMX MBeanServer。在默认配置中,每个条目的 keybeans Map被用作ObjectName对应条目值所引用的 bean。您可以更改此行为,如Controlling ObjectNameInstances for Your Beans中所述。

使用此配置,testBeanbean 在 ObjectName bean:name=testBean1. 默认情况下,publicbean 的所有属性都作为属性公开,所有public方法(从类继承的方法除外 Object)都作为操作公开。

MBeanExporter是一个Lifecyclebean(请参阅启动和关闭回调)。默认情况下,在应用程序生命周期中尽可能晚地导出 MBean。您可以通过设置标志 来配置phase导出发生的时间或禁用自动注册。autoStartup

5.1.1。创建 MBeanServer

上一节中显示的配置假定应用程序正在运行的环境中运行一个(并且只有一个)MBeanServer 已经运行。在这种情况下,Spring 会尝试定位正在运行MBeanServer的 bean 并将您的 bean 注册到该服务器(如果有)。当您的应用程序在具有自己的MBeanServer.

但是,这种方法在独立环境中或在不提供MBeanServer. 为了解决这个问题,您可以 通过将类的实例添加到您的配置来以MBeanServer声明方式创建一个实例 。您还可以通过将 实例属性的值设置为 an 返回 的值org.springframework.jmx.support.MBeanServerFactoryBean来确保使用特定的,如以下示例所示:MBeanServerMBeanExporterserverMBeanServerMBeanServerFactoryBean

<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提供给。当您提供自己的 实例时,不会尝试查找正在运行 的实例并使用提供的实例。要使其正常工作,您的类路径上必须有 JMX 实现。MBeanExporterserverMBeanServerMBeanExporterMBeanServerMBeanServer

5.1.2. 重用现有的MBeanServer

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

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

5.1.3. 延迟初始化的 MBean

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

5.1.4。MBean 的自动注册

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

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

在前面的示例中,调用的 beanspring:mbean=true已经是一个有效的 JMX MBean,并且由 Spring 自动注册。默认情况下,自动检测到 JMX 注册的 bean 将其 bean 名称用作ObjectName. 您可以覆盖此行为,如Controlling ObjectNameInstances for Your Beans中所述。

5.1.5。控制注册行为

考虑SpringMBeanExporter尝试MBean 使用MBeanServer. ObjectName bean:name=testBean1如果一个MBean 实例已经在相同的 下注册ObjectName,则默认行为是失败(并抛出一个InstanceAlreadyExistsException)。

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

表 6. 注册行为
注册行为 解释

FAIL_ON_EXISTING

这是默认注册行为。如果一个MBean实例已经在 same 下注册ObjectNameMBean则正在注册的 没有注册,并且InstanceAlreadyExistsException抛出 an。现有 MBean的不受影响。

IGNORE_EXISTING

如果一个MBean实例已经在 same 下注册,则正在注册的那个没有注册ObjectNameMBean现有MBean的不受影响,并且没有Exception抛出。这在多个应用程序想要MBean在一个共享的MBeanServer.

REPLACE_EXISTING

如果一个MBean实例已经在 same 下注册,则先前注册 ObjectName的现有实例将被取消注册,而新实例将在其位置注册(新实例有效地替换了先前的实例)。MBeanMBeanMBean

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

以下示例显示了如何从默认注册行为更改为该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>

5.2. 控制 Bean 的管理界面

在上一节的示例中,您几乎无法控制 bean 的管理界面。每个导出的 bean 的所有public 属性和方法分别公开为 JMX 属性和操作。为了对导出的 bean 的哪些属性和方法实际公开为 JMX 属性和操作进行更细粒度的控制,Spring JMX 提供了一种全面且可扩展的机制来控制 bean 的管理接口。

5.2.1。使用MBeanInfoAssembler界面

在幕后,MBeanExporter代表 org.springframework.jmx.export.assembler.MBeanInfoAssembler接口的实现,它负责定义每个暴露的 bean 的管理接口。默认实现 org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler定义了一个管理接口,该接口公开所有公共属性和方法(如您在前面部分的示例中所见)。Spring 提供了两个额外的MBeanInfoAssembler接口实现,让您可以使用源级元数据或任意接口控制生成的管理接口。

5.2.2. 使用源级元数据:Java 注释

通过使用MetadataMBeanInfoAssembler,您可以使用源级元数据为您的 bean 定义管理接口。元数据的读取由org.springframework.jmx.export.metadata.JmxAttributeSource接口封装。Spring JMX 提供了一个使用 Java 注解的默认实现,即 org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource. 您必须MetadataMBeanInfoAssembler使用接口的实现实例JmxAttributeSource配置它才能正常运行(没有默认值)。

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

带注释的ManagedResourcebean 必须是公共的,公开操作或属性的方法也必须是公共的。

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

package org.springframework.jmx;

import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedAttribute;

@ManagedResource(
        objectName="bean:name=testBean4",
        description="My Managed Bean",
        log=true,
        logFile="jmx.log",
        currencyTimeLimit=15,
        persistPolicy="OnUpdate",
        persistPeriod=200,
        persistLocation="foo",
        persistName="bar")
public class AnnotationTestBean implements IJmxTestBean {

    private String name;
    private int age;

    @ManagedAttribute(description="The Age Attribute", currencyTimeLimit=15)
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @ManagedAttribute(description="The Name Attribute",
            currencyTimeLimit=20,
            defaultValue="bar",
            persistPolicy="OnUpdate")
    public void setName(String name) {
        this.name = name;
    }

    @ManagedAttribute(defaultValue="foo", persistPeriod=300)
    public String getName() {
        return name;
    }

    @ManagedOperation(description="Add two numbers")
    @ManagedOperationParameters({
        @ManagedOperationParameter(name = "x", description = "The first number"),
        @ManagedOperationParameter(name = "y", description = "The second number")})
    public int add(int x, int y) {
        return x + y;
    }

    public void dontExposeMe() {
        throw new RuntimeException();
    }

}

在前面的示例中,您可以看到JmxTestBean该类被标记了 ManagedResource注解,并且该ManagedResource注解配置了一组属性。这些属性可用于配置由 生成的 MBean 的各个方面,稍后将在Source-level Metadata TypesMBeanExporter中进行更详细的说明。

agename属性都使用注解进行注解ManagedAttribute ,但是,对于age属性,只有 getter 被标记。这会导致这两个属性都作为属性包含在管理界面中,但该age属性是只读的。

最后,add(int, int)方法用ManagedOperation属性标记,而dontExposeMe()方法没有。当add(int, int)您使用MetadataMBeanInfoAssembler.

以下配置显示了如何配置MBeanExporter以使用 MetadataMBeanInfoAssembler

<beans>
    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="assembler" ref="assembler"/>
        <property name="namingStrategy" ref="namingStrategy"/>
        <property name="autodetect" value="true"/>
    </bean>

    <bean id="jmxAttributeSource"
            class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>

    <!-- will create management interface using annotation metadata -->
    <bean id="assembler"
            class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
        <property name="attributeSource" ref="jmxAttributeSource"/>
    </bean>

    <!-- will pick up the ObjectName from the annotation -->
    <bean id="namingStrategy"
            class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
        <property name="attributeSource" ref="jmxAttributeSource"/>
    </bean>

    <bean id="testBean" class="org.springframework.jmx.AnnotationTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>
</beans>

在前面的例子中,一个MetadataMBeanInfoAssemblerbean 已经配置了一个类的实例,并通过 assembler 属性AnnotationJmxAttributeSource传递给了。MBeanExporter这就是为 Spring 公开的 MBean 利用元数据驱动的管理接口所需的全部内容。

5.2.3。源级元数据类型

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

表 7. 源级元数据类型
目的 注解 注释类型

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

@ManagedResource

班级

将方法标记为 JMX 操作。

@ManagedOperation

方法

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

@ManagedAttribute

方法(仅 getter 和 setter)

定义操作参数的描述。

@ManagedOperationParameter@ManagedOperationParameters

方法

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

表 8. 源级元数据参数
范围 描述 适用于

ObjectName

用于MetadataNamingStrategy确定ObjectName托管资源的属性。

ManagedResource

description

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

ManagedResource, ManagedAttribute, ManagedOperation, 或ManagedOperationParameter

currencyTimeLimit

设置currencyTimeLimit描述符字段的值。

ManagedResource或者ManagedAttribute

defaultValue

设置defaultValue描述符字段的值。

ManagedAttribute

log

设置log描述符字段的值。

ManagedResource

logFile

设置logFile描述符字段的值。

ManagedResource

persistPolicy

设置persistPolicy描述符字段的值。

ManagedResource

persistPeriod

设置persistPeriod描述符字段的值。

ManagedResource

persistLocation

设置persistLocation描述符字段的值。

ManagedResource

persistName

设置persistName描述符字段的值。

ManagedResource

name

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

ManagedOperationParameter

index

设置操作参数的索引。

ManagedOperationParameter

5.2.4。使用AutodetectCapableMBeanInfoAssembler界面

为了进一步简化配置,Spring 包含了 AutodetectCapableMBeanInfoAssembler接口,它扩展了MBeanInfoAssembler 接口以添加对 MBean 资源自动检测的支持。如果您 MBeanExporter使用 的实例配置AutodetectCapableMBeanInfoAssembler,则允许对包含 bean 以暴露于 JMX 进行“投票”。

AutodetectCapableMBeanInfo接口的唯一实现是MetadataMBeanInfoAssembler,它投票包含任何标记有该ManagedResource属性的 bean。在这种情况下,默认方法是使用 bean 名称作为ObjectName,这会导致类似于以下的配置:

<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仍然被注册,因为它被标记了ManagedResource 属性并且MetadataMBeanInfoAssembler检测到这个并且投票包含它。这种方法的唯一问题是JmxTestBean现在的名称具有商业意义。您可以通过更改Controlling Instances for Your BeansObjectName 中定义的默认创建行为来解决此问题。ObjectName

5.2.5。使用 Java 接口定义管理接口

除了 之外MetadataMBeanInfoAssembler,Spring 还包括 InterfaceBasedMBeanInfoAssembler,它允许您根据接口集合中定义的方法集来约束公开的方法和属性。

尽管公开 MBean 的标准机制是使用接口和简单的命名方案,InterfaceBasedMBeanInfoAssembler但通过消除对命名约定的需求、允许您使用多个接口以及消除对 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>

在前面的示例中,InterfaceBasedMBeanInfoAssembler配置为在 IJmxTestBean为任何 bean 构造管理接口时使用该接口。重要的是要了解由 处理的 beanInterfaceBasedMBeanInfoAssembler 不需要实现用于生成 JMX 管理接口的接口。

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

如果没有通过managedInterfacesinterfaceMappings属性指定管理接口,则InterfaceBasedMBeanInfoAssembler反映在 bean 上并使用该 bean 实现的所有接口来创建管理接口。

5.2.6。使用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>

在前面的示例中,您可以看到addandmyOperation方法作为 JMX 操作公开,并且getName(), setName(String), 和getAge()作为 JMX 属性的适当一半公开。在前面的代码中,方法映射适用于暴露给 JMX 的 bean。要逐个 bean 控制方法公开,您可以使用 的methodMappings属性MethodNameMBeanInfoAssembler将 bean 名称映射到方法名称列表。

5.3. 控制ObjectNameBean 的实例

在幕后,MBeanExporter委托一个实现为 它注册的每个 beanObjectNamingStrategy获取一个ObjectName实例。默认情况下,默认实现KeyNamingStrategy使用 的键 beans Map作为ObjectName. 此外,KeyNamingStrategy可以将 的键映射到文件(或多个文件)中 beans Map的条目以解析. 除了 之外,Spring 还提供了两个额外 的实现:(基于 bean 的 JVM 标识构建)和( 使用源级元数据来获取)。PropertiesObjectNameKeyNamingStrategyObjectNamingStrategyIdentityNamingStrategyObjectNameMetadataNamingStrategyObjectName

5.3.1。ObjectName从属性中读取实例

您可以配置自己的KeyNamingStrategy实例并将其配置为 ObjectName从实例中读取实例,Properties而不是使用 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>

前面的示例配置了KeyNamingStrategy一个实例,该实例由映射属性定义的实例和位于映射属性定义的路径中的属性文件Properties合并而成。Properties在此配置中,testBeanbean 被赋予ObjectNameof bean:name=testBean1,因为这是Properties实例中具有与 bean 键对应的键的条目。

如果在实例中找不到条目,Properties​​则使用 bean 键名作为ObjectName.

5.3.2. 使用MetadataNamingStrategy

MetadataNamingStrategy使用每个 bean 上的属性objectNameManagedResource 属性来创建ObjectName. 以下代码显示了 的配置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>

如果没有objectName为该ManagedResource属性提供, ObjectName则使用以下格式创建一个:[fully-qualified-package-name]:type=[short-classname],name=[bean-name]。例如,ObjectName为以下 bean 生成的将是 com.example:type=MyClass,name=myBean

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

5.3.3. 配置基于注解的 MBean 导出

如果您更喜欢使用基于注解的方法来定义您的管理接口,可以使用一个方便的子类MBeanExporterAnnotationMBeanExporter. 定义此子类的实例时,您不再需要 namingStrategyassemblerattributeSource配置,因为它始终使用标准的基于 Java 注释的元数据(也始终启用自动检测)。事实上,注解MBeanExporter支持更简单的语法,而不是定义 bean,@EnableMBeanExport @Configuration如以下示例所示:

@Configuration
@EnableMBeanExport
public class AppConfig {

}

如果您更喜欢基于 XML 的配置,则该<context:mbean-export/>元素具有相同的用途,并显示在以下清单中:

<context:mbean-export/>

如有必要,您可以提供对特定 MBean 的引用server,并且 defaultDomain属性( 的属性AnnotationMBeanExporter)接受生成的 MBeanObjectName域的替代值。这用于代替上一节 MetadataNamingStrategy中描述的完全限定包名称,如以下示例所示:

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

}

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

<context:mbean-export server="myMBeanServer" default-domain="myDomain"/>
不要将基于接口的 AOP 代理与 bean 类中 JMX 注释的自动检测结合使用。基于接口的代理“隐藏”目标类,这也隐藏了 JMX 管理的资源注释。因此,在这种情况下,您应该使用目标类代理(通过设置 'proxy-target-class' 标志<aop:config/><tx:annotation-driven/>等等)。否则,您的 JMX bean 可能会在启动时被静默忽略。

5.4. 使用 JSR-160 连接器

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

5.4.1。服务器端连接器

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

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

默认情况下,ConnectorServerFactoryBean创建一个JMXConnectorServer绑定到 service:jmx:jmxmp://localhost:9875. 因此,该serverConnectorbeanMBeanServer通过 localhost 端口 9875 上的 JMXMP 协议向客户端公开本地。请注意,JSR 160 规范将 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>

如果ObjectName设置了该属性,Spring 会自动将您的连接器注册到MBeanServerObjectName. 以下示例显示了ConnectorServerFactoryBean在创建 时 可以传递给 的完整参数集JMXConnector

<bean id="serverConnector"
        class="org.springframework.jmx.support.ConnectorServerFactoryBean">
    <property name="objectName" value="connector:name=iiop"/>
    <property name="serviceUrl"
        value="service:jmx:iiop://localhost/jndi/iiop://localhost:900/myconnector"/>
    <property name="threaded" value="true"/>
    <property name="daemon" value="true"/>
    <property name="environment">
        <map>
            <entry key="someKey" value="someValue"/>
        </map>
    </property>
</bean>

请注意,当您使用基于 RMI 的连接器时,您需要启动查找服务 (tnameservrmiregistry) 才能完成名称注册。如果你使用 Spring 通过 RMI 为你导出远程服务,那么 Spring 已经构建了一个 RMI 注册中心。如果没有,您可以使用以下配置片段轻松启动注册表:

<bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
    <property name="port" value="1099"/>
</bean>

5.4.2. 客户端连接器

要创建MBeanServerConnection远程 JSR-160-enabled MBeanServer,您可以使用 MBeanServerConnectionFactoryBean,如以下示例所示:

<bean id="clientConnector" class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
    <property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"/>
</bean>

5.4.3. 基于 Hessian 或 SOAP 的 JMX

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

<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 文档。

5.5. 通过代理访问 MBean

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

<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
    <property name="objectName" value="bean:name=testBean"/>
    <property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/>
</bean>

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

MBeanProxyFactoryBean可以为任何可 通过MBeanServerConnection. 默认情况下,本地MBeanServer被定位并使用,但您可以覆盖它并提供一个MBeanServerConnection指向远程 MBeanServer的,以迎合指向远程 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>

在前面的示例中,我们创建了一个MBeanServerConnection指向使用MBeanServerConnectionFactoryBean. 然后MBeanServerConnection将其传递给MBeanProxyFactoryBeanthroughserver属性。MBeanServer创建的代理通过 this 将所有调用转发给MBeanServerConnection

5.6. 通知

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

5.6.1。为通知注册监听器

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

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(在前面的示例中定义)添加到notificationListenerMappings

<beans>

    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="beans">
            <map>
                <entry key="bean:name=testBean1" value-ref="testBean"/>
            </map>
        </property>
        <property name="notificationListenerMappings">
            <map>
                <entry key="bean:name=testBean1">
                    <bean class="com.example.ConsoleLoggingNotificationListener"/>
                </entry>
            </map>
        </property>
    </bean>

    <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>

</beans>

通过上述配置,每次Notification从目标 MBean ( bean:name=testBean1)广播 JMX 时,都会通知通过属性ConsoleLoggingNotificationListener注册为侦听器的 bean 。notificationListenerMappings然后ConsoleLoggingNotificationListenerbean 可以采取任何它认为合适的动作来响应Notification.

您还可以使用直接 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>

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

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

如果您需要执行相反的操作(即,针对 MBean 注册多个不同的侦听器),则必须改为使用notificationListenerslist 属性(优先于该notificationListenerMappings属性)。这一次,我们不是NotificationListener为单个 MBean 配置一个,而是配置 NotificationListenerBean实例。A将 a和要注册的(or )NotificationListenerBean封装 在 a 中。还封装了许多其他属性,例如可用于高级 JMX 通知场景的 a 和任意 handback 对象。NotificationListenerObjectNameObjectNamesMBeanServerNotificationListenerBeanNotificationFilter

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

前面的示例等效于第一个通知示例。然后,假设我们希望在每次 aNotification被提升时都获得一个 handback 对象,并且我们还希望Notifications通过提供 a 来过滤掉无关的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 通知模型”的部分。)

5.6.2. 发布通知

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

本节实际上只与通过MBeanExporter. 任何现有的用户定义 MBean 都应使用标准 JMX API 来发布通知。

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

如接口的javadoc中所述 NotificationPublisher ,通过该NotificationPublisher 机制发布事件的托管bean不负责通知监听器的状态管理。Spring 的 JMX 支持负责处理所有 JMX 基础结构问题。作为应用程序开发人员,您需要做的就是实现 NotificationPublisherAware接口并使用提供的NotificationPublisher实例开始发布事件。请注意,NotificationPublisher 在托管 bean 注册后设置MBeanServer.

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

在以下示例中,每次调用操作时都会导出JmxTestBean发布 实例:NotificationEventadd(int, int)

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 的耦合,那么就这样做。

5.7. 更多资源

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

6. 电子邮件

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

库依赖项

为了使用 Spring Framework 的电子邮件库,以下 JAR 需要位于应用程序的类路径中:

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

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

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

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

6.1。用法

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

public interface OrderManager {

    void placeOrder(Order order);

}

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

6.1.1. 基本MailSenderSimpleMailMessage用法

以下示例显示了如何在有人下订单时使用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>

6.1.2. 使用JavaMailSenderMimeMessagePreparator

本节描述了另一个OrderManager使用MimeMessagePreparator 回调接口的实现。在以下示例中,该mailSender属性是类型 JavaMailSender,以便我们能够使用 JavaMailMimeMessage类:

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

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

}
邮件代码是一个横切关注点,很可能是重构为自定义 Spring AOP 方面的候选者,然后可以在OrderManager目标上的适当连接点处运行。

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

6.2. 使用 JavaMailMimeMessageHelper

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

6.2.1. 发送附件和内联资源

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

附件

以下示例向您展示如何使用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);
内联资源

以下示例向您展示如何使用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);
MimeMessage通过使用指定的Content-IDidentifier1234在上面的示例中) 将内联资源添加到。添加文本和资源的顺序非常重要。请务必先添加文本,然后再添加资源。如果你反其道而行之,那就行不通了。

6.2.2. 使用模板库创建电子邮件内容

前面部分中显示的示例中的代码通过使用诸如message.setText(..). 这对于简单的情况很好,在上述示例的上下文中也可以,其目的是向您展示 API 的基础知识。

但是,在您的典型企业应用程序中,开发人员通常不使用前面显示的方法创建电子邮件的内容,原因有很多:

  • 在 Java 代码中创建基于 HTML 的电子邮件内容既乏味又容易出错。

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

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

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

7. 任务执行与调度

Spring Framework分别使用TaskExecutor和接口为任务的异步执行和调度提供了抽象。TaskSchedulerSpring 还具有在应用程序服务器环境中支持线程池或委托给 CommonJ 的那些接口的实现。最终,在公共接口后面使用这些实现可以抽象出 Java SE 5、Java SE 6 和 Java EE 环境之间的差异。

Spring 还具有集成类以支持使用Timer (自 1.3 以来的 JDK 的一部分)和 Quartz 调度程序(https://www.quartz-scheduler.org/)进行调度。您可以分别使用对或实例的FactoryBean可选引用来 设置这两个调度程序。此外,还有一个用于 Quartz Scheduler 和 的便利类,可让您调用现有目标对象的方法(类似于正常 操作)。TimerTriggerTimerMethodInvokingFactoryBean

7.1。Spring的TaskExecutor抽象

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

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

TaskExecutor最初创建它是为了在需要时为其他 Spring 组件提供用于线程池的抽象。ApplicationEventMulticaster、JMSAbstractMessageListenerContainer和 Quartz 集成 等组件都使用TaskExecutor抽象来池线程。但是,如果您的 bean 需要线程池行为,您也可以根据自己的需要使用此抽象。

7.1.1. TaskExecutor类型

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

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

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

  • ConcurrentTaskExecutor:这个实现是一个java.util.concurrent.Executor实例的适配器。有一个替代方法 ( ThreadPoolTaskExecutor) 将Executor 配置参数公开为 bean 属性。很少需要 ConcurrentTaskExecutor直接使用。但是,如果ThreadPoolTaskExecutor它对您的需求不够灵活,ConcurrentTaskExecutor则可以选择。

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

  • WorkManagerTaskExecutor:此实现使用 CommonJWorkManager作为其支持服务提供者,并且是在 Spring 应用程序上下文中在 WebLogic 或 WebSphere 上设置基于 CommonJ 的线程池集成的中心便利类。

  • DefaultManagedTaskExecutor:此实现使用ManagedExecutorService在兼容 JSR-236 的运行时环境(例如 Java EE 7+ 应用程序服务器)中获得的 JNDI,为此目的替换了 CommonJ WorkManager。

7.1.2. 用一个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>

7.2. Spring的TaskScheduler抽象

除了TaskExecutor抽象之外,Spring 3.0 还引入了TaskScheduler 一种用于调度任务以在未来某个时间点运行的多种方法。以下清单显示了TaskScheduler接口定义:

public interface TaskScheduler {

    ScheduledFuture schedule(Runnable task, Trigger trigger);

    ScheduledFuture schedule(Runnable task, Instant startTime);

    ScheduledFuture schedule(Runnable task, Date startTime);

    ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);

    ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);

    ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);

    ScheduledFuture scheduleAtFixedRate(Runnable task, long period);

    ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);

    ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);

    ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);

    ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);
}

schedule最简单的方法是仅采用 aRunnable和 a的命名方法Date。这会导致任务在指定时间后运行一次。所有其他方法都能够安排任务重复运行。固定速率和固定延迟方法用于简单的周期性执行,但接受 a 的方法Trigger要灵活得多。

7.2.1. Trigger界面

Trigger接口本质上受到 JSR-236 的启发,截至 Spring 3.0,该接口尚未正式实现。其基本思想Trigger是可以根据过去的执行结果甚至任意条件来确定执行时间。如果这些确定确实考虑了先前执行的结果,则该信息可在TriggerContext. Trigger界面本身非常简单,如以下清单所示:

public interface Trigger {

    Date nextExecutionTime(TriggerContext triggerContext);
}

是最TriggerContext重要的部分。它封装了所有相关数据,并在必要时开放以供将来扩展。这 TriggerContext是一个接口(SimpleTriggerContext默认使用一个实现)。以下清单显示了Trigger实现的可用方法。

public interface TriggerContext {

    Date lastScheduledExecutionTime();

    Date lastActualExecutionTime();

    Date lastCompletionTime();
}

7.2.2. Trigger实现

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

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

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

7.2.3. TaskScheduler实现

与 Spring 的TaskExecutor抽象一样,这种TaskScheduler 安排的主要好处是应用程序的调度需求与部署环境分离。当部署到不应由应用程序本身直接创建线程的应用程序服务器环境时,此抽象级别特别相关。对于这样的场景,Spring 提供了一种TimerManagerTaskScheduler 委托给TimerManagerWebLogic 或 WebSphere 上的 CommonJ 的方法,以及最近的 DefaultManagedTaskScheduler一种委托给ManagedScheduledExecutorService Java EE 7+ 环境中的 JSR-236 的方法。两者通常都配置有 JNDI 查找。

每当不需要外部线程管理时,更简单的替代方法是ScheduledExecutorService在应用程序中进行本地设置,可以通过 Spring 的ConcurrentTaskScheduler. 为方便起见,Spring 还提供了 a ThreadPoolTaskScheduler,它在内部委托 aScheduledExecutorService 以提供常见的 bean 样式配置,类似于ThreadPoolTaskExecutor. 这些变体在宽松的应用程序服务器环境中也非常适合本地嵌入式线程池设置——尤其是在 Tomcat 和 Jetty 上。

7.3. 对调度和异步执行的注解支持

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

7.3.1. 启用计划注释

要启用对@Scheduled@Async注释的支持,您可以向其中一个类添加@EnableScheduling和 ,如以下示例所示:@EnableAsync@Configuration

@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}

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

如果您更喜欢 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

处理@Async注解的默认建议模式是proxy只允许通过代理拦截调用。同一类中的本地调用不能以这种方式被拦截。对于更高级的拦截模式,请考虑切换到aspectj结合编译时或加载时编织的模式。

7.3.2. @Scheduled注释_

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

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

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

例如,前面的例子也可以写成如下。

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

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

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

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

@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
}
您还可以使用该zone属性来指定解析 cron 表达式的时区。

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

从 Spring Framework 4.3 开始,@Scheduled任何范围的 bean 都支持方法。

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

7.3.3. @Async注释_

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

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

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

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

甚至可以异步调用返回值的方法。但是,此类方法需要具有- 类型的Future返回值。这仍然提供了异步执行的好处,以便调用者可以在调用它之前执行其他 get()任务Future。以下示例显示了如何@Async在返回值的方法上使用:

@Async
Future<String> returnSomething(int i) {
    // this will be run asynchronously
}
@Async方法不仅可以声明常规java.util.concurrent.Future返回类型,还可以声明 Springorg.springframework.util.concurrent.ListenableFuture或从 Spring 4.2 开始的 JDK 8 java.util.concurrent.CompletableFuture,以便与异步任务进行更丰富的交互并与进一步的处理步骤进行即时组合。

您不能@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();
    }

}
没有直接的 XML 等效@Async项,因为此类方法首先应设计为异步执行,而不是在外部重新声明为异步。AsyncExecutionInterceptor但是,您可以结合自定义切入点使用 Spring AOP 手动设置 Spring 。

7.3.4. 执行人资格与@Async

默认情况下,在指定@Async方法时,使用的执行器是启用异步支持时配置的执行器,即如果您使用 XML 或您的AsyncConfigurer 实现(如果有),则为“注释驱动”元素。但是, 当您需要指示在执行给定方法时应该使用默认值以外的执行器时,您可以使用注解的value属性。@Async以下示例显示了如何执行此操作:

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

在这种情况下,"otherExecutor"可以是ExecutorSpring 容器中任何 bean 的名称,也可以是与 any 关联的限定符的名称Executor(例如,由<qualifier>元素或 Spring 的@Qualifier注释指定)。

7.3.5。异常管理@Async

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

public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

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

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

7.4. 命名task空间

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

7.4.1。“调度器”元素

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

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

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

7.4.2. executor元素_

下面创建一个ThreadPoolTaskExecutor实例:

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

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

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

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

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

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

以下示例显示了一个元素,该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"/>

7.4.3. “计划任务”元素

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

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

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

调度程序由外部元素引用,每个单独的任务都包括其触发器元数据的配置。在前面的示例中,该元数据定义了一个具有固定延迟的周期性触发器,该延迟指示每个任务执行完成后要等待的毫秒数。另一个选项是 fixed-rate,指示该方法应该多久运行一次,而不管之前的执行需要多长时间。此外,对于fixed-delayfixed-rate任务,您可以指定一个“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"/>

7.5。Cron 表达式

所有 Spring cron 表达式都必须符合相同的格式,无论您是在 @Scheduledannotationstask:scheduled-taskselements还是其他地方使用它们。一个格式良好的 cron 表达式,例如* * * * * *,由六个空格分隔的时间和日期字段组成,每个字段都有自己的有效值范围:

┌────────────── 秒 (0-59)
 │ ┌───────────── 分钟 (0 - 59)
 │ │ ┌───────────── 小时 (0 - 23)
 │ │ │ ┌───────────── 一个月中的某天 (1 - 31)
 │ │ │ │ ┌───────────── 月(1-12)(或JAN-DEC)
 │ │ │ │ │ ┌───────────── 星期几(0 - 7)
 │ │ │ │ │ │ (0 或 7 为星期日,或 MON-SUN)
 │ │ │ │ │ │
 * * * * * *

有一些适用的规则:

  • 一个字段可以是一个星号 ( *),它始终代表“first-last”。对于月份或星期几字段,?可以使用问号 ( ) 代替星号。

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

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

  • 跟随范围(或*/指定数字值在范围内的间隔。

  • 英文名称也可用于 day-of-month 和 day-of-week 字段。使用特定日期或月份的前三个字母(不区分大小写)。

  • day-of-month 和 day-of-week 字段可以包含一个L字符,其含义不同

    • 在 day-of-month 字段中,L代表该月的最后一天。如果后跟一个负偏移量(即L-n),则表示n该月的倒数第二天

    • 在 day-of-week 字段中,L代表一周的最后一天。如果以数字或三字母名称 ( dLor DDDL) 为前缀,则表示该月中一周的最后一天 ( d orDDD ) 。

  • day-of-month 字段可以是nW,表示离该月最近的工作日n。如果n落在星期六,这将产生它之前的星期五。如果n是星期天,这会产生之后的星期一,如果n1并且落在星期六,也会发生这种情况(即:1W代表该月的第一个工作日)。

  • 如果 day-of-month 字段为LW,则表示该月的最后一个工作日

  • day-of-week 字段可以是d#n(or DDD#n),它代表月中n星期几d (orDDD ) 。

这里有些例子:

cron 表达式 意义

0 0 * * * *

每天每个小时的顶部

*/10 * * * * *

每十秒

0 0 8-10 * * *

每天 8 点、9 点和 10 点

0 0 6,19 * * *

每天早上 6:00 和晚上 7:00

0 0/30 8-10 * * *

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

0 0 9-17 * * MON-FRI

工作日朝九晚五

0 0 0 25 DEC ?

每个圣诞节的午夜

0 0 0 L * *

每月最后一天的午夜

0 0 0 L-3 * *

每月倒数第三天午夜

0 0 0 * * 5L

每月最后一个星期五午夜

0 0 0 * * THUL

每月最后一个星期四的午夜

0 0 0 1W * *

每月第一个工作日的午夜

0 0 0 LW * *

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

0 0 0 ? * 5#2

每月第二个星期五午夜

0 0 0 ? * MON#1

每月第一个星期一的午夜

7.5.1. 宏

诸如0 0 * * * *人类难以解析的表达式,因此在出现错误时难以修复。为了提高可读性,Spring 支持以下表示常用序列的宏。您可以使用这些宏来代替六位数的值,因此:@Scheduled(cron = "@hourly").

意义

@yearly(或@annually

一年一次 ( 0 0 0 1 1 *)

@monthly

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

@weekly

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

@daily(或@midnight

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

@hourly

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

7.6. 使用 Quartz 调度器

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

7.6.1. 使用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包含一个名为 的 bean 属性timeout,并且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
    }
}

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

通过使用namegroup属性,您可以分别修改作业的名称和组。默认情况下,作业的名称与JobDetailFactoryBeanexampleJob在上面的示例中)的 bean 名称匹配。

7.6.2. 使用MethodInvokingJobDetailFactoryBean

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

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

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

public class ExampleBusinessObject {

    // properties and collaborators

    public void doIt() {
        // do the actual work
    }
}
<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>

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

默认情况下,Quartz Jobs 是无状态的,从而导致作业相互干扰的可能性。如果您为相同的 指定两个触发器JobDetail,则可能在第一个作业完成之前,第二个作业开始。如果 JobDetail类实现了Stateful接口,则不会发生这种情况。在第一个作业完成之前,第二个作业不会开始。要使由 MethodInvokingJobDetailFactoryBean非并发产生的作业,请将concurrent标志设置为 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>
默认情况下,作业将以并发方式运行。

7.6.3. 使用触发器连接作业和SchedulerFactoryBean

我们已经创建了工作细节和工作。我们还回顾了可让您调用特定对象的方法的便利 bean。当然,我们仍然需要自己安排作业。这是通过使用触发器和SchedulerFactoryBean. Quartz 中提供了几个触发器,Spring 提供了两个FactoryBean 具有方便默认值的 Quartz 实现:CronTriggerFactoryBeanSimpleTriggerFactoryBean.

需要安排触发器。Spring 提供了一个SchedulerFactoryBean将触发器设置为属性的方法。SchedulerFactoryBean使用这些触发器安排实际作业。

以下清单同时使用 aSimpleTriggerFactoryBean和 a CronTriggerFactoryBean

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
    <!-- see the example of method invoking job above -->
    <property name="jobDetail" ref="jobDetail"/>
    <!-- 10 seconds -->
    <property name="startDelay" value="10000"/>
    <!-- repeat every 50 seconds -->
    <property name="repeatInterval" value="50000"/>
</bean>

<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="jobDetail" ref="exampleJob"/>
    <!-- run every morning at 6 AM -->
    <property name="cronExpression" value="0 0 6 * * ?"/>
</bean>

前面的示例设置了两个触发器,一个每 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 数据源。有关更多信息,请参阅SchedulerFactoryBean javadoc。

SchedulerFactoryBeanquartz.properties还根据 Quartz 属性键识别类路径中的文件,与常规 Quartz 配置一样。请注意,许多 SchedulerFactoryBean设置与属性文件中的常见 Quartz 设置交互;因此,不建议在两个级别上指定值。例如,如果您要依赖 Spring 提供的 DataSource,请不要设置“org.quartz.jobStore.class”属性,或者指定org.springframework.scheduling.quartz.LocalDataSourceJobStore一个完全替代标准的变体org.quartz.impl.jdbcjobstore.JobStoreTX

8.缓存抽象

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

在 Spring Framework 4.1 中,缓存抽象显着扩展,支持JSR-107 注释和更多自定义选项。

8.1。理解缓存抽象

缓存与缓冲区

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

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

您可以在此处找到有关缓冲区和缓存之间差异的进一步说明 。

在其核心,缓存抽象将缓存应用于 Java 方法,从而根据缓存中可用的信息减少执行次数。也就是说,每次调用目标方法时,抽象都会应用缓存行为来检查是否已经为给定参数调用了该方法。如果已调用,则返回缓存的结果,而无需调用实际方法。如果没有调用该方法,则调用该方法,并将结果缓存并返回给用户,以便下次调用该方法时,返回缓存的结果。这样,对于给定的一组参数,昂贵的方法(无论是 CPU 还是 IO 绑定)只能被调用一次,并且结果可以重用,而不必再次实际调用该方法。

此方法仅适用于保证为给定输入(或参数)返回相同输出(结果)的方法,无论调用多少次。

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

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

Spring 提供了该抽象的一些实现java.util.concurrent.ConcurrentMap:基于 JDK的缓存、Ehcache 2.x、Gemfire 缓存、Caffeine和符合 JSR-107 的缓存(例如 Ehcache 3.x)。有关插入其他缓存存储和提供程序的更多信息,请参阅插入不同的后端缓存

缓存抽象对多线程和多进程环境没有特殊处理,因为这些功能由缓存实现处理。

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

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

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

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

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

8.2. 基于声明性注解的缓存

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

  • @Cacheable:触发​​缓存填充。

  • @CacheEvict: 触发缓存驱逐。

  • @CachePut:在不干扰方法执行的情况下更新缓存。

  • @Caching:重新组合多个缓存操作以应用于一个方法。

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

8.2.1。@Cacheable注释_

顾名思义,您可以使用@Cacheable来划分可缓存的方法——也就是说,将结果存储在缓存中的方法,以便在后续调用(使用相同的参数)时返回缓存中的值而无需实际调用该方法。在最简单的形式中,注解声明需要与被注解的方法关联的缓存的名称,如以下示例所示:

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

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

不包含该值的所有其他缓存也会更新,即使缓存的方法实际上并未被调用。

以下示例用于具有多个缓存@Cacheable的方法:findBook

@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}
默认密钥生成

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

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

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

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

这种方法适用于大多数用例,只要参数具有自然键并实现有效hashCode()的和equals()方法。如果不是这种情况,则需要更改策略。

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

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

如果您想继续使用以前的密钥策略,您可以配置已弃用的 org.springframework.cache.interceptor.DefaultKeyGenerator类或创建自定义的基于哈希的KeyGenerator实现。

自定义密钥生成声明

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

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

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

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

以下示例使用各种 SpEL 声明(如果您不熟悉 SpEL,请帮自己一个忙,阅读Spring Expression Language):

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

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

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

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

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

@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
keykeyGenerator参数是互斥的,指定两者的操作会导致异常 。
默认缓存分辨率

缓存抽象使用一个简单的方法CacheResolver来检索在操作级别定义的缓存,方法是使用配置的 CacheManager.

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

自定义缓存分辨率

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

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

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

您也可以CacheResolver完全以类似于替换key generation的方式替换。每个缓存操作都请求解析,让实现真正解析缓存以根据运行时参数使用。以下示例显示如何指定 a CacheResolver

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

从 Spring 4.1 开始,value缓存注解的属性不再是强制性的,因为CacheResolver 无论注解的内容如何,​​都可以提供此特定信息。

key与and类似keyGeneratorcacheManagerandcacheResolver 参数是互斥的,并且指定两者的操作会导致异常,因为实现CacheManager会忽略 自定义CacheResolver。这可能不是您所期望的。

同步缓存

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

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

@Cacheable(cacheNames="foos", sync=true) (1)
public Foo executeExpensiveOperation(String id) {...}
1 使用sync属性。
这是一个可选功能,您最喜欢的缓存库可能不支持它。CacheManager核心框架提供的所有实现都支持它。有关更多详细信息,请参阅缓存提供程序的文档。
条件缓存

有时,一个方法可能并不适合一直缓存(例如,它可能取决于给定的参数)。缓存注释通过 condition参数支持此类用例,该参数采用SpEL计算为true or的表达式false。如果true,则缓存该方法。如果没有,它的行为就好像该方法没有被缓存(也就是说,每次调用该方法,无论缓存中有什么值或使用什么参数)。例如,仅当参数name的长度小于 32 时才缓存以下方法:

@Cacheable(cacheNames="book", condition="#name.length() < 32") (1)
public Book findBook(String name)
1 设置条件@Cacheable

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

@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback") (1)
public Book findBook(String name)
1 使用该unless属性来阻止精装本。

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

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

请注意,#result仍然是指Book而不是Optional<Book>。既然是这样 null,我们就使用 SpEL 的安全导航算子

可用的缓存 SpEL 评估上下文

每个SpEL表达式都针对一个专用的context. 除了内置参数之外,该框架还提供了专用的缓存相关元数据,例如参数名称。下表描述了可用于上下文的项目,以便您可以将它们用于键和条件计算:

表 9. 缓存 SpEL 可用元数据
姓名 地点 描述 例子

methodName

根对象

被调用的方法的名称

#root.methodName

method

根对象

被调用的方法

#root.method.name

target

根对象

被调用的目标对象

#root.target

targetClass

根对象

被调用的目标的类

#root.targetClass

args

根对象

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

#root.args[0]

caches

根对象

运行当前方法的缓存集合

#root.caches[0].name

参数名称

评估上下文

任何方法参数的名称。如果名称不可用(可能是由于没有调试信息),则参数名称也可在#a<#arg> where#arg代表参数索引(从 开始0)下获得。

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

result

评估上下文

方法调用的结果(要缓存的值)。仅在unless 表达式、cache put表达式(计算key)或cache evict 表达式(when beforeInvocationis false)中可用。对于支持的包装器(例如 Optional),#result指的是实际对象,而不是包装器。

#result

8.2.2. @CachePut注释_

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

@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
通常强烈建议不要在同一方法上 使用@CachePut和注释,因为它们具有不同的行为。@Cacheable后者通过使用缓存导致方法调用被跳过,而前者强制调用以运行缓存更新。这会导致意外行为,并且除了特定的极端情况(例如具有将它们彼此排除的条件的注释)外,应避免此类声明。另请注意,此类条件不应依赖于结果对象(即#result变量),因为这些条件已预先验证以确认排除。

8.2.3. @CacheEvict注释_

缓存抽象不仅允许缓存存储的填充,还允许逐出。此过程对于从缓存中删除陈旧或未使用的数据很有用。与 相反 @Cacheable@CacheEvict划分了执行缓存驱逐的方法(即,充当从缓存中删除数据的触发器的方法)。与其兄弟相似,@CacheEvict需要指定一个或多个受操作影响的缓存,允许自定义缓存和键解析或指定条件,并具有一个额外的参数 ( allEntries),指示是否需要进行缓存范围的逐出执行而不仅仅是一个条目驱逐(基于密钥)。以下示例从books缓存中逐出所有条目:

@CacheEvict(cacheNames="books", allEntries=true) (1)
public void loadBooks(InputStream batch)
1 使用该allEntries属性从缓存中逐出所有条目。

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

beforeInvocation您还可以使用属性指示驱逐是应该在(默认)之后还是在调用方法之前发生。前者提供与其余注释相同的语义:一旦方法成功完成,就会运行缓存上的操作(在本例中为驱逐)。如果方法没有运行(因为它可能被缓存)或抛出异常,则不会发生驱逐。后者 ( beforeInvocation=true) 导致驱逐总是在调用方法之前发生。这在驱逐不需要与方法结果相关联的情况下很有用。

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

8.2.4. @Caching注释_

有时,需要指定多个相同类型的注解(例如@CacheEvictor @CachePut)——例如,因为不同缓存之间的条件或键表达式不同。允许在同一方法上使用@Caching多个嵌套 @Cacheable@CachePut、 和注释。@CacheEvict以下示例使用两个@CacheEvict注释:

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

8.2.5。@CacheConfig注释_

到目前为止,我们已经看到缓存操作提供了许多自定义选项,并且您可以为每个操作设置这些选项。但是,如果某些自定义选项适用于类的所有操作,则配置起来可能会很繁琐。例如,指定用于类的每个缓存操作的缓存名称可以由单个类级别定义替换。这就是@CacheConfig 发挥作用的地方。以下示例用于@CacheConfig设置缓存的名称:

@CacheConfig("books") (1)
public class BookRepositoryImpl implements BookRepository {

    @Cacheable
    public Book findBook(ISBN isbn) {...}
}
1 用于@CacheConfig设置缓存的名称。

@CacheConfig是一个类级注释,允许共享缓存名称、自定义KeyGenerator、自定义CacheManager和自定义CacheResolver。将此注释放在类上不会打开任何缓存操作。

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

  • 全局配置,可用于CacheManager, KeyGenerator

  • 在课堂级别,使用@CacheConfig.

  • 在操作层面。

8.2.6。启用缓存注释

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

要启用缓存注释,请将注释添加@EnableCaching到您的 @Configuration类之一:

@Configuration
@EnableCaching
public class AppConfig {
}

或者,对于 XML 配置,您可以使用以下cache:annotation-driven元素:

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

cache:annotation-driven元素和注释都@EnableCaching允许您指定各种选项,这些选项会影响通过 AOP 将缓存行为添加到应用程序的方式。该配置有意与 @Transactional.

处理缓存注释的默认建议模式是proxy,它只允许通过代理拦截调用。同一类中的本地调用不能以这种方式被拦截。对于更高级的拦截模式,请考虑切换到aspectj结合编译时或加载时编织的模式。
有关实现所需的高级自定义(使用 Java 配置)的更多详细信息CachingConfigurer,请参阅 javadoc
表 10. 缓存注释设置
XML 属性 注释属性 默认 描述

cache-manager

不适用(请参阅CachingConfigurerjavadoc)

cacheManager

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

cache-resolver

不适用(请参阅CachingConfigurerjavadoc)

ASimpleCacheResolver使用配置的cacheManager.

用于解析后备缓存的 CacheResolver 的 bean 名称。此属性不是必需的,仅需要指定为“缓存管理器”属性的替代项。

key-generator

不适用(请参阅CachingConfigurerjavadoc)

SimpleKeyGenerator

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

error-handler

不适用(请参阅CachingConfigurerjavadoc)

SimpleCacheErrorHandler

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

mode

mode

proxy

默认模式proxy(替代模式 ( aspectj) 使用 Spring 的 AspectJ 缓存方面编织受影响的类,修改目标类字节码以应用于任何类型的方法调用。AspectJ 编织需要spring-aspects.jar 在类路径中以及加载时编织(或编译时编织)启用。(有关 如何设置加载时编织的详细信息,请参阅Spring 配置。)

proxy-target-class

proxyTargetClass

false

仅适用于代理模式。@Cacheable控制为使用或注释注释的类创建什么类型的缓存代理@CacheEvict。如果该 proxy-target-class属性设置为true,则创建基于类的代理。如果proxy-target-classfalse或者如果省略该属性,则会创建标准的基于 JDK 接口的代理。(有关不同代理类型的详细检查,请参阅代理机制 。)

order

order

Ordered.LOWEST_PRECEDENCE

定义应用到用 @Cacheableor注释的 bean 的缓存通知的顺序@CacheEvict。(有关 AOP 通知排序规则的更多信息,请参阅Advice Ordering。)没有指定排序意味着 AOP 子系统决定通知的顺序。

<cache:annotation-driven/>@Cacheable/@CachePut/@CacheEvict/@Caching 仅在定义它的同一应用程序上下文中查找bean。这意味着,如果您放入<cache:annotation-driven/>a WebApplicationContextfor a DispatcherServlet,它只会检查您的控制器中的 bean,而不是您的服务。有关详细信息,请参阅MVC 部分
方法可见性和缓存注释

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

Spring 建议您只使用注解来注解具体类(和具体类的方法)@Cache*,而不是注解接口。您当然可以在接口(或接口方法)上放置@Cache*注释,但这仅在您使用代理模式(mode="proxy")时才有效。如果您使用基于编织的方面 ( mode="aspectj"),则编织基础结构在接口级声明上无法识别缓存设置。
在代理模式(默认)下,只拦截通过代理传入的外部方法调用。这意味着自调用(实际上,目标对象中的一个方法调用目标对象的另一个方法)不会在运行时导致实际缓存,即使调用的方法标记为@Cacheable. 在这种情况下考虑使用该aspectj模式。此外,代理必须完全初始化以提供预期的行为,因此您不应在初始化代码(即@PostConstruct)中依赖此功能。

8.2.7. 使用自定义注解

自定义注释和 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 注释,容器也会在运行时自动获取其声明并理解其含义。请注意, 如前所述,需要启用注释驱动的行为。

8.3. JCache (JSR-107) 注释

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

8.3.1. 功能摘要

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

表 11. Spring 与 JSR-107 缓存注释
Spring JSR-107 评论

@Cacheable

@CacheResult

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

@CachePut

@CachePut

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

@CacheEvict

@CacheRemove

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

@CacheEvict(allEntries=true)

@CacheRemoveAll

@CacheRemove

@CacheConfig

@CacheDefaults

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

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

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

@CacheResult(cacheNames="books", cacheResolverFactory=MyCacheResolverFactory.class) (1)
public Book findBook(ISBN isbn)
1 为此操作定制工厂。
对于所有引用的类,Spring 尝试定位具有给定类型的 bean。如果存在多个匹配项,则会创建一个新实例并可以使用常规 bean 生命周期回调,例如依赖注入。

密钥由 a 生成,javax.cache.annotation.CacheKeyGenerator其用途与 Spring 的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)

您还可以指定CacheKeyResolveron 操作,类似于指定CacheResolverFactory.

JCache 可以管理带注释的方法抛出的异常。这可以防止更新缓存,但它也可以将异常缓存为失败的指示符,而不是再次调用该方法。假设InvalidIsbnNotFoundException如果 ISBN 的结构无效则抛出该错误。这是一个永久性的失败(没有书可以用这样的参数检索)。以下缓存异常,以便进一步调用相同的、无效的 ISBN 直接抛出缓存的异常,而不是再次调用该方法:

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

8.3.2. 启用 JSR-107 支持

您不需要做任何特定的事情来启用 JSR-107 支持以及 Spring 的声明性注释支持。如果 JSR-107 API 和模块都存在于类路径中,那么两者@EnableCachingcache:annotation-driven XML 元素都会自动启用 JCache 支持 。spring-context-support

根据您的用例,选择基本上是您的。您甚至可以通过在某些服务上使用 JSR-107 API 并在其他服务上使用 Spring 自己的注释来混合和匹配服务。但是,如果这些服务影响相同的缓存,您应该使用一致且相同的密钥生成实现。

8.4. 声明式基于 XML 的缓存

如果注释不是一个选项(可能是由于无法访问源代码或没有外部代码),您可以使用 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是可缓存的。要应用的缓存语义被封装在cache:advice定义中,这导致findBooks 用于将数据放入缓存的loadBooks方法和用于驱逐数据的方法。这两个定义都对books缓存起作用。

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

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

8.5。配置缓存存储

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

8.5.1。基于JDKConcurrentMap的缓存

基于 JDK 的Cache实现位于 org.springframework.cache.concurrent包下。它使您可以ConcurrentHashMap 用作后备Cache存储。以下示例显示了如何配置两个缓存:

<!-- 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创建一个。请注意,直接为每个缓存配置名称。CacheManagerConcurrentMapCachedefaultbooks

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

8.5.2. 基于 Ehcache 的缓存

Ehcache 3.x 完全符合 JSR-107,不需要专门的支持。

Ehcache 2.x 实现位于org.springframework.cache.ehcache 包中。同样,要使用它,您需要声明适当的CacheManager. 以下示例显示了如何执行此操作:

<bean id="cacheManager"
        class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/>

<!-- EhCache library setup -->
<bean id="ehcache"
        class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="ehcache.xml"/>

此设置引导 Spring IoC 内的 ehcache 库(通过ehcache bean),然后将其连接到专用CacheManager实现中。请注意,整个 Ehcache 特定配置是从ehcache.xml.

8.5.3. 咖啡因缓存

Caffeine 是对 Guava 缓存的 Java 8 重写,其实现位于 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还支持自定义CaffeineCacheLoader. 有关这些内容的更多信息,请参阅咖啡因文档

8.5.4. 基于 GemFire 的缓存

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

8.5.5。JSR-107 缓存

Spring 的缓存抽象也可以使用符合 JSR-107 的缓存。JCache 实现位于org.springframework.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" .../>

8.5.6。在没有后备存储的情况下处理缓存

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

<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实例,并通过fallbackToNoOpCache标志为所有未由配置的缓存管理器处理的定义添加一个无操作缓存。也就是说,在jdkCacheor中未找到的每个缓存定义gemfireCache(在示例前面配置)都由 no-op 缓存处理,该缓存不存储任何信息,导致每次都调用目标方法。

8.6. 插入不同的后端缓存

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

8.7. 如何设置 TTL/TTI/Eviction 策略/XXX 功能?

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

9. 附录

9.1。XML 模式

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

9.1.1。jee架构_

这些jee元素处理与 Java EE(Java 企业版)配置相关的问题,例如查找 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/>(简单)

下面的例子展示了如何使用 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/>(使用单一 JNDI 环境设置)

下面的例子展示了如何使用 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/>(具有多个 JNDI 环境设置)

以下示例显示了如何使用 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/>(复杂的)

以下示例显示了如何使用 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/>(简单的)

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

以下示例显示了如何配置对本地 EJB 无状态会话 Bean 的引用,而不使用jee

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

以下示例显示如何配置对本地 EJB 无状态会话 Bean 的引用jee

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

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

以下示例显示了如何配置对本地 EJB 无状态会话 Bean 的引用和许多属性jee

<jee:local-slsb id="complexLocalEjb"
        jndi-name="ejb/RentalServiceBean"
        business-interface="com.foo.service.RentalService"
        cache-home="true"
        lookup-home-on-startup="true"
        resource-ref="true">
<jee:remote-slsb/>

<jee:remote-slsb/>元素配置对remoteEJB 无状态会话 Bean 的引用。

以下示例显示了如何配置对远程 EJB 无状态会话 Bean 的引用,而不使用jee

<bean id="complexRemoteEjb"
        class="org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean">
    <property name="jndiName" value="ejb/MyRemoteBean"/>
    <property name="businessInterface" value="com.foo.service.RentalService"/>
    <property name="cacheHome" value="true"/>
    <property name="lookupHomeOnStartup" value="true"/>
    <property name="resourceRef" value="true"/>
    <property name="homeInterface" value="com.foo.service.RentalService"/>
    <property name="refreshHomeOnConnectFailure" value="true"/>
</bean>

以下示例显示如何配置对远程 EJB 无状态会话 Bean 的引用jee

<jee:remote-slsb id="complexRemoteEjb"
        jndi-name="ejb/MyRemoteBean"
        business-interface="com.foo.service.RentalService"
        cache-home="true"
        lookup-home-on-startup="true"
        resource-ref="true"
        home-interface="com.foo.service.RentalService"
        refresh-home-on-connect-failure="true">

9.1.2. jms架构_

这些jms元素处理配置与 JMS 相关的 bean,例如 Spring 的 Message Listener Containers。这些元素在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>

9.1.3. 使用<context:mbean-export/>

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

9.1.4。cache架构_

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

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

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

1. see XML Configuration