参考文档的这一部分介绍了 Spring Framework 与 多项技术。

1. REST端点

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

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

  • WebClient:非阻塞、反应式替代方案 支持同步和异步以及流式处理方案。

从 5.0 开始,它处于维护模式,只有少量请求 今后要接受的更改和错误。请考虑使用提供更现代 API 和 支持同步、异步和流式处理方案。RestTemplate

1.1.RestTemplate

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

Table 1. RestTemplate methods
方法组 描述

getForObject

通过 GET 检索表示形式。

getForEntity

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

headForHeaders

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

postForLocation

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

postForObject

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

postForEntity

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

put

使用 PUT 创建或更新资源。

patchForObject

使用 PATCH 更新资源,并从响应中返回表示形式。 请注意,JDK 不支持 ,但 Apache HttpComponents 和其他人可以。HttpURLConnectionPATCH

delete

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

optionsForAllow

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

exchange

上述方法的更通用(和更少固执己见)版本,提供额外的 在需要时具有灵活性。它接受一个(包括 HTTP 方法、URL、标头、 和 body 作为输入),并返回一个 .RequestEntityResponseEntity

这些方法允许使用 而不是 to 指定 具有泛型的响应类型。ParameterizedTypeReferenceClass

execute

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

1.1.1. 初始化

用于执行请求的默认构造函数。您可以 切换到实现为 的其他 HTTP 库。 内置了对以下内容的支持:java.net.HttpURLConnectionClientHttpRequestFactory

  • Apache HttpComponents

  • 网状

  • 确定Http

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

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

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

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

许多方法接受 URI 模板和 URI 模板变量, 作为变量参数,或作为 .RestTemplateStringMap<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"

可以使用 的属性来自定义 URI 的方式 被编码。或者,您可以准备一个并将其传递到其中之一 接受 .uriTemplateHandlerRestTemplatejava.net.URIRestTemplateURI

有关使用和编码 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();

您可以通过许多返回 .RestTemplateResponseEntity

1.1.2. 正文

传入方法和从方法返回的对象将转换为原始对象或从原始方法转换 内容在.RestTemplateHttpMessageConverter

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

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

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

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

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

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

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

1.1.3. 消息转换

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

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

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

Table 2. HttpMessageConverter Implementations
消息转换器 描述

StringHttpMessageConverter

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

FormHttpMessageConverter

可以从HTTP读取和写入表单数据的实现 请求和响应。默认情况下,此转换器读取和写入媒体类型。从中读取表单数据并将其写入 .转换器还可以写入(但不能读取)多部分 从 .默认情况下,是 支持。从 Spring Framework 5.2 开始,可以支持其他多部分子类型 写入表单数据。有关更多详细信息,请参阅 javadoc。HttpMessageConverterapplication/x-www-form-urlencodedMultiValueMap<String, String>MultiValueMap<String, Object>multipart/form-dataFormHttpMessageConverter

ByteArrayHttpMessageConverter

一个可以从 HTTP 请求和响应。默认情况下,此转换器支持所有媒体类型 () 并用 的 .您可以覆盖它 通过设置属性并覆盖 .HttpMessageConverter*/*Content-Typeapplication/octet-streamsupportedMediaTypesgetContentType(byte[])

MarshallingHttpMessageConverter

一个实现,可以使用 Spring 的 XML 和包中的抽象来读取和写入 XML。 此转换器需要 and 才能使用。你可以注射这些 通过构造函数或 Bean 属性。默认情况下,此转换器支持 和 。HttpMessageConverterMarshallerUnmarshallerorg.springframework.oxmMarshallerUnmarshallertext/xmlapplication/xml

MappingJackson2HttpMessageConverter

可以使用 Jackson 的 .您可以根据需要使用 Jackson 的 提供了注释。当您需要进一步控制时(对于自定义 JSON 的情况 需要为特定类型提供序列化程序/解串程序),可以通过属性注入自定义。默认情况下,此 converter支持。HttpMessageConverterObjectMapperObjectMapperObjectMapperapplication/json

MappingJackson2XmlHttpMessageConverter

可以使用 Jackson XML 扩展的 .您可以根据需要通过使用 JAXB 来定制 XML 映射 或杰克逊提供的注释。当您需要进一步控制时(对于自定义 XML 需要为特定类型提供序列化程序/解串程序),可以通过属性注入自定义。默认情况下,此 converter支持。HttpMessageConverterXmlMapperXmlMapperObjectMapperapplication/xml

SourceHttpMessageConverter

可以从 HTTP 请求和响应中读取和写入的实现。仅支持 、 和 。默认情况下,此转换器支持 和 。HttpMessageConverterjavax.xml.transform.SourceDOMSourceSAXSourceStreamSourcetext/xmlapplication/xml

BufferedImageHttpMessageConverter

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

1.1.4. Jackson 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);

1.1.5. 多部分

要发送分段数据,您需要提供 whose 的值 可以是 for 部件内容、for 文件部件或 for 带有标题的部分内容。例如:MultiValueMap<String, Object>ObjectResourceHttpEntity

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

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

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

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

1.2. 使用(已弃用)AsyncRestTemplate

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

2. 远程处理和 Web 服务

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

  • Java Web Services:Spring 通过 JAX-WS 为 Web Service 提供远程处理支持。

  • AMQP:通过 AMQP 作为底层协议进行远程处理,由 单独的 Spring AMQP 项目。

从 Spring Framework 5.3 开始,现在不推荐使用对多种远程处理技术的支持 出于安全原因和更广泛的行业支持。支持性基础设施将被移除 从 Spring Framework 的下一个主要版本。

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

  • 远程方法调用(RMI):通过使用 和 ,Spring 既支持传统的 RMI(带有接口 和 ),也支持通过 RMI 进行透明远程处理 invokers(使用任何 Java 接口)。RmiProxyFactoryBeanRmiServiceExporterjava.rmi.Remotejava.rmi.RemoteException

  • Spring HTTP Invoker(已弃用):Spring 提供了一种特殊的远程处理策略,允许 通过 HTTP 进行 Java 序列化,支持任何 Java 接口(如 RMI invoker 确实如此)。相应的支持类是 和 。HttpInvokerProxyFactoryBeanHttpInvokerServiceExporter

  • Hessian:通过使用 Spring 和 ,你可以通过 Caucho提供的基于HTTP的轻量级二进制协议。HessianProxyFactoryBeanHessianServiceExporter

  • JMS(已弃用):通过模块中的 和 类支持通过 JMS 作为底层协议进行远程处理。JmsInvokerServiceExporterJmsInvokerProxyFactoryBeanspring-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 序列化。它与 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 调用程序以支持安全上下文传播 好吧,虽然这不是由 core Spring 提供的。弹簧只提供合适的钩子 用于插入第三方或自定义解决方案。

2.3. Java Web服务

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

  • 使用 JAX-WS 公开 Web Service

  • 使用 JAX-WS 访问 Web Service

除了 Spring Core 中对 JAX-WS 的库存支持外,Spring 产品组合还 具有 Spring Web Services,它是 契约优先、文档驱动的 Web 服务 — 强烈建议用于构建现代、 面向未来的 Web 服务。

2.3.1. 使用 JAX-WS 公开基于 Servlet 的 Web Service

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

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

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

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

Oracle JDK 附带的内置 JAX-WS 提供程序支持 Web 公开 服务,使用内置的 HTTP 服务器,该服务器也包含在 JDK 中。Spring's 检测 Spring 中所有带注释的 bean 应用程序上下文,并通过缺省 JAX-WS 服务器(JDK HTTP 服务器)。SimpleJaxWsServiceExporter@WebService

在此方案中,端点实例被定义为 Spring Bean 并进行管理 他们自己。它们已向 JAX-WS 引擎注册,但其生命周期长达 Spring 应用程序上下文。这意味着您可以应用 Spring 功能 (例如显式依赖项注入)到端点实例。注释驱动 注射贯穿也有效。下面的示例演示如何 定义这些 bean:@Autowired

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

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

...

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

@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 环境中是不可移植的。它主要用于非 EE 将 JAX-WS RI 嵌入到 Web 应用程序中的环境,例如 Tomcat。

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

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

2.3.4. 使用 JAX-WS 访问 Web Service

Spring 提供了两个工厂 Bean 来创建 JAX-WS Web 服务代理,即 和 .前者可以 仅返回一个 JAX-WS 服务类供我们使用。后者是成熟的 版本,可以返回实现业务服务接口的代理。 在以下示例中,我们用于为终结点创建代理(再次):LocalJaxWsServiceFactoryBeanJaxWsPortProxyFactoryBeanJaxWsPortProxyFactoryBeanAccountService

<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 服务。 对应于 .wsdl 文件。 对应于 .wsdl 文件中的服务名称。 对应于 .wsdl 文件中的端口名称。namespaceUritargetNamespaceserviceNameportName

访问 Web 服务很容易,因为我们有一个 Bean 工厂,将其公开为 一个名为 的接口。以下示例显示了如何连接它 在春天:AccountService

<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 需要端点接口 以及要注释的实现类 、 等。 附注。这意味着你不能(轻松)使用普通的 Java 接口和 实现类作为 JAX-WS 端点工件;您需要对它们进行注释 因此,首先。有关这些需求的详细信息,请查看 JAX-WS 文档。@WebService@SOAPBinding

2.4. RMI(已弃用)

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

通过使用 Spring 对 RMI 的支持,您可以通过 RMI 基础结构。完成此设置后,您基本上有一个类似的配置 到远程 EJB,除了没有标准的安全性支持 上下文传播或远程事务传播。Spring 确实为 当您使用 RMI 调用程序时,这种额外的调用上下文,因此您可以,例如, 插入安全框架或自定义安全凭据。

2.4.1. 使用导出服务RmiServiceExporter

使用 ,我们可以公开 AccountService 对象的接口 作为 RMI 对象。可以使用 访问该接口,也可以通过 在传统 RMI 服务的情况下,普通 RMI。显式的 支持通过 RMI 调用程序公开任何非 RMI 服务。RmiServiceExporterRmiProxyFactoryBeanRmiServiceExporter

我们首先必须在 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 注册表,明智的做法是不要干扰该注册表。 此外,服务名称用于绑定服务。因此,在前面的示例中, service 绑定在 。我们稍后使用此 URL 进行链接 客户端的服务。'rmi://HOST:1199/AccountService'

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

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>

这就是我们在客户端上支持远程帐户服务所需要做的全部工作。春天 透明地创建一个调用程序,并通过 远程启用帐户服务。在客户端,我们使用 .RmiServiceExporterRmiProxyFactoryBean

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

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

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

2.5.1. 黑森州

Hessian 通过 HTTP 进行通信,并使用自定义 servlet 进行通信。通过使用 Spring 的原则(参见 webmvc.html),我们可以连接这样一个 servlet 来公开您的服务。首先,我们必须在应用程序中创建一个新的 servlet, 如以下摘录所示:DispatcherServletweb.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 的原理,你可能会 知道现在您必须在目录中创建一个名为(以 servlet 名称命名)的 Spring 容器配置资源。 下一节将使用应用程序上下文。DispatcherServletremoting-servlet.xmlWEB-INF

或者,考虑使用 Spring 的 simpler .这样做 允许您在根应用程序上下文中嵌入远程导出器定义(通过 缺省值,在 中 ),具有单独的 servlet 定义 指向特定的出口商 bean。在这种情况下,每个 servlet 名称都需要与 其目标出口商。HttpRequestHandlerServletWEB-INF/applicationContext.xml

2.5.2. 使用HessianServiceExporter

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

<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 映射到服务),因此我们使用 used。因此,服务将通过其 Bean 名称指示的 URL 导出 在包含实例的映射(如前所述)中:。BeanNameUrlHandlerMappingDispatcherServlethttps://HOST:8080/remoting/AccountService

或者,您可以在根应用程序上下文中创建一个(例如, in ),如以下示例所示:HessianServiceExporterWEB-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。 具有相同的最终结果:导出器映射到位于 的请求路径。请注意,servlet 名称需要与 目标导出器。以下示例演示如何执行此操作:web.xml/remoting/AccountService

<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. 在客户端上链接服务

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

<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 的。普通的 HTTP 服务器安全机制可以 例如,通过使用安全功能来应用。通常 此处无需使用每用户安全凭据。相反,您可以使用您定义的共享凭据 在级别(类似于 JDBC ),如以下示例所示:web.xmlHessianProxyFactoryBeanDataSource

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

在前面的示例中,我们明确提到了 和 一个拦截器,只允许管理员和操作员调用 此应用程序上下文。BeanNameUrlHandlerMapping

前面的示例未显示一种灵活的安全基础结构。为 就安全性而言,更多选项,请查看 Spring Security 项目 在 https://spring.io/projects/spring-security/

2.6. Spring HTTP Invoker(已弃用)

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

与 Hessian 相反,Spring HTTP 调用程序都是使用自己的 slim 的轻量级协议 序列化机制并使用标准的 Java 序列化 通过 HTTP 公开服务的机制。如果你的论点,这有一个巨大的优势 返回类型是无法使用序列化进行序列化的复杂类型 Hessian 使用的机制(有关以下情况的更多注意事项,请参阅下一节 您选择远程处理技术)。

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

请注意由于不安全的 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 支持所提供的,Spring 的 HttpInvoker 支持提供了 。HessianServiceExporterorg.springframework.remoting.httpinvoker.HttpInvokerServiceExporter

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

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

此类导出器定义通过实例的标准公开 测绘设施,如黑森州部分所述。DispatcherServlet

或者,您可以在根应用程序上下文中创建一个 (例如,in ),如以下示例所示:HttpInvokerServiceExporter'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>

此外,您可以在 中为此导出器定义相应的 servlet,并使用 servlet 名称与目标导出器的 Bean 名称匹配,如以下示例所示:web.xml

<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 可以将你的调用转换为 对指向导出服务的 URL 的 HTTP POST 请求。以下示例 演示如何配置此排列:

<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 客户端。默认情况下,使用 JDK 的 HTTP 功能,但您也可以通过设置该属性来使用 Apache 客户端。 以下示例演示如何执行此操作:HttpInvokerProxyHttpComponentshttpInvokerRequestExecutor

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

2.7. JMS(已弃用)

从 Spring Framework 5.3 开始,不推荐使用 JMS 远程处理支持,并且不会被替换。

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

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

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. Enterprise JavaBeans (EJB) 集成

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

但是,需要注意的是,使用 Spring 并不妨碍您使用 EJB。 事实上,Spring 使得访问 EJB 和实现 EJB 和功能变得更加容易 在他们里面。此外,使用 Spring 访问 EJB 提供的服务允许 实现这些服务,以便以后在本地 EJB 之间透明地切换, 远程 EJB 或 POJO(普通的旧 Java 对象)变体,无需客户端代码 被更改。

在本章中,我们将了解 Spring 如何帮助您访问和实现 EJB。春天 在访问无状态会话 Bean (SLSB) 时提供特定值,因此我们开始 通过讨论这个话题。

3.1. 访问 EJB

本节介绍如何访问 EJB。

3.1.1. 概念

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

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

  • 通常,使用 EJB 的代码依赖于 Service Locator 或 Business Delegate 单例。 使其难以测试。

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

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

Spring 方法是允许创建和使用代理对象(通常 在 Spring 容器中配置),充当无代码业务委托。你需要 不要在 手动编码的业务委托,除非您实际上在此类代码中添加了实际价值。

3.1.2. 访问本地 SLSB

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

public interface MyComponent {
    ...
}

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

private MyComponent myComponent;

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

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

<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 创建一个代理,该代理实现业务 方法接口。EJB 本地主目录在启动时被高速缓存,因此只有一个 JNDI 查找。每次调用 EJB 时,代理都会调用 本地 EJB 并在 EJB 上调用相应的业务方法。myComponentclassname

Bean 定义设置控制器的属性 类添加到 EJB 代理中。myControllermyComponent

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

<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 的使用。自 将此 EJB 引用替换为 POJO 或模拟对象或其他测试存根,我们可以 在不更改任何 Java 代码行的情况下更改 Bean 定义。 此外,我们不必编写任何一行 JNDI 查找或其他 EJB 管道 代码作为我们应用程序的一部分。myComponent

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

关于 JNDI 查找,有一个注意事项。在 Bean 容器中,此类是 通常最好用作单例(没有理由将其作为原型)。 但是,如果该 Bean 容器预先实例化了单例(就像各种 XML 变体一样),那么在加载 Bean 容器时可能会出现问题 在 EJB 容器装入目标 EJB 之前。这是因为 JNDI 查找是 在此类的方法中执行,然后缓存,但 EJB 尚未 尚未绑定在目标位置。解决方案是不要预先实例化它 factory 对象,但让它在第一次使用时创建。在 XML 容器中,您可以对此进行控制 通过使用属性。ApplicationContextinit()lazy-init

虽然大多数 Spring 用户不感兴趣,但那些 使用 EJB 的编程 AOP 工作可能需要查看 .LocalSlsbInvokerInterceptor

3.1.3. 访问远程 SLSB

访问远程 EJB 与访问本地 EJB 基本相同,只是 or 配置 元素。当然,无论有没有 Spring,远程调用语义都适用:A 有时必须对另一台计算机中另一个 VM 中的对象调用方法 在使用场景和故障处理方面区别对待。SimpleRemoteStatelessSessionProxyFactoryBean<jee:remote-slsb>

与非 Spring 方法相比,Spring 的 EJB 客户端支持又增加了一个优势。 通常,EJB 客户端代码很容易地来回切换是有问题的 在本地或远程调用 EJB 之间。这是因为远程接口方法 必须声明它们抛出,客户端代码必须处理这个问题, 而本地接口方法则不需要。为需要 要移动到远程 EJB,通常必须进行修改,以添加对远程 异常,以及为需要移动到本地的远程 EJB 编写的客户端代码 EJB 可以保持不变,但对远程执行大量不必要的处理 例外或修改以删除该代码。使用 Spring 远程 EJB proxy,你不能在你的业务方法中声明任何抛出的东西 接口和实现 EJB 代码,具有相同的远程接口(除了 它确实抛出),并依靠代理来动态处理两者 接口,就好像它们是相同的。也就是说,客户端代码不必处理 已检查类。在 EJB 调用作为未检查的类被重新抛出,该类 是 的子类。然后,您可以随意切换目标服务 在本地 EJB 或远程 EJB(甚至是纯 Java 对象)实现之间,没有 客户端代码知道或关心。当然,这是可选的:什么都没有 阻止您在业务界面中声明。RemoteExceptionRemoteExceptionRemoteExceptionRemoteExceptionRemoteExceptionRemoteAccessExceptionRuntimeExceptionRemoteException

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

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

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

4. JMS(Java 消息服务)

Spring 提供了一个 JMS 集成框架,该框架简化了 JMS API 的使用。 与 Spring 对 JDBC API 的集成方式相同。

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

该软件包提供了使用 JMS的。它包含 JMS 模板类,这些类通过处理 资源的创建和发布,与JDBC的操作非常相似。这 Spring 模板类常见的设计原则是提供辅助方法 执行常见操作,并且对于更复杂的用法,委托 将任务处理到用户实现的回调接口。JMS 模板遵循 相同的设计。这些类提供了各种方便的发送消息的方法, 同步使用消息,并将 JMS 会话和消息生产者公开给 用户。org.springframework.jms.coreJdbcTemplate

该软件包提供翻译 功能性。转换将选中的层次结构转换为 未经检查的异常的镜像层次结构。如果任何特定于提供商 的子类存在,此异常包装在 猖獗。org.springframework.jms.supportJMSExceptionJMSExceptionjavax.jms.JMSExceptionUncategorizedJmsException

该包提供了一个抽象,用于在 Java 对象和 JMS 消息之间进行转换。org.springframework.jms.support.converterMessageConverter

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

该软件包提供了必要的基础结构 通过使用来支持注释驱动的侦听器端点。org.springframework.jms.annotation@JmsListener

该软件包提供了命名空间的解析器实现,以及用于配置侦听器容器和 创建侦听器终端节点。org.springframework.jms.configjms

最后,该软件包提供了 适用于独立应用。它还包含一个 Spring's for JMS的实现(狡猾地命名)。这允许将 JMS 无缝集成为事务 资源添加到 Spring 的事务管理机制中。org.springframework.jms.connectionConnectionFactoryPlatformTransactionManagerJmsTransactionManager

从 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 约定,因此在进行相应的配置时,它遵循 JMS 1.1 约定 支持这样的场景。但是,请仅将此情况考虑为过渡方案。

4.1. 使用 Spring JMS

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

4.1.1. 使用JmsTemplate

该类是 JMS 核心包中的中心类。它简化了 使用 JMS,因为它在发送或 同步接收消息。JmsTemplate

仅使用 needs 来实现回调接口的代码,这些接口提供它们 明确定义的高级合同。回调接口创建一个 消息,当给定 中调用代码提供的消息。自 允许更复杂地使用 JMS API,提供 JMS 会话,并公开一个 和 对。JmsTemplateMessageCreatorSessionJmsTemplateSessionCallbackProducerCallbackSessionMessageProducer

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

某些 JMS 提供程序允许通过 的配置。这样做的效果是,调用实例的方法 () 使用的 QOS 缺省值与 JMS 规范中指定的缺省值不同。挨次 因此,为了提供一致的 QOS 值管理,必须 通过将 boolean 属性设置为 ,专门启用以使用其自己的 QOS 值。ConnectionFactoryMessageProducersendsend(Destination destination, Message message)JmsTemplateisExplicitQosEnabledtrue

为方便起见,还公开了一个基本的请求-答复操作,该操作允许 用于发送消息并等待作为 操作。JmsTemplate

类的实例一旦配置,就是线程安全的。这是 重要,因为这意味着您可以配置 A 的单个实例,然后安全地将此共享引用注入到多个协作者中。成为 显然,它是有状态的,因为它维护了对 的引用,但这种状态不是会话状态。JmsTemplateJmsTemplateJmsTemplateConnectionFactory

从 Spring Framework 4.1 开始,它建立在消息传递抽象之上,并提供了与消息传递抽象的集成,即 .这使您可以将消息创建到 以通用方式发送。JmsMessagingTemplateJmsTemplateorg.springframework.messaging.Message

4.1.2. 连接

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

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

缓存消息传递资源

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

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

在和操作之间,三个中间 对象被创建和销毁。优化资源使用并增加 性能,Spring 提供了两种实现。ConnectionFactorySendConnectionFactory

SingleConnectionFactory

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

CachingConnectionFactory

扩展了 、 和 实例的功能并添加了缓存。 初始高速缓存大小设置为 。您可以使用该属性来 增加缓存会话数。请注意,实际缓存的会话数 大于该数字,因为会话是根据其确认模式缓存的, 因此,最多可以有四个缓存会话实例(每个确认模式一个) when 设置为 1。 和实例 缓存在其拥有的会话中,并考虑唯一属性 缓存时的生产者和消费者。MessageProducer 根据其 目的地。MessageConsumers 基于由 destination、selector、 noLocal 传递标志和持久订阅名称(如果创建持久使用者)。CachingConnectionFactorySingleConnectionFactorySessionMessageProducerMessageConsumer1sessionCacheSizesessionCacheSizeMessageProducerMessageConsumer

用于临时队列和主题的 MessageProducers 和 MessageConsumers (TemporaryQueue/TemporaryTopic) 永远不会被缓存。不幸的是,WebLogic JMS 发生了 要在其常规目标实现上实现临时队列/主题接口, 错误地指示其任何目标都无法缓存。请使用其他连接 WebLogic 上的池/缓存,或针对 WebLogic 目的进行自定义。CachingConnectionFactory

4.1.3. 目的地管理

目标(作为实例)是您可以存储的 JMS 管理对象 并在 JNDI 中检索。配置 Spring 应用程序上下文时,可以使用 JNDI 工厂类或执行依赖关系 对对象对 JMS 目标的引用进行注入。但是,这种策略 如果应用程序中有大量目的地或有 是 JMS 提供程序独有的高级目标管理功能。示例 此类高级目的地管理包括创建动态目的地或 支持目标的分层命名空间。委托人 将目标名称解析为实现接口的 JMS 目标对象。 是默认值 实现使用并适应解析动态目标。此外,还提供了 A 作为服务定位器 目标包含在 JNDI 中,并可选择回退到 中包含的行为。ConnectionFactoryJndiObjectFactoryBean<jee:jndi-lookup>JmsTemplateDestinationResolverDynamicDestinationResolverJmsTemplateJndiDestinationResolverDynamicDestinationResolver

通常,JMS 应用程序中使用的目标仅在运行时是已知的,并且, 因此,在部署应用程序时,无法以管理方式创建。这是 通常是因为交互系统组件之间存在共享的应用程序逻辑 根据众所周知的命名约定在运行时创建目标。甚至 尽管动态目标的创建不是 JMS 规范的一部分,但大多数 供应商已提供此功能。动态目标使用用户定义的名称创建, 这使它们与临时目的地区分开来,并且通常是 未在JNDI注册。用于创建动态目标的 API 因提供商而异 提供程序,因为与目标关联的属性是特定于供应商的。 但是,供应商有时会做出一个简单的实现选择,即 忽略 JMS 规范中的警告,并使用该方法或该方法创建具有缺省目标属性的新目标。取决于 在供应商实现上,还可以创建一个 物理目的地,而不是只解析一个。TopicSessioncreateTopic(String topicName)QueueSessioncreateQueue(String queueName)DynamicDestinationResolver

boolean 属性用于配置 了解正在使用的 JMS 域。默认情况下,此属性的值为 false,表示要使用点对点域 。此属性 (使用者)通过 确定动态目标解析的行为 接口的实现。pubSubDomainJmsTemplateQueuesJmsTemplateDestinationResolver

您还可以通过 财产。默认目标是发送和接收 不引用特定目标的操作。JmsTemplatedefaultDestination

4.1.4. 消息监听器容器

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

消息侦听器容器用于从 JMS 消息队列接收消息,并且 驱动注入其中的。侦听器容器是 负责将消息接收和调度到侦听器的所有线程 加工。消息侦听器容器是 MDP 和 消息传递提供商并负责注册接收消息,参与 事务、资源获取和释放、异常转换等。这 允许您编写(可能很复杂的)业务逻辑 与接收消息(并可能响应消息)和委托相关联 样板 JMS 基础架构与框架有关。MessageListener

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

SimpleMessageListenerContainer

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

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

在大多数情况下使用此消息侦听器容器。与 相比,此容器变体允许动态适应 运行时需求,并能够参与外部管理的事务。 当配置了 .因此,处理可能会利用 XA 事务 语义学。这个侦听器容器在对 JMS 提供程序,高级功能(例如参与外部管理 transactions),以及与 Java EE 环境的兼容性。SimpleMessageListenerContainerJtaTransactionManager

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

当代理关闭时,此容器还具有可恢复的功能。默认情况下, 一个简单的实现每五秒重试一次。您可以指定 用于更精细恢复选项的自定义实现。有关示例,请参阅 ExponentialBackOffBackOffBackOff

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

4.1.5. 事务管理

Spring 提供了一个管理单个 JMS 事务的 。这允许 JMS 应用程序利用托管事务 Spring 的功能,如“数据访问”一章的“事务管理”部分所述。 执行本地资源事务,绑定 JMS 从指定到线程的连接/会话对。 自动检测此类事务资源并运行 相应地在他们身上。JmsTransactionManagerConnectionFactoryJmsTransactionManagerConnectionFactoryJmsTemplate

在 Java EE 环境中,池 Connection 和 Session 实例、 因此,这些资源可以在事务中有效地重用。在独立环境中, 在共享 JMS 中使用 Spring 的结果,并使用 每笔交易都有自己独立的.或者,考虑使用 特定于提供程序的池适配器,例如 ActiveMQ 的类。ConnectionFactorySingleConnectionFactoryConnectionSessionPooledConnectionFactory

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

在托管和非托管事务环境中重用代码可能会令人困惑 使用 JMS API 从 .这是因为 JMS API 只有一个工厂方法来创建一个 ,并且它需要 事务和确认模式。在托管环境中,设置这些值是 环境的事务基础结构的责任,因此这些值 被供应商的 JMS Connection 包装器忽略。在非托管环境中使用 时,可以通过使用 properties 和 .当您使用 with 时,模板总是会被赋予一个 事务性 JMS。SessionConnectionSessionJmsTemplatesessionTransactedsessionAcknowledgeModePlatformTransactionManagerJmsTemplateSession

4.2. 发送消息

包含许多发送消息的便捷方法。发送 方法使用对象指定目标,而其他方法则指定目标 在 JNDI 查找中使用 指定目标。方法 that takes no destination 参数使用默认目标。JmsTemplatejavax.jms.DestinationStringsend

以下示例使用回调从 提供的对象:MessageCreatorSession

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

在前面的示例中,通过传递对 .作为替代方案,提供了零参数构造函数 and,可用于构造 JavaBean 样式(使用或纯 Java 代码)。或者,考虑 派生自 Spring 的便利基类,该基类提供 用于 JMS 配置的预构建 Bean 属性。JmsTemplateConnectionFactoryconnectionFactoryBeanFactoryJmsGatewaySupport

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

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

4.2.1. 使用消息转换器

为了便于域模型对象的发送,具有 将 Java 对象作为消息数据参数的各种发送方法 内容。重载方法和方法将转换过程委托给接口的实例。此接口定义了一个简单的协定,用于在 Java 对象和 JMS 消息。默认实现 () 支持转换 在 和 、 和 和 之间。通过使用转换器,您和您的应用程序代码可以专注于 通过 JMS 发送或接收的业务对象,并且不关心 有关如何将其表示为 JMS 消息的详细信息。JmsTemplateconvertAndSend()receiveAndConvert()JmsTemplateMessageConverterSimpleMessageConverterStringTextMessagebyte[]BytesMessagejava.util.MapMapMessage

沙盒目前包括一个 ,它使用反射来转换 在 JavaBean 和 .您可能选择的其他常用实现方案 实现自己是使用现有 XML 封送包(例如 JAXB 或 XStream) 来创建一个表示对象的。MapMessageConverterMapMessageTextMessage

适应不能 通常封装在转换器类中,接口 允许您在转换邮件之后但在发送邮件之前访问邮件。这 下面的示例演示如何在将 A 转换为消息后修改消息头和属性:MessagePostProcessorjava.util.Map

public void sendWithConversion() {
    Map map = new HashMap();
    map.put("Name", "Mark");
    map.put("Age", new Integer(47));
    jmsTemplate.convertAndSend("testQueue", map, new MessagePostProcessor() {
        public Message postProcessMessage(Message message) throws JMSException {
            message.setIntProperty("AccountID", 1234);
            message.setJMSCorrelationID("123-00001");
            return message;
        }
    });
}

这将导致以下形式的消息:

MapMessage={
    Header={
        ... standard headers ...
        CorrelationID={123-00001}
    }
    Properties={
        AccountID={Integer:1234}
    }
    Fields={
        Name={String:Mark}
        Age={Integer:47}
    }
}

4.2.2. 使用 和SessionCallbackProducerCallback

虽然发送操作涵盖了许多常见的使用方案,但有时可能会 想要对 JMS 或 执行多个操作。和 分别公开 JMS 和 / 对。运行中的方法 这些回调方法。SessionMessageProducerSessionCallbackProducerCallbackSessionSessionMessageProducerexecute()JmsTemplate

4.3. 接收消息

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

4.3.1. 同步接收

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

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

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

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

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

该接口是特定于 Spring 的接口,它提供 与 JMS 接口类似的约定,但也提供了消息处理 方法访问从中接收 的 JMS。 下面的清单显示了接口的定义:SessionAwareMessageListenerMessageListenerSessionMessageSessionAwareMessageListener

package org.springframework.jms.listener;

public interface SessionAwareMessageListener {

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

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

请注意,接口的方法会抛出 .与标准 JMS 接口相比,使用该接口时,它是 客户端代码负责处理任何引发的异常。onMessage(..)SessionAwareMessageListenerJMSExceptionMessageListenerSessionAwareMessageListener

4.3.4. 使用MessageListenerAdapter

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

请考虑以下接口定义:

public interface MessageDelegate {

    void handleMessage(String message);

    void handleMessage(Map message);

    void handleMessage(byte[] message);

    void handleMessage(Serializable message);
}

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

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

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

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

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

下一个示例显示了另一个只能处理接收 JMS 消息的 MDP。请注意消息处理方法的实际调用方式(消息处理方法的名称默认为 ),但它是可配置的(如本节后面部分所示)。通知 以及该方法如何强类型化以仅接收和响应 JMS 消息。 下面的清单显示了接口的定义:TextMessagereceiveMessageListenerAdapterhandleMessagereceive(..)TextMessageTextMessageDelegate

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>

请注意,如果接收到一个 除了 之外,an 被抛出(随后 吞咽)。该类的另一个功能是 如果处理程序方法返回 非 void 值。请考虑以下接口和类:messageListenerMessageTextMessageIllegalStateExceptionMessageListenerAdapterMessage

public interface ResponsiveTextMessageDelegate {

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

如果将 与 结合使用 ,则执行 时返回的任何非 null 值 该方法(在默认配置中)转换为 .然后将结果发送到 (如果 一个存在)在原始 JMS 属性中定义,或者 默认设置(如果已配置)。 如果未找到,则抛出 (请注意,此异常不会被吞噬,而是向上传播 调用堆栈)。DefaultResponsiveTextMessageDelegateMessageListenerAdapter'receive(..)'TextMessageTextMessageDestinationReply-ToMessageDestinationMessageListenerAdapterDestinationInvalidDestinationException

4.3.5. 处理事务中的消息

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

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

请考虑以下 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 事务参与配置消息侦听器容器,您需要 配置一个(缺省情况下,它委托给 Java EE 服务器的事务子系统)。请注意,底层 JMS 需要 具有 XA 功能,并在您的 JTA 事务协调器中正确注册。(检查你的 Java EE 服务器对 JNDI 资源的配置。这也允许接收消息 作为(例如)数据库访问是同一事务的一部分(使用统一提交 语义,但以 XA 事务日志开销为代价)。JtaTransactionManagerConnectionFactory

以下 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 的容器的支持。尝试 根据提供程序的类名自动确定类名。因此,通常可以提供 Spring 的泛型,如以下示例所示:MessageListenerJmsMessageEndpointManagerActivationSpecResourceAdapterJmsActivationSpecConfig

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

或者,您可以使用给定对象设置该对象也可能来自 JNDI 查找 (使用 )。以下示例演示如何执行此操作:JmsMessageEndpointManagerActivationSpecActivationSpec<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 的 ,您可以在本地配置目标,如以下示例所示:ResourceAdapterFactoryBeanResourceAdapter

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

指定的线程池还可以指向特定于环境的线程池,通常通过实例的属性。考虑 为所有实例定义一个共享线程池(如果碰巧) 使用多个适配器。WorkManagerSimpleTaskWorkManagerasyncTaskExecutorResourceAdapter

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

请参阅 JmsMessageEndpointManagerJmsActivationSpecConfig 的 javadoc、 和 ResourceAdapterFactoryBean 了解更多详细信息。

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

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

4.5. 注解驱动的监听器端点

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

@Component
public class MyService {

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

前面示例的思想是,每当 上有一条消息可用时,都会调用该方法 因此(在本例中,使用 JMS 消息的内容,类似于 MessageListenerAdapter 提供的内容)。javax.jms.DestinationmyDestinationprocessOrder

带批注的端点基础结构创建消息侦听器容器 在每个带注释的方法的幕后,通过使用 . 此类容器不会针对应用程序上下文进行注册,但可以很容易地注册 使用 Bean 进行管理。JmsListenerContainerFactoryJmsListenerEndpointRegistry

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

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

缺省情况下,基础结构会查找名为源的 Bean,以便工厂用于创建消息侦听器容器。在这个 case (并忽略 JMS 基础结构设置),您可以调用核心轮询大小为 3 个线程且最大池大小为 10 个线程的方法。jmsListenerContainerFactoryprocessOrder

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

如果更喜欢 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);
    }
}

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

请注意,您可以完全跳过使用 并以编程方式仅通过 注册端点。@JmsListenerJmsListenerConfigurer

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

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

@Component
public class MyService {

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

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

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

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

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

  • @Header-annotated 方法参数来提取特定的标头值,包括 标准 JMS 标头。

  • 一个带注释的参数,也必须分配给 for 获取对所有标头的访问权限。@Headersjava.util.Map

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

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

@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 中的现有支持已允许方法具有非返回类型。在这种情况下,结果 调用封装在指定的目标中发送的 中 在原始邮件的标头中或在 上配置的默认目标中 侦听器。现在,您可以使用 消息传递抽象。voidjavax.jms.MessageJMSReplyTo@SendTo

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

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

如果需要以与传输无关的方式设置其他标头,则可以使用类似于以下内容的方法返回: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 namespace 元素,您需要引用 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 定义和两个不同的 Bean 定义,如图所示 在使用 MessageListenerAdapter 中。除了显示的属性 在前面的示例中,该元素可以包含多个可选元素。 下表描述了所有可用属性:MessageListenerAdapterlistener

Table 3. Attributes of the JMS <listener> element
属性 描述

id

托管侦听器容器的 Bean 名称。如果未指定,则 Bean 名称为 自动生成。

destination(必填)

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

ref(必填)

处理程序对象的 Bean 名称。

method

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

response-destination

要向其发送响应消息的默认响应目标的名称。这是 在请求消息不包含字段的情况下应用。这 此目标的类型由 listener-container 的属性确定。请注意,这仅适用于具有 返回值,每个 result 对象都转换为响应消息。JMSReplyToresponse-destination-type

subscription

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

selector

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

concurrency

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

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

您可以自动公开此类设置,例如 指定要通过属性公开的 bean, 如以下示例所示:JmsListenerContainerFactoryidfactory-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>

下表描述了所有可用属性。请参阅类级 javadoc 的 AbstractMessageListenerContainer 及其具体子类,以获取有关各个属性的更多详细信息。javadoc 此外,还讨论了事务选择和消息重新传递方案。

Table 4. Attributes of the JMS <listener-container> element
属性 描述

container-type

此侦听器容器的类型。可用选项为 、 、 或 (默认选项为 )。defaultsimpledefault102simple102default

container-class

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

factory-id

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

connection-factory

对 JMS Bean 的引用(缺省 Bean 名称为 )。ConnectionFactoryconnectionFactory

task-executor

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

destination-resolver

对用于解析 JMS 实例的策略的引用。DestinationResolverDestination

message-converter

对将 JMS 消息转换为侦听器的策略的引用 方法参数。缺省值为 .MessageConverterSimpleMessageConverter

error-handler

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

destination-type

此侦听器的 JMS 目标类型:、、、、 或。这可能会启用容器的 和 属性。默认值为 (禁用 这三个属性)。queuetopicdurableTopicsharedTopicsharedDurableTopicpubSubDomainsubscriptionDurablesubscriptionSharedqueue

response-destination-type

响应的 JMS 目标类型:或 。默认值为属性的值。queuetopicdestination-type

client-id

此侦听器容器的 JMS 客户机标识。在使用 持久订阅。

cache

JMS 资源的高速缓存级别:、 、 、 或 。默认情况下 (),缓存级别有效为 ,除非 已指定外部事务管理器 — 在这种情况下,有效的 默认值将是(假设 Java EE 样式的事务管理,其中给定 ConnectionFactory 是一个 XA 感知池)。noneconnectionsessionconsumerautoautoconsumernone

acknowledge

本机 JMS 确认模式:、 、 或 。一个值 的激活本地事务。或者,您可以指定 该属性,稍后在表中描述。缺省值为 。autoclientdups-oktransactedtransactedSessiontransaction-managerauto

transaction-manager

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

concurrency

要为每个侦听器启动的并发会话数或使用者数。它可以是 指示最大数字的简单数字(例如,)或指示 下限和上限(例如,)。请注意,指定的最小值只是一个 提示,可能会在运行时被忽略。缺省值为 。应将并发限制为 主题侦听器的情况,或者队列排序是否重要。考虑将其提高 常规队列。53-511

prefetch

要加载到单个会话中的最大消息数。请注意,提出这个问题 数字可能会导致并发使用者的匮乏。

receive-timeout

用于接收呼叫的超时(以毫秒为单位)。默认值为 (one 第二)。 表示无超时。1000-1

back-off

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

recovery-interval

指定恢复尝试之间的间隔(以毫秒为单位)。它提供了一个方便的 以指定间隔创建的方式。为了更多恢复 选项,请考虑改为指定实例。默认值为 5000 毫秒 (即 5 秒)。FixedBackOffBackOff

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

Table 5. Attributes of the JMS <jca-listener-container/> element
属性 描述

factory-id

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

resource-adapter

对 JCA Bean 的引用(缺省 Bean 名称为 )。ResourceAdapterresourceAdapter

activation-spec-factory

对 的引用。缺省值是自动检测 JMS provider 及其类(请参阅 DefaultJmsActivationSpecFactory)。JmsActivationSpecFactoryActivationSpec

destination-resolver

对解析 JMS 的策略的引用。DestinationResolverDestinations

message-converter

对将 JMS 消息转换为侦听器的策略的引用 方法参数。缺省值为 。MessageConverterSimpleMessageConverter

destination-type

此侦听器的 JMS 目标类型:、 、 、 。 或。这可能会启用 、 、 和容器的属性。默认值为 (禁用 这三个属性)。queuetopicdurableTopicsharedTopicsharedDurableTopicpubSubDomainsubscriptionDurablesubscriptionSharedqueue

response-destination-type

响应的 JMS 目标类型:或 。默认值为属性的值。queuetopicdestination-type

client-id

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

acknowledge

本机 JMS 确认模式:、 、 或 。一个值 的激活本地事务。或者,您可以指定 稍后描述的属性。缺省值为 。autoclientdups-oktransactedtransactedSessiontransaction-managerauto

transaction-manager

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

concurrency

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

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 框架中的核心类是 .这个班级是 负责获取您的 Spring Bean 并将它们注册到 JMX 。 例如,请考虑以下类:MBeanExporterMBeanServer

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,您可以在 配置文件并传入 bean,如以下示例所示:MBeanExporter

<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 定义是 Bean。该属性确切地告诉您的bean必须是哪个 导出到 JMX .在默认配置中,每个条目的键 中,用作 相应的条目值。您可以更改此行为,如控制 Bean 的 ObjectName 实例中所述。exporterbeansMBeanExporterMBeanServerbeansMapObjectName

使用此配置,Bean 将作为 MBean 公开在 下。默认情况下,Bean 的所有属性 作为属性公开,所有方法(从类继承的方法除外)都作为操作公开。testBeanObjectNamebean:name=testBean1publicpublicObject

MBeanExporter是一个 bean(请参阅启动和关闭回调)。缺省情况下,MBean 在 应用程序生命周期。您可以配置 导出发生或通过设置标志禁用自动注册。LifecyclephaseautoStartup

5.1.1. 创建 MBeanServer

上一节中所示的配置假定 应用程序在已运行一个(且只有一个)的环境中运行。在这种情况下,Spring 会尝试找到正在运行的 向该服务器注册 Bean(如果有)。此行为在以下情况下很有用 应用程序在容器(例如 Tomcat 或 IBM WebSphere)内运行,该容器具有 有。MBeanServerMBeanServerMBeanServer

但是,这种方法在独立环境中或在内部运行时没有用 不提供 .若要解决此问题,可以通过将类的实例添加到配置中以声明方式创建实例。 您还可以通过将实例属性的值设置为 返回的值来确保使用特定值,如以下示例所示:MBeanServerMBeanServerorg.springframework.jmx.support.MBeanServerFactoryBeanMBeanServerMBeanExporterserverMBeanServerMBeanServerFactoryBean

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

在前面的示例中,一个实例由 和 是 通过物业供应。当您提供自己的实例时,不会尝试查找正在运行的实例,而是使用提供的实例。为此,请允许 正确地,类路径上必须有一个 JMX 实现。MBeanServerMBeanServerFactoryBeanMBeanExporterserverMBeanServerMBeanExporterMBeanServerMBeanServer

5.1.2. 重用现有的MBeanServer

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

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

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

<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

如果使用 同样配置为惰性的 Bean 来配置 初始化时,不会破坏此合约并避免 实例化 Bean。相反,它会向 和 注册代理 将从容器中获取 Bean 推迟到代理上的第一次调用 发生。MBeanExporterMBeanExporterMBeanServer

5.1.4. MBean 的自动注册

任何通过 导出的 Bean 都是 按原样注册,无需 Spring 的进一步干预。您可以导致 MBean 通过将属性设置为 自动检测,如以下示例所示:MBeanExporterMBeanServerMBeanExporterautodetecttrue

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

在前面的示例中,调用的 Bean 已经是有效的 JMX MBean 并由 Spring 自动注册。缺省情况下,为 JMX 自动检测的 Bean 注册的 Bean 名称用作 .您可以覆盖此行为, 如控制 Bean 的 ObjectName 实例中所述。spring:mbean=trueObjectName

5.1.5. 控制注册行为

考虑这样一种情况:Spring 尝试使用 .如果实例已在该实例下注册,则默认行为 是失败(并抛出一个)。MBeanExporterMBeanMBeanServerObjectNamebean:name=testBean1MBeanObjectNameInstanceAlreadyExistsException

您可以精确地控制 在 .Spring 的 JMX 支持允许三种不同的 注册行为:控制注册时的注册行为 进程发现 an 已在同一 下注册。 下表总结了这些注册行为:MBeanMBeanServerMBeanObjectName

Table 6. Registration Behaviors
注册行为 解释

FAIL_ON_EXISTING

这是默认的注册行为。如果实例已经 注册在同一名下,正在注册的不是 注册,并抛出 an。现有不受影响。MBeanObjectNameMBeanInstanceAlreadyExistsExceptionMBean

IGNORE_EXISTING

如果实例已在同一实例下注册,则不会注册正在注册的实例。现有的是 不受影响,并且不会抛出。这在以下情况下很有用 多个应用程序希望在一个共享中共享一个公共。MBeanObjectNameMBeanMBeanExceptionMBeanMBeanServer

REPLACE_EXISTING

如果实例已注册在同一 , 先前注册的现有已注册未注册,而新的已注册在其位置(新的有效地替换了 上一个实例)。MBeanObjectNameMBeanMBeanMBean

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

下面的示例演示如何从默认注册进行更改 行为到行为: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 的所有属性和方法都作为 JMX 属性公开,并且 操作。对具体哪个进行更细粒度的控制 导出的 Bean 的属性和方法实际上作为 JMX 属性公开 和操作,Spring JMX 为 控制 Bean 的管理界面。public

5.2.1. 使用接口MBeanInfoAssembler

在后台,委托给接口的实现,即 负责定义公开的每个 Bean 的管理接口。 默认实现 , 定义公开所有公共属性和方法的管理接口 (正如您在前面部分的示例中看到的那样)。Spring 提供了两个 接口的其他实现,允许您 使用任一源级元数据控制生成的管理界面 或任何任意接口。MBeanExporterorg.springframework.jmx.export.assembler.MBeanInfoAssemblerorg.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssemblerMBeanInfoAssembler

5.2.2. 使用源代码级元数据:Java 注解

通过使用 ,您可以定义管理接口 使用源级元数据为您的 Bean。元数据的读取是封装的 通过界面。 Spring JMX 提供了一个使用 Java 注释的默认实现,即 . 您必须使用 它正常运行的接口(没有默认值)。MetadataMBeanInfoAssemblerorg.springframework.jmx.export.metadata.JmxAttributeSourceorg.springframework.jmx.export.annotation.AnnotationJmxAttributeSourceMetadataMBeanInfoAssemblerJmxAttributeSource

要标记要导出到 JMX 的 bean,应该使用注释来注释 bean 类。必须将要公开的每个方法标记为操作 使用注释并标记要公开的每个属性 与注释。标记属性时,可以省略 getter 或 setter 的注解,用于创建只写或只读 属性。ManagedResourceManagedOperationManagedAttribute

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

以下示例显示了我们 用于创建 MBeanServerJmxTestBean

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

}

在前面的示例中,您可以看到该类标有批注,并且配置了此批注 具有一组属性。这些属性可用于配置各个方面 由 生成的 MBean,并在 稍后在源代码级元数据类型中详细介绍。JmxTestBeanManagedResourceManagedResourceMBeanExporter

和属性都用注解进行注释,但是,在属性的情况下,只标记 getter。 这会导致这两个属性都包含在管理界面中 作为属性,但该属性是只读的。agenameManagedAttributeageage

最后,该方法用属性标记, 而方法不是。这会导致管理接口 使用 时仅包含一个操作 ()。add(int, int)ManagedOperationdontExposeMe()add(int, int)MetadataMBeanInfoAssembler

以下配置显示了如何配置 使用 :MBeanExporterMetadataMBeanInfoAssembler

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

在前面的示例中,Bean 配置了 实例,并通过汇编程序属性传递给这就是利用 元数据驱动的管理界面,用于 Spring 公开的 MBean。MetadataMBeanInfoAssemblerAnnotationJmxAttributeSourceMBeanExporter

5.2.3. 源代码级元数据类型

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

Table 7. Source-level metadata types
目的 注解 批注类型

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

@ManagedResource

将方法标记为 JMX 操作。

@ManagedOperation

方法

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

@ManagedAttribute

方法(仅限 getter 和 setter)

定义操作参数的描述。

@ManagedOperationParameter@ManagedOperationParameters

方法

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

Table 8. Source-level metadata parameters
参数 描述 适用于

ObjectName

用于确定托管资源。MetadataNamingStrategyObjectName

ManagedResource

description

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

ManagedResourceManagedAttributeManagedOperationManagedOperationParameter

currencyTimeLimit

设置描述符字段的值。currencyTimeLimit

ManagedResourceManagedAttribute

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 包含该接口,该接口扩展了接口以添加对 MBean 资源自动检测的支持。如果使用 的实例进行配置 ,则 允许“投票”是否包含用于暴露于 JMX 的 bean。AutodetectCapableMBeanInfoAssemblerMBeanInfoAssemblerMBeanExporterAutodetectCapableMBeanInfoAssembler

该接口的唯一实现是 ,它投票以包含任何标记的 Bean 替换为属性。在这种情况下,默认方法是使用 Bean 名称作为 ,这将产生类似于以下内容的配置:AutodetectCapableMBeanInfoMetadataMBeanInfoAssemblerManagedResourceObjectName

<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 传递给 . 但是,它仍然被注册,因为它被标记为属性,并且检测到这一点并投票包含它。 这种方法的唯一问题是现在的名称有业务 意义。您可以通过更改控制 Bean 的 ObjectName 实例中定义的默认创建行为来解决此问题。MBeanExporterJmxTestBeanManagedResourceMetadataMBeanInfoAssemblerJmxTestBeanObjectName

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

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

尽管公开 MBean 的标准机制是使用接口和简单的 命名方案,通过以下方式扩展此功能 无需命名约定,允许您使用多个接口 并且不需要 Bean 来实现 MBean 接口。InterfaceBasedMBeanInfoAssembler

请考虑以下接口,该接口用于为我们前面展示的类定义管理接口: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>

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

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

如果未通过 或 属性指定管理接口,则 并使用该 Bean 实现的所有接口来创建 管理界面。managedInterfacesinterfaceMappingsInterfaceBasedMBeanInfoAssembler

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>

在前面的示例中,您可以看到 和 方法以 JMX 的形式公开 操作和 、 、 和 公开为 JMX 属性的适当一半。在上面的代码中,方法映射适用于 向 JMX 公开的 bean。要逐个 Bean 控制方法公开,可以使用 将 Bean 名称映射到 的属性 方法名称列表。addmyOperationgetName()setName(String)getAge()methodMappingsMethodNameMBeanInfoAssembler

5.3. 控制 Bean 的实例ObjectName

在后台,委托给 的实现,以获取它注册的每个 Bean 的实例。 默认情况下,默认实现使用 的键作为 .此外,还可以映射密钥 的 到一个或多个文件中的条目,以解决 .除了 之外,Spring 还提供了两个额外的实现:(它基于 Bean 的 JVM 身份构建)和 (它 使用源级元数据获取 )。MBeanExporterObjectNamingStrategyObjectNameKeyNamingStrategybeansMapObjectNameKeyNamingStrategybeansMapPropertiesObjectNameKeyNamingStrategyObjectNamingStrategyIdentityNamingStrategyObjectNameMetadataNamingStrategyObjectName

5.3.1. 从属性读取实例ObjectName

您可以配置自己的实例,并将其配置为从实例中读取实例,而不是使用 Bean 键。尝试在 with a key 中找到条目 这对应于 Bean 键。如果未找到条目,或者实例为 ,则使用 Bean 键本身。KeyNamingStrategyObjectNamePropertiesKeyNamingStrategyPropertiesPropertiesnull

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

前面的示例将 的实例配置为 从 mapping 属性定义的实例合并而来,并且 属性文件,位于 mappings 属性定义的路径中。在这个 配置时,Bean 的 , 因为这是实例中的条目,其键对应于 Bean 键。KeyNamingStrategyPropertiesPropertiestestBeanObjectNamebean:name=testBean1Properties

如果在实例中找不到任何条目,那么 Bean 键名称将用作 这。PropertiesObjectName

5.3.2. 使用MetadataNamingStrategy

MetadataNamingStrategy使用每个 Bean 上的属性属性来创建 .以下代码显示了 的配置:objectNameManagedResourceObjectNameMetadataNamingStrategy

<beans>

    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="beans">
            <map>
                <entry key="testBean" value-ref="testBean"/>
            </map>
        </property>
        <property name="namingStrategy" ref="namingStrategy"/>
    </bean>

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

    <bean id="namingStrategy" class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
        <property name="attributeSource" ref="attributeSource"/>
    </bean>

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

</beans>

如果未为该属性提供任何属性,则使用以下命令创建一个 格式:[fully-qualified-package-name]:type=[short-classname],name=[bean-name]。为 例如,为以下 Bean 生成的将是:objectNameManagedResourceObjectNameObjectNamecom.example:type=MyClass,name=myBean

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

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

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

@Configuration
@EnableMBeanExport
public class AppConfig {

}

如果您更喜欢基于 XML 的配置,则该元素将提供 相同的用途,如以下清单所示:<context:mbean-export/>

<context:mbean-export/>

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

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

}

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

<context:mbean-export server="myMBeanServer" default-domain="myDomain"/>
请勿将基于接口的 AOP 代理与 JMX 的自动检测结合使用 Bean 类中的注释。基于接口的代理“隐藏”目标类,即 还隐藏了 JMX 管理的资源注释。因此,您应该在其中使用目标类代理 大小写(通过设置 'proxy-target-class' 标志,依此类推)。否则,您的 JMX Bean 可能会在 启动。<aop:config/><tx:annotation-driven/>

5.4. 使用 JSR-160 连接器

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

5.4.1. 服务器端连接器

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

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

默认情况下,创建绑定到 .因此,Bean 暴露了 通过 localhost 上的 JMXMP 协议(端口 9875)本地到客户端。注意 JMXMP 协议被 JSR 160 规范标记为可选协议。现在 主要的开源 JMX 实现 MX4J 和 JDK 随附的实现 不支持 JMXMP。ConnectorServerFactoryBeanJMXConnectorServerservice:jmx:jmxmp://localhost:9875serverConnectorMBeanServer

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

<bean id="serverConnector"
        class="org.springframework.jmx.support.ConnectorServerFactoryBean">
    <property name="objectName" value="connector:name=rmi"/>
    <property name="serviceUrl"
            value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector"/>
</bean>

如果设置了该属性,Spring 会自动注册连接器 在那下面.以下示例显示了完整的 参数,您可以在创建时传递给:ObjectNameMBeanServerObjectNameConnectorServerFactoryBeanJMXConnector

<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 的连接器时,需要启动查找服务 ( 或 ) 才能完成名称注册。如果你 使用 Spring 通过 RMI 为你导出远程服务,Spring 已经 构建了 RMI 注册表。如果没有,您可以使用以下命令轻松启动注册表 配置片段:tnameservrmiregistry

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

5.4.2. 客户端连接器

要创建一个启用了 JSR-160 的远程 ,您可以使用 ,如以下示例所示:MBeanServerConnectionMBeanServerMBeanServerConnectionFactoryBean

<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 允许扩展客户端之间完成通信的方式 和服务器。前面各节中显示的示例使用基于 RMI 的强制性实现 JSR-160 规范(IIOP 和 JRMP)和(可选)JMXMP 要求。通过使用 其他提供程序或 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 允许您创建代理,将调用重新路由到 本地或远程。这些代理为您提供了一个标准的 Java 接口, 通过它,您可以与 MBean 进行交互。以下代码演示如何配置 在本地运行的 MBean 的代理:MBeanServerMBeanServer

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

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

可以为可通过 .默认情况下,定位并使用本地,但 您可以覆盖它并提供指向远程的 THAT 来满足指向远程 MBean 的代理:MBeanProxyFactoryBeanMBeanServerConnectionMBeanServerMBeanServerConnectionMBeanServer

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

在前面的示例中,我们创建了一个指向远程计算机的 使用 .这就是 传递到属性。代理 created 通过 this 将所有调用转发给 .MBeanServerConnectionMBeanServerConnectionFactoryBeanMBeanServerConnectionMBeanProxyFactoryBeanserverMBeanServerMBeanServerConnection

5.6. 通知

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

5.6.1. 注册通知监听器

Spring 的 JMX 支持使得向任意数量的 MBean 注册(包括由 Spring 和 MBean 通过其他机制注册)。为 例如,考虑这样一种场景:每次目标 MBean 的属性发生更改时,人们都希望(通过 )收到通知。以下 示例将通知写入控制台:NotificationListenersMBeanExporterNotification

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

}

以下示例添加 (在前面的 example) 更改为:ConsoleLoggingNotificationListenernotificationListenerMappings

<beans>

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

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

</beans>

使用上述配置后,每次从 目标 MBean(),Bean 通过属性注册为侦听器是 通知。然后,Bean 可以采取任何行动 它认为对 .Notificationbean:name=testBean1ConsoleLoggingNotificationListenernotificationListenerMappingsConsoleLoggingNotificationListenerNotification

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

如果要为所有 Bean 注册单个实例 ,您可以使用特殊的通配符 () 作为属性中条目的键 map,如以下示例所示:NotificationListenerMBeanExporter*notificationListenerMappings

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

如果需要执行相反的操作(即,针对 一个 MBean),您必须改用 list 属性(在 对属性的偏好)。这一次,而不是 对于单个 MBean,我们配置实例。A 封装了 a 和 (or) 它将成为 在 .还封装了 许多其他属性,例如 A 和任意切换 可用于高级 JMX 通知方案的对象。notificationListenersnotificationListenerMappingsNotificationListenerNotificationListenerBeanNotificationListenerBeanNotificationListenerObjectNameObjectNamesMBeanServerNotificationListenerBeanNotificationFilter

使用实例时的配置不是很随意 与前面介绍的内容不同,如以下示例所示: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>

前面的示例等效于第一个通知示例。那么,假设 我们希望每次提出 A 时都会得到一个交还对象,并且 我们还希望通过提供 .以下示例可实现这些目标:NotificationNotificationsNotificationFilter

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

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

5.6.2. 发布通知

Spring 不仅支持注册接收,还支持注册接收 用于发布 .NotificationsNotifications

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

Spring 的 JMX 通知发布支持中的关键接口是接口(在包中定义)。任何将要成为的豆子 通过实例导出为 MBean 可以实现相关接口来获取对实例的访问权。该接口通过一个简单的 setter 方法向实现 bean 提供 a 的实例, 然后 Bean 可以使用它来发布 .NotificationPublisherorg.springframework.jmx.export.notificationMBeanExporterNotificationPublisherAwareNotificationPublisherNotificationPublisherAwareNotificationPublisherNotifications

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

使用实例非常简单。创建一个 JMX 实例(或相应子类的实例), 使用与要发生的事件相关的数据填充通知 published,然后在实例上调用 ,传入 .NotificationPublisherNotificationNotificationsendNotification(Notification)NotificationPublisherNotification

在以下示例中,每次调用操作时,都会导出 publish a 的实例:JmxTestBeanNotificationEventadd(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;
    }

}

使这一切工作的界面和机器是其中之一 Spring 的 JMX 支持的更好功能。然而,它确实带有 将您的类耦合到 Spring 和 JMX。与往常一样,这里的建议是 务实。如果您需要 和 您可以接受与 Spring 和 JMX 的耦合,然后这样做。NotificationPublisherNotificationPublisher

5.7. 更多资源

本节包含指向有关 JMX 的其他资源的链接:

6. 电子邮件

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

库依赖项

以下 JAR 需要位于应用程序的类路径上,以便使用 Spring Framework 的电子邮件支持:

该库可在 Web 上免费获得,例如,在 Maven Central 中为 .请确保使用最新的 1.6.x 版本(该版本使用 包命名空间),而不是 Jakarta Mail 2.0(使用包命名空间)。请参阅 Jakarta Mail API 存储库分支中的代码。com.sun.mail:jakarta.mailjavax.mailjakarta.mailv1.x

Spring Framework 提供了一个有用的实用程序库,用于发送屏蔽 您从底层邮件系统的具体情况出发,并负责 代表客户端进行低级资源处理。

该包是 Spring 的根级包 框架的电子邮件支持。发送电子邮件的中央界面是界面。一个简单的值对象,它封装了简单邮件的属性,例如 作为和(以及许多其他人)是类。此套餐 还包含已检查异常的层次结构,这些异常提供更高级别的 对较低级别的邮件系统异常进行抽象,根例外是 。有关富邮件异常层次结构的更多信息,请参阅 javadocorg.springframework.mailMailSenderfromtoSimpleMailMessageMailException

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

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="customerservice@mycompany.example"/>
    <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

本节介绍使用回调接口的另一种实现。在以下示例中,该属性的类型为,以便我们能够使用 JavaMail 类:OrderManagerMimeMessagePreparatormailSenderJavaMailSenderMimeMessage

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("mail@mycompany.example"));
                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 消息时非常方便的一个类是 ,它可以保护您免受 必须使用冗长的 JavaMail API。使用 ,它是 创建 非常容易,如以下示例所示:org.springframework.mail.javamail.MimeMessageHelperMimeMessageHelperMimeMessage

// 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("test@host.com");
helper.setText("Thank you for ordering!");

sender.send(message);

6.2.1. 发送附件和内联资源

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

附件

以下示例演示如何使用 发送电子邮件 使用单个 JPEG 图像附件: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("test@host.com");

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("test@host.com");

// 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);
内联资源通过使用指定的(在上面的示例中)添加到 中。添加文本的顺序 资源非常重要。请务必先添加文本,然后 资源。如果你反过来做,它是行不通的。MimeMessageContent-IDidentifier1234

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

前面部分中所示的示例中的代码显式创建了电子邮件的内容。 通过使用 .这对于简单的情况很好,而且它 在上述示例的上下文中是可以的,其目的是向您展示 API 的基础知识。message.setText(..)

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

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

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

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

通常,解决这些问题的方法是使用模板库(例如 as FreeMarker) 来定义电子邮件内容的显示结构。这将留下您的代码 仅负责创建要在电子邮件模板中呈现的数据,以及 发送电子邮件。当您的电子邮件内容时,这绝对是最佳实践 甚至变得适度复杂,并且,随着 Spring Framework 的支持类 FreeMarker,这变得非常容易。

7. 任务执行和调度

Spring Framework 为异步执行和调度提供了抽象 任务,分别带有 和 接口。春天也 Features 实现那些支持线程池或委派给 应用程序服务器环境中的 CommonJ。最终,使用这些 通用接口背后的实现抽象化了 Java 之间的差异 SE 5、Java SE 6 和 Java EE 环境。TaskExecutorTaskScheduler

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

7.1. Spring 抽象TaskExecutor

执行程序是线程池概念的 JDK 名称。“执行程序”命名是 由于无法保证底层实现是 实际上是一个游泳池。执行器可以是单线程的,甚至可以是同步的。春天的 抽象隐藏了 Java SE 和 Java EE 环境之间的实现细节。

Spring 的接口与接口相同。事实上,最初,它存在的主要原因是抽象化 使用线程池时对 Java 5 的需求。该接口具有单个方法 () 接受基于语义执行的任务 以及线程池的配置。TaskExecutorjava.util.concurrent.Executorexecute(Runnable task)

最初创建它是为了给其他 Spring 组件一个抽象 用于在需要时进行线程池化。组件,例如 、 JMS 和 Quartz 的集成都使用抽象来池化线程。但是,如果您的 Bean 需要线程池 行为,你也可以根据自己的需求使用这个抽象。TaskExecutorApplicationEventMulticasterAbstractMessageListenerContainerTaskExecutor

7.1.1. 类型TaskExecutor

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

  • SyncTaskExecutor: 此实现不会异步运行调用。取而代之的是,每个 调用在调用线程中进行。它主要用于以下情况 不需要多线程的地方,例如在简单的测试用例中。

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

  • ConcurrentTaskExecutor: 此实现是实例的适配器。 还有另一种 () 将配置参数公开为 Bean 属性。很少需要直接使用。但是,如果不是 足够灵活,满足您的需求,是一种选择。java.util.concurrent.ExecutorThreadPoolTaskExecutorExecutorConcurrentTaskExecutorThreadPoolTaskExecutorConcurrentTaskExecutor

  • ThreadPoolTaskExecutor: 此实现是最常用的。它公开了 配置 A 并将其包装在 . 如果您需要适应不同的种类,我们 建议您改用java.util.concurrent.ThreadPoolExecutorTaskExecutorjava.util.concurrent.ExecutorConcurrentTaskExecutor

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

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

7.1.2. 使用TaskExecutor

Spring 的实现被用作简单的 JavaBeans。在以下示例中, 我们定义了一个 bean 它使用 异步打印 输出一组消息:TaskExecutorThreadPoolTaskExecutor

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

正如你所看到的,而不是从池中检索一个线程并自己执行它, 将你的添加到队列中。然后使用其内部规则来 确定任务的运行时间。RunnableTaskExecutor

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

<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

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

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

最简单的方法是仅接受 a 和 a 的命名方法。 这会导致任务在指定时间后运行一次。所有其他方法 能够安排任务重复运行。固定速率和固定延迟 方法用于简单的定期执行,但接受 更加灵活。scheduleRunnableDateTrigger

7.2.1. 接口Trigger

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

public interface Trigger {

    Date nextExecutionTime(TriggerContext triggerContext);
}

这是最重要的部分。它封装了所有 相关数据,并在必要时开放以备将来扩展。是一个接口(实现由 默认值)。以下列表显示了实现的可用方法。TriggerContextTriggerContextSimpleTriggerContextTrigger

public interface TriggerContext {

    Date lastScheduledExecutionTime();

    Date lastActualExecutionTime();

    Date lastCompletionTime();
}

7.2.2. 实现Trigger

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

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

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

7.2.3. 实现TaskScheduler

与 Spring 的抽象一样,这种安排的主要好处是应用程序的调度需求与部署分离 环境。在部署到 应用程序服务器环境,其中线程不应由 应用程序本身。对于这样的场景,Spring 提供了一个委托给 WebLogic 或 WebSphere 上的 CommonJ 以及一个在 Java EE 7+ 环境中委托给 JSR-236 的更新。两者通常都配置了 JNDI 查找。TaskExecutorTaskSchedulerTimerManagerTaskSchedulerTimerManagerDefaultManagedTaskSchedulerManagedScheduledExecutorService

每当不需要外部线程管理时,一个更简单的替代方法是 应用程序中的本地设置,可以进行调整 通过 Spring 的 .为了方便起见,Spring 还提供了一个 ,它在内部委托给 ,以提供沿 . 这些变体非常适合在宽松的情况下进行本地嵌入式线程池设置 应用服务器环境也是如此,尤其是在 Tomcat 和 Jetty 上。ScheduledExecutorServiceConcurrentTaskSchedulerThreadPoolTaskSchedulerScheduledExecutorServiceThreadPoolTaskExecutor

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

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

7.3.1. 启用调度注解

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

@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}

您可以为应用程序选择相关的注释。例如 如果只需要支持 ,则可以省略 。查看更多 细粒度控制,可以另外实现接口和/或接口。有关完整的详细信息,请参阅 SchedulingConfigurer 和 AsyncConfigurer javadoc。@Scheduled@EnableAsyncSchedulingConfigurerAsyncConfigurer

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

处理注释的默认建议模式是允许 仅用于拦截通过代理的呼叫。同一班级内的本地呼叫 不能以这种方式被拦截。对于更高级的拦截模式,请考虑 切换到与编译时或加载时编织相结合的模式。@Asyncproxyaspectj

7.3.2. 注解@Scheduled

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

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

如果需要固定速率执行,则可以在 注解。以下方法每 5 秒调用一次(在 每次调用的连续开始时间)。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
}
您还可以使用该属性来指定 cron 所在的时区 表达式已解析。zone

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

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

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

7.3.3. 注解@Async

您可以提供方法的注释,以便调用该方法 异步发生。换句话说,调用方在以下情况下立即返回 调用,而该方法的实际执行发生在 提交给 Spring .在最简单的情况下,您可以应用注释 转换为返回 的方法,如以下示例所示:@AsyncTaskExecutorvoid

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

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

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

甚至可以异步调用返回值的方法。但是,这些方法 需要具有 -typed 返回值。这仍然提供了以下好处 异步执行,以便调用方可以在调用该任务之前执行其他任务。下面的示例演示如何在方法上使用 返回一个值:Futureget()Future@Async

@Async
Future<String> returnSomething(int i) {
    // this will be run asynchronously
}
@Async方法不仅可以声明常规返回类型 但也有春天的,或者,从春天开始 4.2、JDK 8 的 ,用于更丰富的交互 异步任务,并用于通过进一步的处理步骤立即组合。java.util.concurrent.Futureorg.springframework.util.concurrent.ListenableFuturejava.util.concurrent.CompletableFuture

不能与生命周期回调(如 .要异步初始化 Spring bean,您当前必须使用 一个单独的初始化 Spring bean,然后调用 target,如以下示例所示:@Async@PostConstruct@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 等价物,因为应该设计这样的方法 首先,对于异步执行,而不是在外部重新声明为异步。 但是,您可以使用 Spring AOP 手动设置 Spring, 与自定义切入点相结合。@AsyncAsyncExecutionInterceptor

7.3.4. 遗嘱执行人资格@Async

默认情况下,在方法上指定时,使用的执行器是 一个在启用异步支持时配置, 即“注释驱动”元素(如果您使用的是 XML 或您的实现,如果有)。但是,当您需要指示除默认值之外的执行程序时,可以使用注释的属性 在执行给定方法时使用。以下示例演示如何执行此操作:@AsyncAsyncConfigurervalue@Async

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

在这种情况下,可以是 Spring 中任何 Bean 的名称 容器,或者它可以是与任何关联的限定符的名称(例如,如 用元素或 Spring 的注解指定)。"otherExecutor"ExecutorExecutor<qualifier>@Qualifier

7.3.5. 异常管理@Async

当方法具有 -typed 返回值时,它很容易管理 在方法执行期间引发的异常,因为此异常是 调用结果时抛出。使用返回类型, 但是,异常未捕获且无法传输。您可以提供 来处理此类异常。以下示例显示 如何操作:@AsyncFuturegetFuturevoidAsyncUncaughtExceptionHandler

public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

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

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

7.4. 命名空间task

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

7.4.1. 'scheduler' 元素

以下元素创建一个实例,其中包含 指定的线程池大小:ThreadPoolTaskScheduler

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

为该属性提供的值用作线程名称的前缀 在游泳池内。该元素相对简单。如果你不这样做 提供属性,默认线程池只有一个线程。 调度程序没有其他配置选项。idschedulerpool-size

7.4.2. 元素executor

下面创建一个实例:ThreadPoolTaskExecutor

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

上一节中所示的调度程序一样, 为该属性提供的值用作 游泳池。就池大小而言,该元素支持更多 配置选项。首先,线程池 A 本身更具可配置性。而不是只有单一的尺寸, 执行程序的线程池可以具有不同的核心值和最大大小值。 如果提供单个值,则执行程序具有固定大小的线程池(核心和 最大大小相同)。但是,元素的属性也 接受 形式的范围。以下示例将最小值设置为 ,最大值为 :idexecutorschedulerThreadPoolTaskExecutorexecutorpool-sizemin-max525

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

在前面的配置中,还提供了一个值。 线程池的配置也应根据 执行程序的队列容量。有关池之间关系的完整说明 大小和队列容量,请参阅 ThreadPoolExecutor 的文档。 主要思想是,当提交任务时,执行者首先尝试使用 如果当前活动线程数小于内核大小,则为空闲线程。 如果已达到核心大小,则将任务添加到队列中,只要其 容量尚未达到。只有这样,如果队列的容量已 到达时,执行程序是否创建超出核心大小的新线程。如果最大尺寸 也已达到,则执行者拒绝该任务。queue-capacity

默认情况下,队列是无界的,但这很少是所需的配置, 因为它可能导致是否将足够多的任务添加到该队列中,而 所有池线程都处于繁忙状态。此外,如果队列是无限制的,则最大大小具有 完全没有效果。由于执行器总是在创建新的队列之前尝试队列 线程超出核心大小,队列必须具有有限的容量,线程池才能 增长到超出核心大小(这就是为什么固定大小的池是唯一明智的情况 使用无界队列时)。OutOfMemoryErrors

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

下面的示例演示一个元素,其中包含许多要指定的属性 各种行为:executor

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

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

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

7.4.3. 'scheduled-tasks' 元素

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

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

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

调度程序由外部元素引用,每个元素都单独引用 task 包括其触发器元数据的配置。在前面的示例中, 元数据定义了一个周期性触发器,该触发器具有固定的延迟,指示 每个任务执行完成后等待的毫秒数。另一个选项是 ,指示无论运行多长时间,方法都应运行的频率 任何先前的执行都需要。此外,对于这两个任务和任务,您可以指定一个 “initial-delay”参数,指示等待的毫秒数 在首次执行方法之前。为了获得更多控制权,您可以改为提供属性 以提供 cron 表达式。 以下示例显示了这些其他选项:fixed-ratefixed-delayfixed-ratecron

<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 表达式都必须符合相同的格式,无论您是在@Scheduled注释中使用它们, task:scheduled-tasks 元素, 或其他地方。 格式正确的 cron 表达式(如 )由六个空格分隔的时间和日期组成 字段,每个字段都有自己的有效值范围:* * * * * *

 ┌───────────── second (0-59)
 │ ┌───────────── minute (0 - 59)
 │ │ ┌───────────── hour (0 - 23)
 │ │ │ ┌───────────── day of the month (1 - 31)
 │ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)
 │ │ │ │ │ ┌───────────── day of the week (0 - 7)
 │ │ │ │ │ │          (0 or 7 is Sunday, or MON-SUN)
 │ │ │ │ │ │
 * * * * * *

有一些规则适用:

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

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

  • 用连字符 () 分隔的两个数字表示数字范围。 指定的范围是非独占的。-

  • 在范围(或 )后面 with 指定数字值在该范围中的间隔。*/

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

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

    • 在“月日”字段中,表示该月的最后一天。 如果后跟负偏移量(即 ),则表示该月的数第 n 天LL-n

    • 在星期几字段中,代表一周的最后一天。 如果以数字或三个字母的名称 ( 或 ) 为前缀,则表示当月中一周的最后一天(dDDD)。LdLDDDL

  • “月日”字段可以是 ,它表示最接近 n 月中的某一天的工作日。 如果落在星期六,则会产生它之前的星期五。 如果落在星期日,这将产生下一个星期一,如果是并落在 星期六(即:代表每月的第一个工作日)。nWnnn11W

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

  • 星期几字段可以是 (或 ),它代表当月第 n 周 d(或 DDD)的第 nd#nDDD#n

以下是一些示例:

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

诸如此类的表达式对于人类来说很难解析,因此在出现错误时很难修复。 为了提高可读性,Spring 支持以下宏,这些宏表示常用的序列。 您可以使用这些宏来代替六位数的值,因此: .0 0 * * * *@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 使用 、 和对象来实现各种调度 的工作。有关 Quartz 背后的基本概念,请参阅 https://www.quartz-scheduler.org/。为了方便起见,Spring 提供了几个 简化在基于 Spring 的应用程序中使用 Quartz 的类。TriggerJobJobDetail

7.6.1. 使用JobDetailFactoryBean

Quartz 对象包含运行作业所需的所有信息。Spring 提供了一个 ,它为 XML 配置目的提供了 bean 样式的属性。 请看以下示例:JobDetailJobDetailFactoryBean

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

作业详细信息配置包含运行作业所需的所有信息 ()。 超时在作业数据映射中指定。作业数据映射可通过 (在执行时传递给您),但也会得到 其属性从作业数据映射到作业实例的属性。因此,在以下示例中, 包含一个名为 的 Bean 属性,并自动应用它:ExampleJobJobExecutionContextJobDetailExampleJobtimeoutJobDetail

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

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

通过使用 和 属性,可以修改名称和组 的工作。缺省情况下,作业的名称与 Bean 名称匹配 的(在上面的前面的示例中)。namegroupJobDetailFactoryBeanexampleJob

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>

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

public class ExampleBusinessObject {

    // properties and collaborators

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

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

默认情况下,Quartz 作业是无状态的,导致作业可能会受到干扰 彼此之间。如果为同一个 指定两个触发器,则为 可能在第一个作业完成之前,第二个作业就开始了。如果类实现接口,则不会发生这种情况。第二个 在第一个作业完成之前,作业不会启动。若要使作业产生非并发,请将标志设置为 ,如以下示例所示:JobDetailJobDetailStatefulMethodInvokingJobDetailFactoryBeanconcurrentfalse

<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

我们创建了工作详细信息和工作。我们还审查了便利豆,让 在特定对象上调用方法。当然,我们仍然需要安排 工作本身。这是通过使用触发器和 .几个 触发器在 Quartz 中可用,Spring 提供了两种具有方便默认值的 Quartz 实现:和 .SchedulerFactoryBeanFactoryBeanCronTriggerFactoryBeanSimpleTriggerFactoryBean

需要安排触发器。Spring 提供了一个暴露 要设置为属性的触发器。 使用 这些触发因素。SchedulerFactoryBeanSchedulerFactoryBean

以下列表同时使用了 a 和 a:SimpleTriggerFactoryBeanCronTriggerFactoryBean

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

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

前面的示例设置了两个触发器,一个触发器每 50 秒运行一次,启动延迟为 10 秒,每天早上 6 点运行。要完成所有内容,我们需要设置 ,如以下示例所示:SchedulerFactoryBean

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

更多属性可用于 ,例如 作业详细信息、用于自定义 Quartz 的属性以及 Spring 提供的 JDBC DataSource。看 SchedulerFactoryBean javadoc 了解更多信息。SchedulerFactoryBean

SchedulerFactoryBean还可以识别类路径中的文件, 基于 Quartz 属性键,与常规 Quartz 配置一样。请注意,许多设置与属性文件中的常见 Quartz 设置交互; 因此,不建议在两个级别上都指定值。例如,不要将 “org.quartz.jobStore.class”属性,如果你打算依赖Spring提供的DataSource, 或指定一个变体 是标准的全面替代品。quartz.propertiesSchedulerFactoryBeanorg.springframework.scheduling.quartz.LocalDataSourceJobStoreorg.quartz.impl.jdbcjobstore.JobStoreTX

8. 缓存抽象

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

在 Spring Framework 4.1 中,缓存抽象得到了显著扩展,并提供了支持 用于 JSR-107 注解和更多自定义选项。

8.1. 理解缓存抽象

缓存与缓冲区

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

另一方面,根据定义,缓存是隐藏的,任何一方都不知道 发生缓存。它还可以提高性能,但通过让相同的数据 快速阅读多遍。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • @Cacheable:触发缓存填充。

  • @CacheEvict:触发缓存逐出。

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

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

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

8.2.1. 注解@Cacheable

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

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

在前面的代码段中,该方法与名为 的缓存相关联。 每次调用该方法时,都会检查缓存以查看调用是否具有 已经运行,不必重复。而在大多数情况下,只有一个 缓存,注解允许指定多个名称,以便多个名称 缓存正在使用中。在这种情况下,在调用 method — 如果至少命中一个缓存,则返回关联的值。findBookbooks

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

以下示例使用具有多个缓存的方法:@CacheablefindBook

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

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

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

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

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

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

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

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

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

自定义密钥生成声明

由于缓存是泛型的,因此目标方法很可能具有各种签名 这不能轻易地映射到缓存结构之上。这往往变得很明显 当目标方法有多个参数时,其中只有一些参数适合 缓存(其余部分仅由方法逻辑使用)。请看以下示例:

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

乍一看,虽然这两个论点影响了这本书的发现方式, 它们对缓存没有用处。此外,如果两者中只有一个很重要怎么办 而另一个不是?boolean

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

以下示例使用各种 SpEL 声明(如果您不熟悉 SpEL, 帮自己一个忙,阅读 Spring 表达式语言):

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

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

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

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

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

@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
和参数是互斥的,并且是一个操作 这两者都指定为异常结果。keykeyGenerator
默认缓存分辨率

缓存抽象使用一个简单的 使用配置的 .CacheResolverCacheManager

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

自定义缓存分辨率

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

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

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

您也可以以类似于 替换密钥生成。解决方法是 请求每个缓存操作,让实现实际解析 要基于运行时参数使用的缓存。下面的示例演示如何 指定一个 :CacheResolverCacheResolver

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

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

与 和 类似,和参数是互斥的,并且指定两者的操作 导致异常,因为实现忽略了自定义。这可能不是你所期望的。keykeyGeneratorcacheManagercacheResolverCacheManagerCacheResolver

同步缓存

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

对于这些特定情况,可以使用该属性来指示基础 缓存提供程序,用于在计算值时锁定缓存条目。因此, 只有一个线程忙于计算值,而其他线程则被阻塞,直到输入 在缓存中更新。以下示例演示如何使用该属性:syncsync

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

有时,某个方法可能不适合一直缓存(例如,它可能 取决于给定的参数)。缓存注释通过参数支持此类用例,该参数采用计算结果为 或 的表达式。如果 ,则缓存该方法。如果不是,则其行为就像方法不是一样 cached(也就是说,无论缓存中有什么值,每次都会调用该方法 或使用什么参数)。例如,仅当 参数的长度短于 32:conditionSpELtruefalsetruename

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

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

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

缓存抽象支持返回类型。如果一个值 ,它将存储在关联的缓存中。如果值不是 present,将存储在关联的缓存中。 始终指 业务实体,并且从来不是受支持的包装器,因此可以重写前面的示例 如下:java.util.OptionalOptionalOptionalnull#result

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

请注意,仍然是指而不是 .由于可能是,我们使用 SpEL 的安全导航运算符#resultBookOptional<Book>null

可用的缓存 SpEL 评估上下文

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

Table 9. Cache SpEL available metadata
名字 位置 描述

methodName

根对象

正在调用的方法的名称

#root.methodName

method

根对象

正在调用的方法

#root.method.name

target

根对象

正在调用的目标对象

#root.target

targetClass

根对象

被调用目标的类

#root.targetClass

args

根对象

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

#root.args[0]

caches

根对象

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

#root.caches[0].name

参数名称

评估背景

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

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

result

评估背景

方法调用的结果(要缓存的值)。仅在表达式、表达式(用于计算 )或表达式(when is )中可用。对于受支持的包装器(如 ),是指实际对象,而不是包装器。unlesscache putkeycache evictbeforeInvocationfalseOptional#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@CacheEvictallEntriesbooks

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

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

还可以指示逐出是在(默认)之后还是之前进行 该方法是使用该属性调用的。前者提供 与其他注解的语义相同: 方法成功完成后, 对缓存运行操作(在本例中为逐出)。如果该方法没有 运行(因为它可能被缓存)或引发异常,则不会发生逐出。 后一个 () 导致逐出始终发生在 方法。这在不需要捆绑驱逐的情况下很有用 到方法结果。beforeInvocationbeforeInvocation=true

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

8.2.4. 注解@Caching

有时,需要指定相同类型的多个注释(例如 或 ),例如,因为条件或键 不同缓存之间的表达式不同。 允许在同一方法上使用多个嵌套的 、 和 注释。 以下示例使用两个注释:@CacheEvict@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是一个类级注释,允许共享缓存名称, 自定义 、 自定义 和 自定义 。 将此批注放在类上不会打开任何缓存操作。KeyGeneratorCacheManagerCacheResolver

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

  • 全局配置,可用于 、 。CacheManagerKeyGenerator

  • 在类级别,使用 .@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>

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

处理缓存注释的默认建议模式是 ,它允许 仅用于拦截通过代理的呼叫。同一班级内的本地呼叫 不能以这种方式被拦截。对于更高级的拦截模式,请考虑 切换到与编译时或加载时编织相结合的模式。proxyaspectj
有关高级定制(使用 Java 配置)的更多详细信息,这些自定义项是 需要实现,请参阅 javadocCachingConfigurer
Table 10. Cache annotation settings
XML 属性 注释属性 违约 描述

cache-manager

N/A(请参阅 CachingConfigurer javadoc)

cacheManager

要使用的缓存管理器的名称。默认值初始化在后面 具有此缓存管理器的场景(如果未设置)。查看更多 缓存解析的细粒度管理,请考虑设置“缓存解析器” 属性。CacheResolvercacheManager

cache-resolver

N/A(请参阅 CachingConfigurer javadoc)

A 使用配置的 .SimpleCacheResolvercacheManager

用于解析后备高速缓存的 CacheResolver 的 Bean 名称。 此属性不是必需的,只需指定为 “cache-manager”属性。

key-generator

N/A(请参阅 CachingConfigurer javadoc)

SimpleKeyGenerator

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

error-handler

N/A(请参阅 CachingConfigurer javadoc)

SimpleCacheErrorHandler

要使用的自定义缓存错误处理程序的名称。默认情况下,在 与缓存相关的操作在客户端被抛回。

mode

mode

proxy

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

proxy-target-class

proxyTargetClass

false

仅适用于代理模式。控制为哪种类型的缓存代理创建缓存代理 使用 OR 注解的类。如果该属性设置为 ,则创建基于类的代理。 如果是或省略属性,则为标准 JDK 创建基于接口的代理。(有关不同代理类型的详细检查,请参阅代理机制@Cacheable@CacheEvictproxy-target-classtrueproxy-target-classfalse

order

order

Ordered.LOWEST_PRECEDENCE

定义应用于用 或 批注的 Bean 的高速缓存建议的顺序。(有关与以下规则有关的更多信息 订购 AOP 建议,请参阅建议订购。 没有指定的排序意味着 AOP 子系统确定建议的顺序。@Cacheable@CacheEvict

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

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

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

8.2.7. 使用自定义注解

自定义注释和 AspectJ

此功能仅适用于基于代理的方法,但可以启用 通过使用 AspectJ 需要付出一些额外的努力。

该模块仅定义标准注释的一个方面。 如果您已经定义了自己的注解,您还需要为 那些。检查示例。spring-aspectsAnnotationCacheAspect

缓存抽象允许您使用自己的注释来标识哪种方法 触发缓存填充或逐出。作为模板机制,这是非常方便的, 因为它消除了复制缓存注释声明的需要,这是 如果指定了键或条件,或者如果外部导入,则特别有用 () 不允许在代码库中使用。与其他人类似 的构造型注解,你可以 使用 、 、 和 作为元注释(即 可以对其他注释进行注释)。在以下示例中,我们将通用声明替换为我们自己的自定义注释: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)

即使不是 Spring 注解,容器也会自动选择 在运行时 up 其声明并理解其含义。请注意,如前所述,需要启用注释驱动的行为。@SlowService

8.3. JCache (JSR-107) 注解

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

8.3.1. 功能总结

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

Table 11. Spring vs. JSR-107 caching annotations
春天 JSR-107型 备注

@Cacheable

@CacheResult

相当相似。 可以缓存特定的异常并强制 无论缓存的内容如何,都执行该方法。@CacheResult

@CachePut

@CachePut

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

@CacheEvict

@CacheRemove

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

@CacheEvict(allEntries=true)

@CacheRemoveAll

看。@CacheRemove

@CacheConfig

@CacheDefaults

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

JCache 具有相同的概念 ,这是相同的 到 Spring 的接口,除了 JCache 只支持单个 缓存。默认情况下,一个简单的实现会根据 在批注上声明的名称。需要注意的是,如果没有缓存名称 在注释上指定,将自动生成默认值。请参阅 javadoc 了解更多信息。javax.cache.annotation.CacheResolverCacheResolver@CacheResult#cacheName()

CacheResolver实例由 .这是可能的 为每个缓存操作自定义工厂,如以下示例所示:CacheResolverFactory

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

密钥由服务于 与 Spring 的 .默认情况下,所有方法参数都采用 ,除非至少有一个参数用 注释。这是 类似于 Spring 的自定义密钥生成 声明。例如,以下是相同的操作,一个使用 Spring 的抽象和另一个使用 JCache 的抽象:javax.cache.annotation.CacheKeyGeneratorKeyGenerator@CacheKey

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

您还可以指定 on 操作,类似于如何 指定 .CacheKeyResolverCacheResolverFactory

JCache 可以管理带注释的方法引发的异常。这可以防止更新 缓存,但它也可以将异常缓存为失败的指示器,而不是 再次调用该方法。假设抛出时 ISBN 的结构无效。這是一個永久性的失敗(沒有一本書可能會這樣做 使用这样的参数检索)。下面缓存异常,以便进一步 具有相同、无效 ISBN 的调用会直接抛出缓存的异常,而不是 再次调用该方法:InvalidIsbnNotFoundException

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

8.3.2. 启用 JSR-107 支持

您无需执行任何特定操作即可启用 JSR-107 支持以及 Spring 的 声明式注解支持。如果类路径中同时存在 JSR-107 API 和模块,则 XML 元素和 XML 元素都会自动启用 JCache 支持。@EnableCachingcache:annotation-drivenspring-context-support

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

8.4. 基于 XML 的声明式缓存

如果注释不是一个选项(可能是由于无法访问源 或无外部代码),可以使用 XML 进行声明性缓存。所以,而不是 注释缓存的方法,您可以指定 Target 方法和 在外部缓存指令(类似于声明性事务管理建议)。示例 从上一节可以翻译成以下示例:

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

在前面的配置中,使 是可缓存的。缓存语义 应用封装在定义中,这会导致用于将数据放入缓存的方法和用于逐出的方法 数据。这两个定义都适用于缓存。bookServicecache:advicefindBooksloadBooksbooks

该定义将缓存建议应用于 使用 AspectJ 切入点表达式进行编程(更多信息可在 Aspect Oriented Programming with Spring 中找到)。在前面的示例中, 考虑 中的所有方法,并将缓存建议应用于它们。aop:configBookService

声明式 XML 缓存支持所有基于注释的模型,因此在 这两者应该相当容易。此外,两者都可以在同一应用程序中使用。 基于 XML 的方法不触及目标代码。然而,它本质上更多 详细。在处理具有重载方法的类时,这些方法针对 缓存,识别正确的方法确实需要额外的努力,因为参数不是一个好的鉴别器。在这些情况下,您可以使用 AspectJ 切入点 选择目标方法并应用适当的缓存功能。 但是,通过 XML,可以更轻松地应用包或组或接口范围的缓存 (同样,由于 AspectJ 切入点)并创建类似模板的定义(就像我们所做的那样) 在前面的示例中,通过属性定义目标缓存)。methodcache:definitionscache

8.5. 配置缓存存储

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

8.5.1. 基于 JDK 的缓存ConcurrentMap

基于 JDK 的实现位于包下。它允许您用作后备存储。以下示例演示如何配置两个缓存:Cacheorg.springframework.cache.concurrentConcurrentHashMapCache

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

前面的代码片段使用 为 两个名为 和 的嵌套实例。请注意, 直接为每个缓存配置名称。SimpleCacheManagerCacheManagerConcurrentMapCachedefaultbooks

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

8.5.2. 基于 Ehcache 的缓存

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

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

<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 库(通过 bean),然后将其连接到专用实现中。请注意, 整个特定于 Ehcache 的配置是从 读取的。ehcacheCacheManagerehcache.xml

8.5.3. 咖啡因缓存

Caffeine 是 Guava 缓存的 Java 8 重写,其实现位于包中,并提供对多个功能的访问 咖啡因。org.springframework.cache.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>

Caffeine 还支持 custom 和 . 有关这些内容的更多信息,请参阅 Caffeine 文档CacheManagerCaffeineCacheLoader

8.5.4. 基于 GemFire 的缓存

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

8.5.5. JSR-107 缓存

Spring 的缓存抽象也可以使用符合 JSR-107 的缓存。The 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>

在前面的链中,链接了多个实例,并且, 通过标志,为所有定义添加一个无操作缓存 由配置的缓存管理器处理。也就是说,在 either 或(在示例前面配置)由 no-op 缓存,它不存储任何信息,导致目标方法 每次调用。CompositeCacheManagerCacheManagerfallbackToNoOpCachejdkCachegemfireCache

8.6. 插入不同的后端缓存

显然,有很多缓存产品可以用作支持 商店。对于那些不支持 JSR-107 的用户,您需要提供 和 实现。这听起来可能比实际更难,因为在实践中,类 往往是映射 在存储 API 之上缓存抽象框架,就像类一样。 大多数类都可以使用包中的类(例如 注意样板代码,只留下实际的映射来完成)。CacheManagerCacheehcacheCacheManagerorg.springframework.cache.supportAbstractCacheManager

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

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

9. 附录

9.1.XML模式

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

9.1.1. 模式jee

这些元素处理与 Java EE (Java Enterprise Edition) 配置相关的问题, 例如查找 JNDI 对象和定义 EJB 引用。jee

若要使用架构中的元素,需要在顶部具有以下前导码 Spring XML 配置文件。以下代码片段中的文本引用了 更正架构,以便命名空间中的元素可供您使用:jeejee

<?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/>(简单)

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

以下示例显示如何配置对本地 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/>(复杂)

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

以下示例显示如何配置对本地 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/>

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

以下示例显示如何配置对远程 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 相关的 bean,例如 Spring 的 Message Listener Containers。这些元素在 标题为 JMS 名称空间支持的 JMS 章节。有关此支持的完整详细信息,请参阅该章 以及元素本身。jmsjms

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

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

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

要使用架构中的元素,您需要在 Spring XML 配置文件的顶部。以下代码片段中的文本引用 正确的架构,以便命名空间中的元素可供您使用:cachecache

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