这部分参考文档涵盖了 Spring Framework 与多种技术的集成。
1. REST 端点
Spring Framework 提供了两种调用 REST 端点的选择:
-
RestTemplate
:具有同步模板方法 API 的原始 Spring REST 客户端。 -
WebClient:一种非阻塞、反应式的替代方案,支持同步和异步以及流式场景。
从 5.0 开始,RestTemplate 它处于维护模式,只有较小的更改请求和错误被接受。请考虑使用
提供更现代 API 并支持同步、异步和流式传输方案
的WebClient 。 |
1.1。RestTemplate
它RestTemplate
通过 HTTP 客户端库提供了更高级别的 API。它使在一行中调用 REST 端点变得容易。它公开了以下重载方法组:
方法组 | 描述 |
---|---|
|
通过 GET 检索表示。 |
|
使用 GET检索 a |
|
使用 HEAD 检索资源的所有标头。 |
|
使用 POST 创建新资源并 |
|
使用 POST 创建新资源并从响应中返回表示。 |
|
使用 POST 创建新资源并从响应中返回表示。 |
|
使用 PUT 创建或更新资源。 |
|
使用 PATCH 更新资源并从响应中返回表示。请注意,JDK |
|
使用 DELETE 删除指定 URI 处的资源。 |
|
使用 ALLOW 检索资源的允许 HTTP 方法。 |
|
上述方法的更通用(且不那么固执)版本,可在需要时提供额外的灵活性。它接受 a 这些方法允许使用 |
|
执行请求的最通用方式,通过回调接口完全控制请求准备和响应提取。 |
1.1.1。初始化
默认构造函数用于java.net.HttpURLConnection
执行请求。您可以切换到具有ClientHttpRequestFactory
. 有对以下内容的内置支持:
-
Apache HttpComponents
-
网状
-
OkHttp
例如,要切换到 Apache HttpComponents,您可以使用以下命令:
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
每个都ClientHttpRequestFactory
公开了特定于底层 HTTP 客户端库的配置选项——例如,用于凭据、连接池和其他详细信息。
请注意,java.net 当访问表示错误的响应状态(例如 401)时,HTTP 请求的实现可能会引发异常。如果这是一个问题,请切换到另一个 HTTP 客户端库。
|
URI
许多RestTemplate
方法接受 URI 模板和 URI 模板变量,可以作为String
变量参数,也可以作为Map<String,String>
.
以下示例使用String
变量参数:
String result = restTemplate.getForObject(
"https://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");
以下示例使用Map<String, String>
:
Map<String, String> vars = Collections.singletonMap("hotel", "42");
String result = restTemplate.getForObject(
"https://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
请记住,URI 模板是自动编码的,如以下示例所示:
restTemplate.getForObject("https://example.com/hotel list", String.class);
// Results in request to "https://example.com/hotel%20list"
您可以使用 的uriTemplateHandler
属性来自RestTemplate
定义 URI 的编码方式。或者,您可以准备 ajava.net.URI
并将其传递给RestTemplate
接受 a 的方法之一URI
。
有关使用和编码 URI 的更多详细信息,请参阅URI 链接。
标头
您可以使用这些exchange()
方法来指定请求标头,如以下示例所示:
String uriTemplate = "https://example.com/hotels/{hotel}";
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42);
RequestEntity<Void> requestEntity = RequestEntity.get(uri)
.header("MyRequestHeader", "MyValue")
.build();
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();
RestTemplate
您可以通过许多返回的方法变体
获取响应标头ResponseEntity
。
1.1.2。身体
传入和从RestTemplate
方法返回的对象在HttpMessageConverter
.
在 POST 中,输入对象被序列化为请求正文,如以下示例所示:
URI 位置 = template.postForLocation("https://example.com/people", person);
您无需显式设置请求的 Content-Type 标头。在大多数情况下,您可以根据源Object
类型找到兼容的消息转换器,并且选择的消息转换器会相应地设置内容类型。如有必要,您可以使用这些
exchange
方法显式提供Content-Type
请求标头,这反过来会影响选择的消息转换器。
在 GET 上,响应的主体被反序列化为 output Object
,如以下示例所示:
Person person = restTemplate.getForObject("https://example.com/people/{id}", Person.class, 42);
请求的Accept
标头不需要显式设置。在大多数情况下,可以根据预期的响应类型找到兼容的消息转换器,然后有助于填充Accept
标头。如有必要,您可以使用这些exchange
方法显式提供Accept
标头。
默认情况下,RestTemplate
注册所有内置
消息转换器,具体取决于有助于确定存在哪些可选转换库的类路径检查。您还可以将消息转换器设置为显式使用。
1.1.3。消息转换
该spring-web
模块包含通过和HttpMessageConverter
读取和写入 HTTP 请求和响应正文的合同。
实例用于客户端(例如,在 中)和服务器端(例如,在 Spring MVC REST 控制器中)。InputStream
OutputStream
HttpMessageConverter
RestTemplate
框架中提供了主要媒体 (MIME) 类型的具体实现,默认情况下,RestTemplate
在客户端和
RequestMappingHandlerAdapter
服务器端注册(请参阅
配置消息转换器)。
的实现HttpMessageConverter
将在以下部分中描述。对于所有转换器,都使用默认媒体类型,但您可以通过设置
supportedMediaTypes
bean 属性来覆盖它。下表描述了每个实现:
消息转换器 | 描述 |
---|---|
|
可以从 HTTP 请求和响应 |
|
一种 |
|
一种 |
|
|
|
可以 |
|
一个 |
|
可以从 HTTP 请求和响应 |
|
可以从 HTTP 请求和响应 |
1.1.4。杰克逊 JSON 视图
您可以指定Jackson JSON 视图 以仅序列化对象属性的子集,如以下示例所示:
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);
RequestEntity<MappingJacksonValue> requestEntity =
RequestEntity.post(new URI("https://example.com/user")).body(value);
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
多部分
要发送多部分数据,您需要提供一个MultiValueMap<String, Object>
其值可能是一个Object
用于部分内容、一个Resource
用于文件部分或一个HttpEntity
用于带有标题的部分内容。例如:
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("fieldPart", "fieldValue");
parts.add("filePart", new FileSystemResource("...logo.png"));
parts.add("jsonPart", new Person("Jason"));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
parts.add("xmlPart", new HttpEntity<>(myBean, headers));
在大多数情况下,您不必Content-Type
为每个部分指定。内容类型是根据HttpMessageConverter
选择的序列化自动确定的,或者在Resource
基于文件扩展名的情况下。如有必要,您可以显式提供MediaType
包装HttpEntity
器。
准备好后,您可以将其MultiValueMap
传递给RestTemplate
,如下所示:
MultiValueMap<String, Object> parts = ...;
template.postForObject("https://example.com/upload", parts, Void.class);
如果MultiValueMap
包含至少一个非String
值,则由Content-Type
设置为。如果具有
值,则默认为。如有必要,也可以显式设置。multipart/form-data
FormHttpMessageConverter
MultiValueMap
String
Content-Type
application/x-www-form-urlencoded
Content-Type
1.2. 使用AsyncRestTemplate
(已弃用)
已AsyncRestTemplate
弃用。对于您可能考虑使用
AsyncRestTemplate
的所有用例,请改用WebClient。
2. 远程处理和 Web 服务
Spring 支持使用各种技术进行远程处理。远程支持简化了远程服务的开发,这些服务通过 Java 接口和对象作为输入和输出来实现。目前,Spring 支持以下远程技术:
-
Java Web 服务:Spring 通过 JAX-WS 为 Web 服务提供远程支持。
-
AMQP:独立的 Spring AMQP 项目支持通过 AMQP 作为底层协议进行远程处理。
从 Spring Framework 5.3 开始,出于安全原因和更广泛的行业支持,现在已弃用对多种远程处理技术的支持。支持基础设施将从 Spring Framework 中删除,以用于其下一个主要版本。 |
以下远程处理技术现已弃用且不会被替换:
-
远程方法调用(RMI):通过使用
RmiProxyFactoryBean
andRmiServiceExporter
,Spring 支持传统的 RMI(带有java.rmi.Remote
接口 和java.rmi.RemoteException
)和通过 RMI 调用程序(带有任何 Java 接口)的透明远程处理。 -
Spring HTTP Invoker (Deprecated):Spring 提供了一种特殊的远程处理策略,允许通过 HTTP 进行 Java 序列化,支持任何 Java 接口(就像 RMI 调用程序一样)。对应的支持类是
HttpInvokerProxyFactoryBean
和HttpInvokerServiceExporter
。 -
Hessian:通过使用 Spring
HessianProxyFactoryBean
和HessianServiceExporter
,您可以通过 Caucho 提供的基于 HTTP 的轻量级二进制协议透明地公开您的服务。 -
JMS(已弃用) :通过模块中的
JmsInvokerServiceExporter
和JmsInvokerProxyFactoryBean
类 支持通过 JMS 作为底层协议进行远程处理 。spring-jms
在讨论 Spring 的远程处理能力时,我们使用以下领域模型和相应的服务:
public class Account implements Serializable {
private String name;
public String getName(){
return name;
}
public void setName(String name) {
this.name = name;
}
}
public interface AccountService {
public void insertAccount(Account account);
public List<Account> getAccounts(String name);
}
// the implementation doing nothing at the moment
public class AccountServiceImpl implements AccountService {
public void insertAccount(Account acc) {
// do something...
}
public List<Account> getAccounts(String name) {
// do something...
}
}
本节首先使用 RMI 将服务公开给远程客户端,并讨论一下使用 RMI 的缺点。然后继续使用 Hessian 作为协议的示例。
2.1。AMQP
Spring AMQP 项目支持通过 AMQP 作为底层协议进行远程处理。有关详细信息,请访问 Spring AMQP 参考的Spring Remoting 部分。
远程接口没有实现自动检测。 远程接口不会自动检测已实现接口的主要原因是避免为远程调用者打开太多门。目标对象可能实现内部回调接口,例如 在本地情况下,提供具有由目标实现的所有接口的代理通常无关紧要。但是,当您导出远程服务时,您应该公开一个特定的服务接口,以及用于远程使用的特定操作。除了内部回调接口,目标可能实现多个业务接口,其中只有一个用于远程公开。由于这些原因,我们需要指定这样的服务接口。 这是配置便利性和内部方法意外暴露风险之间的权衡。总是指定一个服务接口并不费力,并且在特定方法的受控公开方面让您处于安全的一边。 |
2.2. 选择技术时的注意事项
这里介绍的每一项技术都有其缺点。在选择技术时,您应该仔细考虑您的需求、您公开的服务以及您通过网络发送的对象。
使用 RMI 时,您无法通过 HTTP 协议访问对象,除非您通过隧道传输 RMI 流量。RMI 是一个相当重量级的协议,因为它支持全对象序列化,这在您使用需要通过网络进行序列化的复杂数据模型时非常重要。但是,RMI-JRMP 与 Java 客户端相关联。它是一种 Java 到 Java 的远程解决方案。
如果您需要基于 HTTP 的远程处理但又依赖于 Java 序列化,那么 Spring 的 HTTP 调用程序是一个不错的选择。它与 RMI 调用程序共享基本基础架构,但使用 HTTP 作为传输。请注意,HTTP 调用程序不仅限于 Java 到 Java 远程处理,还包括客户端和服务器端的 Spring。(后者也适用于 Spring 的非 RMI 接口的 RMI 调用程序。)
在异构环境中运行时,Hessian 可能会提供重要的价值,因为它们明确允许非 Java 客户端。但是,非 Java 支持仍然有限。已知问题包括 Hibernate 对象的序列化与延迟初始化的集合相结合。如果您有这样的数据模型,请考虑使用 RMI 或 HTTP 调用程序而不是 Hessian。
JMS 可用于提供服务集群并让 JMS 代理负责负载平衡、发现和自动故障转移。默认情况下,Java 序列化用于 JMS 远程处理,但 JMS 提供者可以使用不同的机制进行有线格式化,例如 XStream 以让服务器在其他技术中实现。
最后但同样重要的是,EJB 优于 RMI,因为它支持标准的基于角色的身份验证和授权以及远程事务传播。也可以让 RMI 调用程序或 HTTP 调用程序支持安全上下文传播,尽管核心 Spring 不提供这一点。Spring 仅提供适当的钩子用于插入第三方或自定义解决方案。
2.3. Java 网络服务
Spring 提供对标准 Java Web 服务 API 的全面支持:
-
使用 JAX-WS 公开 Web 服务
-
使用 JAX-WS 访问 Web 服务
除了 Spring Core 中对 JAX-WS 的库存支持之外,Spring 产品组合还具有Spring Web Services,这是一种合同优先、文档驱动的 Web 服务的解决方案——强烈推荐用于构建现代、面向未来的 Web 服务。
2.3.1。使用 JAX-WS 公开基于 Servlet 的 Web 服务
Spring 为 JAX-WS servlet 端点实现提供了一个方便的基类:
SpringBeanAutowiringSupport
. 为了公开我们的AccountService
,我们扩展了 Spring 的
SpringBeanAutowiringSupport
类并在这里实现了我们的业务逻辑,通常将调用委托给业务层。我们使用 Spring 的@Autowired
注解来表达对 Spring 管理的 bean 的这种依赖关系。下面的例子展示了我们扩展的类SpringBeanAutowiringSupport
:
/**
* JAX-WS compliant AccountService implementation that simply delegates
* to the AccountService implementation in the root web application context.
*
* This wrapper class is necessary because JAX-WS requires working with dedicated
* endpoint classes. If an existing service needs to be exported, a wrapper that
* extends SpringBeanAutowiringSupport for simple Spring bean autowiring (through
* the @Autowired annotation) is the simplest JAX-WS compliant way.
*
* This is the class registered with the server-side JAX-WS implementation.
* In the case of a Java EE server, this would simply be defined as a servlet
* in web.xml, with the server detecting that this is a JAX-WS endpoint and reacting
* accordingly. The servlet name usually needs to match the specified WS service name.
*
* The web service engine manages the lifecycle of instances of this class.
* Spring bean references will just be wired in here.
*/
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
@WebService(serviceName="AccountService")
public class AccountServiceEndpoint extends SpringBeanAutowiringSupport {
@Autowired
private AccountService biz;
@WebMethod
public void insertAccount(Account acc) {
biz.insertAccount(acc);
}
@WebMethod
public Account[] getAccounts(String name) {
return biz.getAccounts(name);
}
}
我们AccountServiceEndpoint
需要在与 Spring 上下文相同的 Web 应用程序中运行,以允许访问 Spring 的设施。在 Java EE 环境中默认情况下就是这种情况,使用 JAX-WS servlet 端点部署的标准协定。有关详细信息,请参阅各种 Java EE Web 服务教程。
2.3.2. 使用 JAX-WS 导出独立 Web 服务
Oracle JDK 附带的内置 JAX-WS 提供程序支持通过使用 JDK 中也包含的内置 HTTP 服务器公开 Web 服务。Spring
在 Spring 应用程序上下文中SimpleJaxWsServiceExporter
检测所有带注释的@WebService
bean,并通过默认的 JAX-WS 服务器(JDK HTTP 服务器)将它们导出。
在这种情况下,端点实例作为 Spring bean 本身定义和管理。它们在 JAX-WS 引擎中注册,但它们的生命周期取决于 Spring 应用程序上下文。这意味着您可以将 Spring 功能(例如显式依赖注入)应用于端点实例。注释驱动的注入也可以通过@Autowired
作品进行。以下示例显示了如何定义这些 bean:
<bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter">
<property name="baseAddress" value="http://localhost:8080/"/>
</bean>
<bean id="accountServiceEndpoint" class="example.AccountServiceEndpoint">
...
</bean>
...
AccountServiceEndpoint
可以但不必从 Spring 派生SpringBeanAutowiringSupport
,因为此示例中的端点是完全由 Spring 管理的 bean 。这意味着端点实现可以如下(没有声明任何超类 - 并且@Autowired
仍然遵循 Spring 的配置注释):
@WebService(serviceName="AccountService")
public class AccountServiceEndpoint {
@Autowired
private AccountService biz;
@WebMethod
public void insertAccount(Account acc) {
biz.insertAccount(acc);
}
@WebMethod
public List<Account> getAccounts(String name) {
return biz.getAccounts(name);
}
}
2.3.3。使用 JAX-WS RI 的 Spring 支持导出 Web 服务
Oracle 的 JAX-WS RI 是作为 GlassFish 项目的一部分开发的,将 Spring 支持作为其 JAX-WS Commons 项目的一部分提供。这允许将 JAX-WS 端点定义为 Spring 管理的 bean,类似于上 一节中讨论的独立模式 ——但这次是在 Servlet 环境中。
这在 Java EE 环境中是不可移植的。它主要用于将 JAX-WS RI 作为 Web 应用程序的一部分嵌入的非 EE 环境,例如 Tomcat。 |
与导出基于 servlet 的端点的标准样式的不同之处在于端点实例本身的生命周期由 Spring 管理,并且在web.xml
. 使用标准 Java EE 样式(如前所示),每个服务端点都有一个 servlet 定义,每个端点通常委托给 Spring bean(通过使用@Autowired
,如前所示)。
有关设置和使用方式的详细信息,请参阅https://jax-ws-commons.java.net/spring/ 。
2.3.4。使用 JAX-WS 访问 Web 服务
LocalJaxWsServiceFactoryBean
Spring 提供了两个工厂 bean 来创建 JAX-WS Web 服务代理,即
JaxWsPortProxyFactoryBean
. 前者只能返回一个 JAX-WS 服务类供我们使用。后者是可以返回实现我们业务服务接口的代理的完整版本。在以下示例中,我们使用(再次)JaxWsPortProxyFactoryBean
为
AccountService
端点创建代理:
<bean id="accountWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
<property name="serviceInterface" value="example.AccountService"/> (1)
<property name="wsdlDocumentUrl" value="http://localhost:8888/AccountServiceEndpoint?WSDL"/>
<property name="namespaceUri" value="https://example/"/>
<property name="serviceName" value="AccountService"/>
<property name="portName" value="AccountServiceEndpointPort"/>
</bean>
1 | serviceInterface 客户使用的我们的业务接口在哪里。 |
wsdlDocumentUrl
是 WSDL 文件的 URL。Spring 在启动时需要它来创建 JAX-WS 服务。namespaceUri
对应targetNamespace
于 .wsdl 文件中的 。serviceName
对应于 .wsdl 文件中的服务名称。portName
对应于 .wsdl 文件中的端口名称。
访问 Web 服务很容易,因为我们有一个 bean 工厂,将其公开为一个名为AccountService
. 以下示例显示了我们如何在 Spring 中将其连接起来:
<bean id="client" class="example.AccountClientImpl">
...
<property name="service" ref="accountWebService"/>
</bean>
从客户端代码中,我们可以像访问普通类一样访问 Web 服务,如以下示例所示:
public class AccountClientImpl {
private AccountService service;
public void setService(AccountService service) {
this.service = service;
}
public void foo() {
service.insertAccount(...);
}
}
上面稍微简化了一点,因为 JAX-WS 要求端点接口和实现类使用 、 等注释进行@WebService 注释@SOAPBinding 。这意味着您不能(轻松地)使用纯 Java 接口和实现类作为 JAX-WS 端点工件;您需要先相应地注释它们。查看 JAX-WS 文档以获取有关这些要求的详细信息。
|
2.4. RMI(已弃用)
从 Spring Framework 5.3 开始,RMI 支持已被弃用并且不会被替换。 |
通过使用 Spring 对 RMI 的支持,您可以通过 RMI 基础架构透明地公开您的服务。完成此设置后,您基本上拥有类似于远程 EJB 的配置,除了没有对安全上下文传播或远程事务传播的标准支持这一事实。当您使用 RMI 调用程序时,Spring 确实为此类额外的调用上下文提供了挂钩,因此您可以插入安全框架或自定义安全凭证。
2.4.1。通过使用导出服务RmiServiceExporter
使用RmiServiceExporter
,我们可以将 AccountService 对象的接口公开为 RMI 对象。可以使用 访问接口RmiProxyFactoryBean
,或者在传统 RMI 服务的情况下通过普通 RMI 访问该接口。明确支持通过RmiServiceExporter
RMI 调用程序公开任何非 RMI 服务。
我们首先必须在 Spring 容器中设置我们的服务。以下示例显示了如何执行此操作:
<bean id="accountService" class="example.AccountServiceImpl">
<!-- any additional properties, maybe a DAO? -->
</bean>
接下来,我们必须使用RmiServiceExporter
. 以下示例显示了如何执行此操作:
<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
<!-- does not necessarily have to be the same name as the bean to be exported -->
<property name="serviceName" value="AccountService"/>
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="example.AccountService"/>
<!-- defaults to 1099 -->
<property name="registryPort" value="1199"/>
</bean>
在前面的示例中,我们覆盖了 RMI 注册表的端口。通常,您的应用程序服务器还维护一个 RMI 注册表,明智的做法是不要干扰该注册表。此外,服务名称用于绑定服务。因此,在前面的示例中,服务绑定在'rmi://HOST:1199/AccountService'
。我们稍后会使用此 URL 链接客户端的服务。
该servicePort 属性已被省略(默认为 0)。这意味着使用匿名端口与服务进行通信。
|
2.4.2. 在客户端链接服务
我们的客户端是一个简单的对象,它使用AccountService
来管理帐户,如以下示例所示:
public class SimpleObject {
private AccountService accountService;
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
// additional methods using the accountService
}
为了在客户端链接服务,我们创建了一个单独的 Spring 容器,以包含以下简单对象和服务链接配置位:
<bean class="example.SimpleObject">
<property name="accountService" ref="accountService"/>
</bean>
<bean id="accountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl" value="rmi://HOST:1199/AccountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
这就是我们在客户端支持远程帐户服务所需要做的一切。Spring 透明地创建一个调用者并通过
RmiServiceExporter
. 在客户端,我们使用RmiProxyFactoryBean
.
2.5. 使用 Hessian 通过 HTTP 远程调用服务(已弃用)
从 Spring Framework 5.3 开始,不推荐使用 Hessian 支持,并且不会被替换。 |
Hessian 提供了一个基于 HTTP 的二进制远程协议。它由 Caucho 开发,您可以在https://www.caucho.com/找到有关 Hessian 本身的更多信息。
2.5.1。黑森州
Hessian 通过 HTTP 进行通信,并使用自定义 servlet 进行通信。通过使用 Spring 的
DispatcherServlet
原理(参见webmvc.html),我们可以连接这样一个 servlet 来公开您的服务。首先,我们必须在我们的应用程序中创建一个新的 servlet,如以下摘录所示web.xml
:
<servlet>
<servlet-name>remoting</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>remoting</servlet-name>
<url-pattern>/remoting/*</url-pattern>
</servlet-mapping>
如果您熟悉 Spring 的DispatcherServlet
原理,您可能知道现在您必须在目录中创建一个名为
remoting-servlet.xml
(在您的 servlet 名称之后)的Spring 容器配置资源WEB-INF
。应用程序上下文将在下一节中使用。
或者,考虑使用 Spring 更简单的HttpRequestHandlerServlet
. 这样做可以让您将远程导出器定义嵌入到您的根应用程序上下文中(默认情况下,在 中WEB-INF/applicationContext.xml
),其中各个 servlet 定义指向特定的导出器 bean。在这种情况下,每个 servlet 名称都需要与其目标导出器的 bean 名称相匹配。
2.5.2. 通过使用暴露你的 BeanHessianServiceExporter
在新创建的名为 的应用程序上下文中remoting-servlet.xml
,我们创建一个
HessianServiceExporter
来导出我们的服务,如以下示例所示:
<bean id="accountService" class="example.AccountServiceImpl">
<!-- any additional properties, maybe a DAO? -->
</bean>
<bean name="/AccountService" class="org.springframework.remoting.caucho.HessianServiceExporter">
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
现在我们准备在客户端链接服务。没有指定显式处理程序映射(将请求 URL 映射到服务),所以我们使用BeanNameUrlHandlerMapping
used. 因此,服务在包含DispatcherServlet
实例的映射(如前所述)中通过其 bean 名称指示的 URL 导出:
https://HOST:8080/remoting/AccountService
。
或者,您可以HessianServiceExporter
在根应用程序上下文中创建一个(例如,在 中WEB-INF/applicationContext.xml
),如以下示例所示:
<bean name="accountExporter" class="org.springframework.remoting.caucho.HessianServiceExporter">
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
在后一种情况下,您应该在 中为该导出器定义一个相应的 servlet,web.xml
最终结果相同:导出器被映射到在 中的请求路径
/remoting/AccountService
。请注意,servlet 名称需要与目标导出器的 bean 名称匹配。以下示例显示了如何执行此操作:
<servlet>
<servlet-name>accountExporter</servlet-name>
<servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>accountExporter</servlet-name>
<url-pattern>/remoting/AccountService</url-pattern>
</servlet-mapping>
2.5.3. 在客户端连接服务
通过使用HessianProxyFactoryBean
,我们可以在客户端链接服务。与 RMI 示例相同的原则适用。SimpleObject
我们创建一个单独的 bean 工厂或应用程序上下文,并通过使用管理帐户来提及以下 bean AccountService
,如以下示例所示:
<bean class="example.SimpleObject">
<property name="accountService" ref="accountService"/>
</bean>
<bean id="accountService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
<property name="serviceUrl" value="https://remotehost:8080/remoting/AccountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
2.5.4。将 HTTP 基本身份验证应用于通过 Hessian 公开的服务
Hessian 的优点之一是我们可以轻松应用 HTTP 基本身份验证,因为这两种协议都是基于 HTTP 的。例如,您可以通过使用web.xml
安全功能来应用您正常的 HTTP 服务器安全机制。通常,您不需要在此处使用每个用户的安全凭证。相反,您可以使用在HessianProxyFactoryBean
级别定义的共享凭证(类似于 JDBC DataSource
),如以下示例所示:
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="interceptors" ref="authorizationInterceptor"/>
</bean>
<bean id="authorizationInterceptor"
class="org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor">
<property name="authorizedRoles" value="administrator,operator"/>
</bean>
在前面的示例中,我们明确提到了BeanNameUrlHandlerMapping
and 设置了一个拦截器,以便只有管理员和操作员才能调用此应用程序上下文中提到的 bean。
前面的例子没有展示一种灵活的安全基础设施。有关安全性的更多选项,请查看https://spring.io/projects/spring-security/上的 Spring Security 项目。 |
2.6. Spring HTTP 调用程序(已弃用)
从 Spring Framework 5.3 开始,HTTP Invoker 支持已弃用且不会被替换。 |
与 Hessian 不同,Spring HTTP 调用程序都是轻量级协议,它们使用自己的苗条序列化机制,并使用标准的 Java 序列化机制通过 HTTP 公开服务。如果您的参数和返回类型是无法通过使用 Hessian 使用的序列化机制进行序列化的复杂类型,这将具有巨大的优势(当您选择远程处理技术时,请参阅下一节以了解更多注意事项)。
在底层,Spring 使用 JDK 或 Apache 提供的标准工具HttpComponents
来执行 HTTP 调用。如果您需要更高级和更易于使用的功能,请使用后者。有关更多信息,请参阅
hc.apache.org/httpcomponents-client-ga/
。
请注意由于不安全的 Java 反序列化导致的漏洞:在反序列化步骤期间,被操纵的输入流可能会导致服务器上不需要的代码执行。因此,不要将 HTTP 调用程序端点暴露给不受信任的客户端。相反,仅在您自己的服务之间公开它们。通常,我们强烈建议使用任何其他消息格式(例如 JSON)。 如果您担心 Java 序列化导致的安全漏洞,请考虑核心 JVM 级别的通用序列化过滤器机制,该机制最初是为 JDK 9 开发的,但同时向后移植到 JDK 8、7 和 6。请参阅 https://blogs.oracle.com/java-platform-group/entry/incoming_filter_serialization_data_a 和https://openjdk.java.net/jeps/290。 |
2.6.1。暴露服务对象
为服务对象设置 HTTP 调用程序基础结构与使用 Hessian 执行相同操作的方式非常相似。正如 Hessian 支持提供
的那样HessianServiceExporter
,Spring 的 HttpInvoker 支持提供
org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter
.
要在 Spring Web MVC 中公开AccountService
(前面提到的)
DispatcherServlet
,需要在调度程序的应用程序上下文中进行以下配置,如以下示例所示:
<bean name="/AccountService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
如Hessian 部分所述,此类导出器定义通过DispatcherServlet
实例的标准映射工具公开。
或者,您可以在根应用程序上下文中创建一个HttpInvokerServiceExporter
(例如, in 'WEB-INF/applicationContext.xml'
),如以下示例所示:
<bean name="accountExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
此外,您可以在 中为这个导出器定义一个对应web.xml
的 servlet,servlet 名称与目标导出器的 bean 名称匹配,如下例所示:
<servlet>
<servlet-name>accountExporter</servlet-name>
<servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>accountExporter</servlet-name>
<url-pattern>/remoting/AccountService</url-pattern>
</servlet-mapping>
2.6.2. 在客户端链接服务
同样,从客户端链接服务与使用 Hessian 时的操作方式非常相似。通过使用代理,Spring 可以将您对 HTTP POST 请求的调用转换为指向导出服务的 URL。以下示例显示了如何配置此排列:
<bean id="httpInvokerProxy" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl" value="https://remotehost:8080/remoting/AccountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
如前所述,您可以选择要使用的 HTTP 客户端。默认情况下,
HttpInvokerProxy
使用 JDK 的 HTTP 功能,但您也可以
HttpComponents
通过设置httpInvokerRequestExecutor
属性来使用 Apache 客户端。以下示例显示了如何执行此操作:
<property name="httpInvokerRequestExecutor">
<bean class="org.springframework.remoting.httpinvoker.HttpComponentsHttpInvokerRequestExecutor"/>
</property>
2.7. JMS(已弃用)
从 Spring Framework 5.3 开始,JMS 远程处理支持已弃用且不会被替换。 |
您还可以使用 JMS 作为底层通信协议透明地公开服务。Spring Framework 中的 JMS 远程处理支持非常基础。same thread
它在同一非事务性的 和 上发送和接收Session
。因此,吞吐量取决于实现。请注意,这些单线程和非事务性约束仅适用于 Spring 的 JMS 远程处理支持。有关Spring 对基于 JMS 的消息传递的丰富支持的信息,请参阅JMS(Java 消息服务) 。
服务器端和客户端都使用以下接口:
package com.foo;
public interface CheckingAccountService {
public void cancelAccount(Long accountId);
}
在服务器端使用上述接口的以下简单实现:
package com.foo;
public class SimpleCheckingAccountService implements CheckingAccountService {
public void cancelAccount(Long accountId) {
System.out.println("Cancelling account [" + accountId + "]");
}
}
以下配置文件包含在客户端和服务器上共享的 JMS 基础结构 bean:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://ep-t43:61616"/>
</bean>
<bean id="queue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="mmm"/>
</bean>
</beans>
2.7.1。服务器端配置
在服务器上,您需要公开使用 的服务对象
JmsInvokerServiceExporter
,如以下示例所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="checkingAccountService"
class="org.springframework.jms.remoting.JmsInvokerServiceExporter">
<property name="serviceInterface" value="com.foo.CheckingAccountService"/>
<property name="service">
<bean class="com.foo.SimpleCheckingAccountService"/>
</property>
</bean>
<bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="queue"/>
<property name="concurrentConsumers" value="3"/>
<property name="messageListener" ref="checkingAccountService"/>
</bean>
</beans>
package com.foo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Server {
public static void main(String[] args) throws Exception {
new ClassPathXmlApplicationContext("com/foo/server.xml", "com/foo/jms.xml");
}
}
2.7.2. 客户端配置
客户端只需要创建一个客户端代理来实现约定的接口(CheckingAccountService
)。
以下示例定义了可以注入其他客户端对象的 bean(代理负责通过 JMS 将调用转发到服务器端对象):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="checkingAccountService"
class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean">
<property name="serviceInterface" value="com.foo.CheckingAccountService"/>
<property name="connectionFactory" ref="connectionFactory"/>
<property name="queue" ref="queue"/>
</bean>
</beans>
package com.foo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Client {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/foo/client.xml", "com/foo/jms.xml");
CheckingAccountService service = (CheckingAccountService) ctx.getBean("checkingAccountService");
service.cancelAccount(new Long(10));
}
}
3. 企业 JavaBeans (EJB) 集成
作为轻量级容器,Spring 通常被认为是 EJB 的替代品。我们确实相信,对于许多(如果不是大多数)应用程序和用例,Spring 作为一个容器,结合其在事务、ORM 和 JDBC 访问领域的丰富支持功能,是比通过 EJB 实现等效功能更好的选择容器和 EJB。
但是,重要的是要注意使用 Spring 并不会阻止您使用 EJB。事实上,Spring 使得访问 EJB 和在其中实现 EJB 和功能变得更加容易。此外,使用 Spring 访问 EJB 提供的服务允许这些服务的实现稍后在本地 EJB、远程 EJB 或 POJO(普通旧 Java 对象)变体之间透明地切换,而无需更改客户端代码。
在本章中,我们将了解 Spring 如何帮助您访问和实现 EJB。Spring 在访问无状态会话 bean (SLSB) 时提供了特殊的价值,因此我们从讨论这个主题开始。
3.1。访问 EJB
本节介绍如何访问 EJB。
3.1.1。概念
要调用本地或远程无状态会话 bean 上的方法,客户端代码通常必须执行 JNDI 查找以获取(本地或远程)EJB Home 对象,然后使用create
该对象上的方法调用来获取实际的(本地或远程) EJB 对象。然后在 EJB 上调用一个或多个方法。
为了避免重复的低级代码,许多 EJB 应用程序使用 Service Locator 和 Business Delegate 模式。这些比在整个客户端代码中喷洒 JNDI 查找要好,但它们通常的实现有明显的缺点:
-
通常,使用 EJB 的代码依赖于 Service Locator 或 Business Delegate 单例,因此很难进行测试。
-
在不使用业务委托的情况下使用服务定位器模式的情况下,应用程序代码最终仍然必须调用
create()
EJB 主目录上的方法并处理产生的异常。因此,它仍然与 EJB API 和 EJB 编程模型的复杂性相关联。 -
实现业务委托模式通常会导致大量代码重复,我们必须编写大量方法来调用 EJB 上的相同方法。
Spring 方法是允许创建和使用代理对象(通常配置在 Spring 容器中),它们充当无代码业务委托。您无需编写另一个服务定位器、另一个 JNDI 查找或在手动编码的业务委托中重复方法,除非您实际上在此类代码中添加了真正的价值。
3.1.2。访问本地 SLSB
假设我们有一个需要使用本地 EJB 的 Web 控制器。我们遵循最佳实践并使用 EJB 业务方法接口模式,以便 EJB 的本地接口扩展非 EJB 特定的业务方法接口。我们称之为业务方法接口MyComponent
。下面的例子展示了这样一个界面:
public interface MyComponent {
...
}
使用业务方法接口模式的主要原因之一是确保本地接口和 bean 实现类中的方法签名之间的同步是自动的。另一个原因是,如果这样做有意义的话,它使我们更容易切换到服务的 POJO(普通旧 Java 对象)实现。我们还需要实现本地home接口,并提供实现类SessionBean
和MyComponent
业务方法接口。现在,为了将 Web 层控制器连接到 EJB 实现,我们需要做的唯一 Java 编码就是在控制器上公开一个类型的 setter 方法MyComponent
。这会将引用保存为控制器中的实例变量。以下示例显示了如何执行此操作:
private MyComponent myComponent;
public void setMyComponent(MyComponent myComponent) {
this.myComponent = myComponent;
}
我们随后可以在控制器中的任何业务方法中使用此实例变量。现在,假设我们从 Spring 容器中获取控制器对象,我们可以(在相同的上下文中)配置一个LocalStatelessSessionProxyFactoryBean
实例,它是 EJB 代理对象。我们配置代理并
myComponent
使用以下配置条目设置控制器的属性:
<bean id="myComponent"
class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
<property name="jndiName" value="ejb/myBean"/>
<property name="businessInterface" value="com.mycom.MyComponent"/>
</bean>
<bean id="myController" class="com.mycom.myController">
<property name="myComponent" ref="myComponent"/>
</bean>
许多工作发生在幕后,由 Spring AOP 框架提供,尽管您不必被迫使用 AOP 概念来享受结果。bean 定义为 EJB 创建了一个代理,
myComponent
它实现了业务方法接口。EJB 本地主目录在启动时被缓存,因此只有一个 JNDI 查找。每次调用 EJB 时,代理都会调用classname
本地 EJB 上的方法,并调用 EJB 上相应的业务方法。
myController
bean 定义将控制器类的属性myComponent
设置为 EJB 代理。
或者(并且最好在许多这样的代理定义的情况下),考虑使用<jee:local-slsb>
Spring 的“jee”命名空间中的配置元素。以下示例显示了如何执行此操作:
<jee:local-slsb id="myComponent" jndi-name="ejb/myBean"
business-interface="com.mycom.MyComponent"/>
<bean id="myController" class="com.mycom.myController">
<property name="myComponent" ref="myComponent"/>
</bean>
这种 EJB 访问机制极大地简化了应用程序代码。Web 层代码(或其他 EJB 客户端代码)不依赖于 EJB 的使用。要用 POJO 或模拟对象或其他测试存根替换此 EJB 引用,我们可以在myComponent
不更改一行 Java 代码的情况下更改 bean 定义。此外,作为我们应用程序的一部分,我们不必编写一行 JNDI 查找或其他 EJB 管道代码。
实际应用程序中的基准和经验表明,这种方法(涉及目标 EJB 的反射调用)的性能开销很小,并且在典型使用中无法检测到。请记住,无论如何我们都不想对 EJB 进行细粒度的调用,因为应用服务器中的 EJB 基础结构会产生相关成本。
关于 JNDI 查找有一个警告。在 bean 容器中,此类通常最好用作单例(没有理由将其作为原型)。但是,如果该 bean 容器预先实例化了单例(各种 XML
ApplicationContext
变体也是如此),如果在 EJB 容器加载目标 EJB 之前加载了 bean 容器,那么您可能会遇到问题。那是因为在init()
这个类的方法中进行了JNDI查找,然后缓存了,但是EJB还没有绑定到目标位置。解决方案是不预先实例化这个工厂对象,而是让它在第一次使用时创建。在 XML 容器中,您可以使用lazy-init
属性来控制它。
尽管大多数 Spring 用户不感兴趣,但那些使用 EJB 进行编程 AOP 的人可能希望查看LocalSlsbInvokerInterceptor
.
3.1.3. 访问远程 SLSB
访问远程 EJB 本质上与访问本地 EJB 相同,只是使用了
SimpleRemoteStatelessSessionProxyFactoryBean
or<jee:remote-slsb>
配置元素。当然,不管有没有 Spring,远程调用语义都适用:对另一台计算机中另一个 VM 中的对象上的方法的调用有时必须在使用场景和故障处理方面进行不同的处理。
Spring 的 EJB 客户端支持比非 Spring 方法增加了一项优势。通常,在本地或远程调用 EJB 之间轻松地来回切换 EJB 客户端代码是有问题的。这是因为远程接口方法必须声明它们 throw RemoteException
,并且客户端代码必须处理这个,而本地接口方法不需要。为需要移动到远程 EJB 的本地 EJB 编写的客户端代码通常必须进行修改以添加对远程异常的处理,并且为需要移动到本地 EJB 的远程 EJB 编写的客户端代码可以保持不变,但执行许多不必要的远程异常处理或被修改以删除该代码。使用 Spring 远程 EJB 代理,您可以不声明任何抛出的RemoteException
在您的业务方法接口和实现 EJB 代码中,有一个相同的远程接口(除了它确实 throw RemoteException
),并依靠代理动态地处理这两个接口,就好像它们是相同的一样。也就是说,客户端代码不必处理被检查的RemoteException
类。在 EJB 调用期间抛出的任何实际RemoteException
值都将作为非检查RemoteAccessException
类重新抛出,该类是RuntimeException
. 然后,您可以在本地 EJB 或远程 EJB(甚至纯 Java 对象)实现之间随意切换目标服务,而无需客户端代码知道或关心。当然,这是可选的:没有什么能阻止您RemoteException
在业务界面中声明。
3.1.4。访问 EJB 2.x SLSB 与 EJB 3 SLSB
通过 Spring 访问 EJB 2.x 会话 Bean 和 EJB 3 会话 Bean 在很大程度上是透明的。Spring 的 EJB 访问器,包括<jee:local-slsb>
and
<jee:remote-slsb>
工具,在运行时透明地适应实际的组件。如果找到主接口(EJB 2.x 样式),它们将处理主接口;如果没有可用的主接口(EJB 3 样式),它们将直接执行组件调用。
注意:对于 EJB 3 会话 Bean,您也可以有效地使用JndiObjectFactoryBean
/
<jee:jndi-lookup>
,因为在那里公开了完全可用的组件引用以用于普通的 JNDI 查找。定义显式<jee:local-slsb>
或<jee:remote-slsb>
查找提供一致且更显式的 EJB 访问配置。
4. JMS(Java消息服务)
Spring 提供了一个 JMS 集成框架,它简化了 JMS API 的使用,其方式与 Spring 对 JDBC API 的集成非常相似。
JMS的功能大致可以分为两个领域,即消息的生产和消费。该类JmsTemplate
用于消息生成和同步消息接收。对于类似于 Java EE 的消息驱动 bean 样式的异步接收,Spring 提供了许多消息侦听器容器,您可以使用它们来创建消息驱动 POJO (MDP)。Spring 还提供了一种声明式的方式来创建消息监听器。
该org.springframework.jms.core
包提供了使用 JMS 的核心功能。它包含 JMS 模板类,通过处理资源的创建和释放来简化 JMS 的使用,就像JdbcTemplate
JDBC 所做的那样。Spring 模板类的共同设计原则是提供帮助方法来执行常见操作,并且为了更复杂的使用,将处理任务的本质委托给用户实现的回调接口。JMS 模板遵循相同的设计。这些类提供了各种方便的方法来发送消息、同步使用消息以及向用户公开 JMS 会话和消息生产者。
该org.springframework.jms.support
软件包提供JMSException
翻译功能。转换将检查JMSException
层次结构转换为未检查异常的镜像层次结构。如果javax.jms.JMSException
存在任何已检查的特定于提供程序的子类,则此异常将包装在未检查的UncategorizedJmsException
.
该org.springframework.jms.support.converter
包提供了MessageConverter
在 Java 对象和 JMS 消息之间进行转换的抽象。
该org.springframework.jms.support.destination
包提供了各种管理 JMS 目的地的策略,例如为存储在 JNDI 中的目的地提供服务定位器。
该org.springframework.jms.annotation
包提供了必要的基础设施来支持注释驱动的侦听器端点,方法是使用@JmsListener
.
该org.springframework.jms.config
包提供了
jms
命名空间的解析器实现以及用于配置侦听器容器和创建侦听器端点的 java config 支持。
最后,该org.springframework.jms.connection
包提供了ConnectionFactory
适用于独立应用程序的实现。它还包含 Spring 的PlatformTransactionManager
JMS 实现(巧妙地命名为
JmsTransactionManager
)。这允许将 JMS 作为事务资源无缝集成到 Spring 的事务管理机制中。
从 Spring Framework 5 开始,Spring 的 JMS 包完全支持 JMS 2.0,并且要求 JMS 2.0 API 在运行时存在。我们建议使用与 JMS 2.0 兼容的提供程序。 如果您碰巧在系统中使用了较旧的消息代理,您可以尝试为现有代理生成升级到与 JMS 2.0 兼容的驱动程序。或者,您也可以尝试针对基于 JMS 1.1 的驱动程序运行,只需将 JMS 2.0 API jar 放在类路径中,但仅针对您的驱动程序使用与 JMS 1.1 兼容的 API。Spring 的 JMS 支持默认遵循 JMS 1.1 约定,因此通过相应的配置,它确实支持这种情况。但是,请仅在过渡场景中考虑这一点。 |
4.1。使用 Spring JMS
本节介绍如何使用 Spring 的 JMS 组件。
4.1.1。使用JmsTemplate
该类JmsTemplate
是 JMS 核心包中的中心类。它简化了 JMS 的使用,因为它在发送或同步接收消息时处理资源的创建和释放。
仅使用JmsTemplate
需要实现回调接口的代码,这些接口为它们提供了明确定义的高级合同。回调接口在给定调用代码提供的时MessageCreator
创建一条消息。为了允许更复杂地使用 JMS API,提供 JMS 会话,并公开一个和
对。Session
JmsTemplate
SessionCallback
ProducerCallback
Session
MessageProducer
JMS API 公开了两种类型的发送方法,一种采用交付模式、优先级和生存时间作为服务质量 (QOS) 参数,另一种采用不采用 QOS 参数并使用默认值。由于JmsTemplate
有许多发送方法,设置 QOS 参数已作为 bean 属性公开,以避免发送方法数量的重复。同样,同步接收调用的超时值是使用该setReceiveTimeout
属性设置的。
一些 JMS 提供程序允许通过ConnectionFactory
. 这会导致调用
MessageProducer
实例的send
方法 ( send(Destination destination, Message message)
) 使用与 JMS 规范中指定的不同的 QOS 默认值。因此,为了提供对 QOS 值的一致管理,JmsTemplate
必须通过将布尔属性设置为 来专门启用它以使用其自己的 QOS
isExplicitQosEnabled
值true
。
为方便起见,JmsTemplate
还公开了一个基本的请求-回复操作,该操作允许在作为操作的一部分创建的临时队列上发送消息并等待回复。
一旦配置,该类的实例JmsTemplate 是线程安全的。这很重要,因为这意味着您可以配置 a 的单个实例,JmsTemplate
然后安全地将这个共享引用注入到多个协作者中。需要明确的是,JmsTemplate 是有状态的,因为它维护对 a 的引用
ConnectionFactory ,但这种状态不是会话状态。
|
从 Spring Framework 4.1 开始,JmsMessagingTemplate
构建在消息传递抽象之上JmsTemplate
并提供与消息传递抽象的集成——即
org.springframework.messaging.Message
. 这使您可以创建要以通用方式发送的消息。
4.1.2. 连接
需要对JmsTemplate
a 的引用ConnectionFactory
。它ConnectionFactory
是 JMS 规范的一部分,用作使用 JMS 的入口点。它被客户端应用程序用作工厂来创建与 JMS 提供者的连接并封装各种配置参数,其中许多是特定于供应商的,例如 SSL 配置选项。
在 EJB 中使用 JMS 时,供应商提供 JMS 接口的实现,以便他们可以参与声明性事务管理并执行连接和会话的池化。为了使用此实现,Java EE 容器通常要求您将 JMS 连接工厂声明为resource-ref
EJB 或 servlet 部署描述符的内部。为了确保在 EJB 内部使用这些特性
JmsTemplate
,客户端应用程序应确保它引用ConnectionFactory
.
缓存消息资源
标准 API 涉及创建许多中间对象。要发送消息,请执行以下“API”遍历:
ConnectionFactory->Connection->Session->MessageProducer->send
在ConnectionFactory
和Send
操作之间,会创建和销毁三个中间对象。为了优化资源使用和提高性能,Spring 提供了两种ConnectionFactory
.
使用SingleConnectionFactory
Spring 提供了ConnectionFactory
接口
的实现,它在所有
调用SingleConnectionFactory
中返回相同的值,并忽略对 的调用。这对于测试和独立环境很有用,因此同一连接可用于
可能跨越任意数量事务的多个调用。
引用通常来自 JNDI 的标准。Connection
createConnection()
close()
JmsTemplate
SingleConnectionFactory
ConnectionFactory
使用CachingConnectionFactory
CachingConnectionFactory
扩展了 、 和 实例的功能并添加了缓存SingleConnectionFactory
。初始缓存大小设置为。您可以使用该属性来增加缓存会话的数量。请注意,实际缓存会话的数量大于该数量,因为会话根据其确认模式进行缓存,因此当设置为 1 时,最多可以有四个缓存会话实例(每种确认模式一个)。和Session
MessageProducer
MessageConsumer
1
sessionCacheSize
sessionCacheSize
MessageProducer
MessageConsumer
实例被缓存在它们所属的会话中,并且在缓存时还考虑了生产者和消费者的独特属性。MessageProducer 根据其目的地进行缓存。MessageConsumers 基于由目标、选择器、noLocal 传递标志和持久订阅名称(如果创建持久消费者)组成的键进行缓存。
临时队列和主题(TemporaryQueue/TemporaryTopic)的 MessageProducers 和 MessageConsumers 永远不会被缓存。不幸的是,WebLogic JMS 恰好在其常规目标实现上实现了临时队列/主题接口,错误地表明它的任何目标都不能被缓存。请在 WebLogic 上使用不同的连接池/缓存,或 |
4.1.3。目的地管理
作为实例,目的地ConnectionFactory
是 JMS 管理的对象,您可以在 JNDI 中存储和检索它们。配置 Spring 应用程序上下文时,您可以使用 JNDIJndiObjectFactoryBean
工厂类或<jee:jndi-lookup>
对对象对 JMS 目标的引用执行依赖注入。但是,如果应用程序中有大量目标,或者如果 JMS 提供程序具有独特的高级目标管理功能,则此策略通常很麻烦。这种高级目的地管理的示例包括创建动态目的地或支持目的地的分层命名空间。将JmsTemplate
目标名称的解析委托给实现
DestinationResolver
接口的 JMS 目标对象。DynamicDestinationResolver
是使用的默认实现JmsTemplate
并适应解析动态目的地。还提供了A
JndiDestinationResolver
来充当 JNDI 中包含的目的地的服务定位器,并且可以选择回退到
DynamicDestinationResolver
.
很多时候,JMS 应用程序中使用的目标仅在运行时才知道,因此在部署应用程序时无法以管理方式创建。这通常是因为交互系统组件之间存在共享应用程序逻辑,这些组件在运行时根据众所周知的命名约定创建目标。尽管创建动态目的地不是 JMS 规范的一部分,但大多数供应商都提供了此功能。动态目的地是使用用户定义的名称创建的,这将它们与临时目的地区分开来,并且通常不会在 JNDI 中注册。用于创建动态目的地的 API 因提供商而异,因为与目的地关联的属性是特定于供应商的。然而,TopicSession
createTopic(String topicName)
或QueueSession
createQueue(String
queueName)
使用默认目标属性创建新目标的方法。根据供应商的实施,DynamicDestinationResolver
还可以创建一个物理目标,而不是只解析一个。
布尔属性pubSubDomain
用于配置JmsTemplate
与正在使用的 JMS 域有关的知识。默认情况下,此属性的值为 false,表示要使用点对点域Queues
。此属性(由 使用JmsTemplate
)通过接口的实现确定动态目标解析的行为DestinationResolver
。
您还可以JmsTemplate
通过属性配置默认目的地defaultDestination
。默认目标是不涉及特定目标的发送和接收操作。
4.1.4。消息侦听器容器
在 EJB 世界中,JMS 消息最常见的用途之一是驱动消息驱动的 bean (MDB)。Spring 提供了一种以不将用户绑定到 EJB 容器的方式创建消息驱动的 POJO (MDP) 的解决方案。(有关 Spring 的 MDP 支持的详细介绍,请参阅异步接收:消息驱动的 POJO。)从 Spring Framework 4.1 开始,可以使用注释端点方法@JmsListener
— 有关更多详细信息,请参阅注释驱动的侦听器端点。
消息侦听器容器用于从 JMS 消息队列接收消息并驱动MessageListener
注入其中的消息。侦听器容器负责消息接收的所有线程,并分派到侦听器中进行处理。消息侦听器容器是 MDP 和消息传递提供者之间的中介,负责注册以接收消息、参与事务、资源获取和释放、异常转换等。这使您可以编写与接收消息(并可能对其进行响应)相关的(可能很复杂的)业务逻辑,并将样板 JMS 基础架构问题委托给框架。
Spring 打包了两个标准的 JMS 消息侦听器容器,每个容器都有其专门的功能集。
使用SimpleMessageListenerContainer
此消息侦听器容器是两种标准风格中较简单的一种。它在启动时创建固定数量的 JMS 会话和消费者,使用标准 JMSMessageConsumer.setMessageListener()
方法注册侦听器,并将其留给 JMS 提供者执行侦听器回调。此变体不允许动态适应运行时需求或参与外部管理的事务。兼容性方面,它非常接近独立 JMS 规范的精神,但通常不兼容 Java EE 的 JMS 限制。
虽然SimpleMessageListenerContainer 不允许参与外部管理的事务,但它确实支持本机 JMS 事务。要启用此功能,您可以将sessionTransacted 标志切换为 ,true 或者在 XML 命名空间中,将
acknowledge 属性设置为transacted 。监听器抛出的异常会导致回滚,消息会重新传递。或者,考虑使用
CLIENT_ACKNOWLEDGE 模式,它也提供在异常情况下的重新传递,但不使用事务Session 实例,因此不包括
Session 事务协议中的任何其他操作(例如发送响应消息)。
|
默认AUTO_ACKNOWLEDGE 模式不提供适当的可靠性保证。当侦听器执行失败(因为提供者在侦听器调用后自动确认每条消息,没有异常传播到提供者)或侦听器容器关闭时(您可以通过设置acceptMessagesWhileStopping 标志进行配置),消息可能会丢失。确保在需要可靠性的情况下使用事务会话(例如,用于可靠的队列处理和持久的主题订阅)。
|
使用DefaultMessageListenerContainer
大多数情况下都会使用此消息侦听器容器。与 相比
SimpleMessageListenerContainer
,此容器变体允许动态适应运行时需求,并且能够参与外部管理的事务。当使用
JtaTransactionManager
. 因此,处理可以利用 XA 事务语义。此侦听器容器在对 JMS 提供程序的低要求、高级功能(例如参与外部管理的事务)和与 Java EE 环境的兼容性之间取得了很好的平衡。
您可以自定义容器的缓存级别。请注意,如果未启用缓存,则会为每个消息接收创建一个新连接和一个新会话。将此与高负载的非持久订阅相结合可能会导致消息丢失。在这种情况下,请确保使用适当的缓存级别。
当代理出现故障时,此容器还具有可恢复的功能。默认情况下,一个简单的BackOff
实现每五秒重试一次。BackOff
您可以为更细粒度的恢复选项指定自定义实现。参见
ExponentialBackOff
示例。
与其兄弟 ( SimpleMessageListenerContainer ) 一样,
DefaultMessageListenerContainer 支持本机 JMS 事务并允许自定义确认模式。如果对您的方案可行,强烈建议在外部管理的事务上执行此操作 - 也就是说,如果您可以忍受偶尔出现的重复消息,以防 JVM 死机。业务逻辑中的自定义重复消息检测步骤可以涵盖此类情况——例如,以业务实体存在检查或协议表检查的形式。任何这样的安排都比替代方案更有效:用 XA 事务包装你的整个处理(通过配置你
DefaultMessageListenerContainer 的JtaTransactionManager ) 来涵盖 JMS 消息的接收以及消息侦听器中业务逻辑的执行(包括数据库操作等)。
|
默认AUTO_ACKNOWLEDGE 模式不提供适当的可靠性保证。当侦听器执行失败(因为提供者在侦听器调用后自动确认每条消息,没有异常传播到提供者)或侦听器容器关闭时(您可以通过设置acceptMessagesWhileStopping 标志进行配置),消息可能会丢失。确保在需要可靠性的情况下使用事务会话(例如,用于可靠的队列处理和持久的主题订阅)。
|
4.1.5。事务管理
Spring 提供了一个JmsTransactionManager
管理单个 JMS 事务的方法
ConnectionFactory
。这让 JMS 应用程序可以利用 Spring 的托管事务特性,如
数据访问一章的事务管理部分所述。执行本地资源事务,将JmsTransactionManager
指定的 JMS 连接/会话对绑定ConnectionFactory
到线程。
JmsTemplate
自动检测此类事务资源并相应地对其进行操作。
在 Java EE 环境中,ConnectionFactory
池连接和会话实例,因此这些资源可以跨事务有效地重用。在独立环境中,SingleConnectionFactory
在共享 JMS 中使用 Spring 的结果Connection
,每个事务都有自己独立的Session
. 或者,考虑使用特定于提供者的池适配器,例如 ActiveMQ 的PooledConnectionFactory
类。
您还可以使用JmsTemplate
支持JtaTransactionManager
XA 的 JMS
ConnectionFactory
来执行分布式事务。请注意,这需要使用 JTA 事务管理器以及正确配置 XA 的 ConnectionFactory。(检查您的 Java EE 服务器或 JMS 提供程序的文档。)
使用 JMS APISession
从Connection
. 这是因为 JMS API 只有一个工厂方法来创建Session
,并且它需要事务和确认模式的值。在托管环境中,设置这些值是环境事务基础架构的责任,因此供应商的 JMS 连接包装器会忽略这些值。在非托管环境中使用 时JmsTemplate
,可以通过使用属性sessionTransacted
和来指定这些值sessionAcknowledgeMode
。当您使用
PlatformTransactionManager
withJmsTemplate
时,模板总是被赋予一个事务性 JMS Session
。
4.2. 发送消息
包含许多方便的JmsTemplate
方法来发送消息。发送方法通过使用javax.jms.Destination
对象来指定目的地,而其他方法通过String
在 JNDI 查找中使用 a 来指定目的地。不带目的地参数的send
方法使用默认目的地。
以下示例使用MessageCreator
回调从提供的Session
对象创建文本消息:
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.Session;
import org.springframework.jms.core.MessageCreator;
import org.springframework.jms.core.JmsTemplate;
public class JmsQueueSender {
private JmsTemplate jmsTemplate;
private Queue queue;
public void setConnectionFactory(ConnectionFactory cf) {
this.jmsTemplate = new JmsTemplate(cf);
}
public void setQueue(Queue queue) {
this.queue = queue;
}
public void simpleSend() {
this.jmsTemplate.send(this.queue, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage("hello queue world");
}
});
}
}
在前面的示例中,JmsTemplate
通过传递对 a 的引用来构造
ConnectionFactory
。作为替代方案,提供了一个零参数构造函数,
connectionFactory
并可用于以 JavaBean 样式构造实例(使用BeanFactory
Java 代码或纯 Java 代码)。或者,考虑从 Spring 的便利基类派生JmsGatewaySupport
,它为 JMS 配置提供预构建的 bean 属性。
该send(String destinationName, MessageCreator creator)
方法允许您使用目标的字符串名称发送消息。如果这些名称已在 JNDI 中注册,则应将destinationResolver
模板的属性设置为
JndiDestinationResolver
.
如果您创建JmsTemplate
并指定了默认目标,
则会向该目标send(MessageCreator c)
发送消息。
4.2.1。使用消息转换器
为了方便域模型对象的发送,JmsTemplate
有多种发送方法,它们将 Java 对象作为消息数据内容的参数。将转换过程中的重载方法convertAndSend()
和receiveAndConvert()
方法
JmsTemplate
委托给MessageConverter
接口的实例。该接口定义了一个简单的协定,用于在 Java 对象和 JMS 消息之间进行转换。默认实现 ( SimpleMessageConverter
) 支持String
and TextMessage
、byte[]
andBytesMessage
和java.util.Map
and之间的转换MapMessage
。通过使用转换器,您和您的应用程序代码可以专注于通过 JMS 发送或接收的业务对象,而不必关心它如何表示为 JMS 消息的细节。
沙箱当前包括一个MapMessageConverter
,它使用反射在 JavaBean 和一个MapMessage
. 您可能自己实现的其他流行实现选择是使用现有 XML 编组包(例如 JAXB 或 XStream)创建TextMessage
表示对象的转换器的转换器。
为了适应无法在转换器类中一般封装的消息属性、标头和正文的设置,该MessagePostProcessor
接口允许您在消息转换之后但在发送之前访问消息。下面的例子展示了在 a
java.util.Map
转换为消息后如何修改消息头和属性:
public void sendWithConversion() {
Map map = new HashMap();
map.put("Name", "Mark");
map.put("Age", new Integer(47));
jmsTemplate.convertAndSend("testQueue", map, new MessagePostProcessor() {
public Message postProcessMessage(Message message) throws JMSException {
message.setIntProperty("AccountID", 1234);
message.setJMSCorrelationID("123-00001");
return message;
}
});
}
这会产生以下形式的消息:
地图消息={ 标头={ ... 标准标题 ... 相关 ID={123-00001} } 属性={ AccountID={整数:1234} } 字段={ 名称={字符串:标记} 年龄={整数:47} } }
4.3. 接收消息
这描述了如何在 Spring 中使用 JMS 接收消息。
4.3.1。同步接收
虽然 JMS 通常与异步处理相关联,但您可以同步使用消息。重载的receive(..)
方法提供了这个功能。在同步接收期间,调用线程会阻塞,直到有消息可用。这可能是一个危险的操作,因为调用线程可能会被无限期地阻塞。该receiveTimeout
属性指定接收者在放弃等待消息之前应该等待多长时间。
4.3.2. 异步接收:消息驱动的 POJO
Spring 还通过使用注释来支持带注释的侦听器端点,@JmsListener
并提供一个开放的基础设施来以编程方式注册端点。到目前为止,这是设置异步接收器最方便的方法。有关更多详细信息,请参阅启用侦听器端点注释。
|
以类似于 EJB 世界中的消息驱动 Bean (MDB) 的方式,消息驱动 POJO (MDP) 充当 JMS 消息的接收者。MDP的一个限制(但请参见
UsingMessageListenerAdapter
)是它必须实现javax.jms.MessageListener
接口。请注意,如果您的 POJO 在多个线程上接收消息,请务必确保您的实现是线程安全的。
以下示例显示了 MDP 的简单实现:
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
public class ExampleListener implements MessageListener {
public void onMessage(Message message) {
if (message instanceof TextMessage) {
try {
System.out.println(((TextMessage) message).getText());
}
catch (JMSException ex) {
throw new RuntimeException(ex);
}
}
else {
throw new IllegalArgumentException("Message must be of type TextMessage");
}
}
}
一旦你实现了你的MessageListener
,是时候创建一个消息监听器容器了。
以下示例显示了如何定义和配置 Spring 附带的消息侦听器容器之一(在本例中为DefaultMessageListenerContainer
):
<!-- this is the Message Driven POJO (MDP) -->
<bean id="messageListener" class="jmsexample.ExampleListener"/>
<!-- and this is the message listener container -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="messageListener"/>
</bean>
有关每个实现支持的功能的完整描述,请参阅各种消息侦听器容器(所有这些都实现 MessageListenerContainer )的 Spring javadoc。
4.3.3. 使用SessionAwareMessageListener
界面
该SessionAwareMessageListener
接口是一个特定于 Spring 的接口,它提供了与 JMS 接口类似的契约,MessageListener
但也为消息处理方法提供了对接收消息的 JMS 的访问Session
权限Message
。以下清单显示了SessionAwareMessageListener
接口的定义:
package org.springframework.jms.listener;
public interface SessionAwareMessageListener {
void onMessage(Message message, Session session) throws JMSException;
}
MessageListener
如果您希望您的 MDP 能够响应任何接收到的消息(通过使用方法中Session
提供的) ,您可以选择让您的 MDP 实现此接口(优先于标准 JMS接口onMessage(Message, Session)
)。Spring 附带的所有消息侦听器容器实现都支持实现MessageListener
or
SessionAwareMessageListener
接口的 MDP。实现的类
SessionAwareMessageListener
附带一个警告,它们然后通过接口绑定到 Spring。是否使用它完全取决于您作为应用程序开发人员或架构师的选择。
注意接口的onMessage(..)
方法SessionAwareMessageListener
throws JMSException
。与标准 JMSMessageListener
接口相比,在使用SessionAwareMessageListener
接口时,客户端代码负责处理任何抛出的异常。
4.3.4. 使用MessageListenerAdapter
该类MessageListenerAdapter
是 Spring 的异步消息支持中的最后一个组件。简而言之,它允许您将几乎任何类公开为 MDP(尽管有一些限制)。
考虑以下接口定义:
public interface MessageDelegate {
void handleMessage(String message);
void handleMessage(Map message);
void handleMessage(byte[] message);
void handleMessage(Serializable message);
}
请注意,尽管接口既不扩展也不扩展MessageListener
接口
SessionAwareMessageListener
,但您仍然可以通过使用
MessageListenerAdapter
类将其用作 MDP。还要注意各种消息处理方法如何根据Message
它们可以接收和处理的各种类型的内容进行强类型化。
现在考虑接口的以下实现MessageDelegate
:
public class DefaultMessageDelegate implements MessageDelegate {
// implementation elided for clarity...
}
特别要注意MessageDelegate
接口(
DefaultMessageDelegate
类)的前面实现是如何完全没有 JMS 依赖关系的。它确实是一个 POJO,我们可以通过以下配置将其制成 MDP:
<!-- this is the Message Driven POJO (MDP) -->
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<constructor-arg>
<bean class="jmsexample.DefaultMessageDelegate"/>
</constructor-arg>
</bean>
<!-- and this is the message listener container... -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="messageListener"/>
</bean>
下一个示例显示了另一个 MDP,它只能处理接收 JMS
TextMessage
消息。注意消息处理方法的实际调用方式
receive
(消息处理方法的名称MessageListenerAdapter
默认为handleMessage
),但它是可配置的(如您在本节后面看到的那样)。还要注意该receive(..)
方法是如何被强类型化为只接收和响应 JMS
TextMessage
消息的。以下清单显示了TextMessageDelegate
接口的定义:
public interface TextMessageDelegate {
void receive(TextMessage message);
}
以下清单显示了一个实现该TextMessageDelegate
接口的类:
public class DefaultTextMessageDelegate implements TextMessageDelegate {
// implementation elided for clarity...
}
话务员的配置MessageListenerAdapter
如下:
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<constructor-arg>
<bean class="jmsexample.DefaultTextMessageDelegate"/>
</constructor-arg>
<property name="defaultListenerMethod" value="receive"/>
<!-- we don't want automatic message context extraction -->
<property name="messageConverter">
<null/>
</property>
</bean>
请注意,如果messageListener
接收到的 JMSMessage
类型不是TextMessage
,IllegalStateException
则抛出 an (并随后吞下)。该类的另一个功能是,如果处理程序方法返回非 void 值,MessageListenerAdapter
则能够自动发回响应。Message
考虑以下接口和类:
public interface ResponsiveTextMessageDelegate {
// notice the return type...
String receive(TextMessage message);
}
public class DefaultResponsiveTextMessageDelegate implements ResponsiveTextMessageDelegate {
// implementation elided for clarity...
}
如果将DefaultResponsiveTextMessageDelegate
与 a 结合使用
MessageListenerAdapter
,则从方法执行返回的任何非空值'receive(..)'
(在默认配置中)都将转换为 a
TextMessage
。然后将结果TextMessage
发送到Destination
(如果存在)在Reply-To
原始的 JMS 属性中定义的Message
或默认Destination
设置的MessageListenerAdapter
(如果已配置)。如果没有Destination
找到,InvalidDestinationException
则抛出 an(请注意,此异常不会被吞没并向上传播调用堆栈)。
4.3.5。处理事务中的消息
在事务中调用消息侦听器只需要重新配置侦听器容器。
sessionTransacted
您可以通过侦听器容器定义上的标志激活本地资源事务。然后,每个消息侦听器调用在活动的 JMS 事务中运行,在侦听器执行失败的情况下回滚消息接收。发送响应消息(通过SessionAwareMessageListener
)是同一本地事务的一部分,但任何其他资源操作(例如数据库访问)独立操作。这通常需要在侦听器实现中检测重复消息,以涵盖数据库处理已提交但消息处理未能提交的情况。
考虑以下 bean 定义:
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="messageListener"/>
<property name="sessionTransacted" value="true"/>
</bean>
要参与外部管理的事务,您需要配置事务管理器并使用支持外部管理事务的侦听器容器(通常为DefaultMessageListenerContainer
)。
要为 XA 事务参与配置消息侦听器容器,您需要配置一个JtaTransactionManager
(默认情况下,它委托给 Java EE 服务器的事务子系统)。请注意,底层 JMSConnectionFactory
需要支持 XA 并在您的 JTA 事务协调器中正确注册。(检查您的 Java EE 服务器的 JNDI 资源配置。)这使得消息接收以及(例如)数据库访问成为同一事务的一部分(具有统一的提交语义,以 XA 事务日志开销为代价)。
以下 bean 定义创建了一个事务管理器:
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
然后我们需要将它添加到我们之前的容器配置中。容器负责其余的工作。以下示例显示了如何执行此操作:
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="messageListener"/>
<property name="transactionManager" ref="transactionManager"/> (1)
</bean>
1 | 我们的交易经理。 |
4.4. 支持 JCA 消息端点
从 2.5 版开始,Spring 还提供对基于 JCA 的
MessageListener
容器的支持。JmsMessageEndpointManager
尝试根据提供者的类名自动确定类ActivationSpec
名
ResourceAdapter
。因此,通常可以提供 Spring 的 generic JmsActivationSpecConfig
,如以下示例所示:
<bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
<property name="resourceAdapter" ref="resourceAdapter"/>
<property name="activationSpecConfig">
<bean class="org.springframework.jms.listener.endpoint.JmsActivationSpecConfig">
<property name="destinationName" value="myQueue"/>
</bean>
</property>
<property name="messageListener" ref="myMessageListener"/>
</bean>
JmsMessageEndpointManager
或者,您可以使用给定的
ActivationSpec
对象设置 a 。该ActivationSpec
对象也可能来自 JNDI 查找(使用<jee:jndi-lookup>
)。以下示例显示了如何执行此操作:
<bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
<property name="resourceAdapter" ref="resourceAdapter"/>
<property name="activationSpec">
<bean class="org.apache.activemq.ra.ActiveMQActivationSpec">
<property name="destination" value="myQueue"/>
<property name="destinationType" value="javax.jms.Queue"/>
</bean>
</property>
<property name="messageListener" ref="myMessageListener"/>
</bean>
使用 Spring 的ResourceAdapterFactoryBean
,您可以在本地配置目标ResourceAdapter
,如以下示例所示:
<bean id="resourceAdapter" class="org.springframework.jca.support.ResourceAdapterFactoryBean">
<property name="resourceAdapter">
<bean class="org.apache.activemq.ra.ActiveMQResourceAdapter">
<property name="serverUrl" value="tcp://localhost:61616"/>
</bean>
</property>
<property name="workManager">
<bean class="org.springframework.jca.work.SimpleTaskWorkManager"/>
</property>
</bean>
指定的WorkManager
也可以指向特定于环境的线程池——通常通过SimpleTaskWorkManager
实例的asyncTaskExecutor
属性。ResourceAdapter
如果碰巧使用多个适配器,请考虑为所有实例定义一个共享线程池。
在某些环境中(例如 WebLogic 9 或更高版本),您可以改为ResourceAdapter
从 JNDI 获取整个对象(通过使用<jee:jndi-lookup>
)。然后,基于 Spring 的消息侦听器可以与 server-hosted 交互ResourceAdapter
,后者也使用服务器的内置WorkManager
.
有关更多详细信息,请参阅 、 和的JmsMessageEndpointManager
javadoc
JmsActivationSpecConfig
。ResourceAdapterFactoryBean
Spring 还提供了一个与 JMS 无关的通用 JCA 消息端点管理器:
org.springframework.jca.endpoint.GenericMessageEndpointManager
. 该组件允许使用任何消息侦听器类型(例如 JMS MessageListener
)和任何特定于提供程序的ActivationSpec
对象。请参阅您的 JCA 提供者的文档以了解您的连接器的实际功能,并参阅
GenericMessageEndpointManager
javadoc 以了解特定于 Spring 的配置详细信息。
基于 JCA 的消息端点管理非常类似于 EJB 2.1 消息驱动 Bean。它使用相同的底层资源提供者合约。与 EJB 2.1 MDB 一样,您也可以在 Spring 上下文中使用 JCA 提供者支持的任何消息侦听器接口。尽管如此,Spring 还是为 JMS 提供了明确的“便利”支持,因为 JMS 是与 JCA 端点管理契约一起使用的最常见的端点 API。 |
4.5. 注释驱动的侦听器端点
异步接收消息的最简单方法是使用带注释的侦听器端点基础结构。简而言之,它允许您将托管 bean 的方法公开为 JMS 侦听器端点。以下示例显示了如何使用它:
@Component
public class MyService {
@JmsListener(destination = "myDestination")
public void processOrder(String data) { ... }
}
前面示例的想法是,只要消息在 上可用,就会
相应地调用javax.jms.Destination
myDestination
该processOrder
方法(在这种情况下,使用 JMS 消息的内容,类似于MessageListenerAdapter
提供的内容)。
带注释的端点基础设施通过使用JmsListenerContainerFactory
. 这样的容器没有针对应用程序上下文注册,但可以通过使用JmsListenerEndpointRegistry
bean 轻松定位以进行管理。
@JmsListener @JmsListener
是 Java 8 上的可重复注解,因此您可以通过向其添加额外声明将
多个 JMS 目标与同一方法相关联。 |
4.5.1。启用侦听器端点注释
要启用对@JmsListener
注释的支持,您可以添加@EnableJms
到您的@Configuration
类之一,如以下示例所示:
@Configuration
@EnableJms
public class AppConfig {
@Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setDestinationResolver(destinationResolver());
factory.setSessionTransacted(true);
factory.setConcurrency("3-10");
return factory;
}
}
默认情况下,基础设施会查找一个名为jmsListenerContainerFactory
源的 bean,以供工厂用来创建消息侦听器容器。processOrder
在这种情况下(并忽略 JMS 基础结构设置),您可以使用三个线程的核心轮询大小和最大池大小为十个线程来调用该方法。
您可以自定义侦听器容器工厂以用于每个注释,也可以通过实现JmsListenerConfigurer
接口来配置显式默认值。只有在没有特定容器工厂的情况下注册了至少一个端点时才需要默认值。JmsListenerConfigurer
有关详细信息和示例,请参阅实现的类的 javadoc
。
如果您更喜欢XML 配置,则可以使用该<jms:annotation-driven>
元素,如以下示例所示:
<jms:annotation-driven/>
<bean id="jmsListenerContainerFactory"
class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destinationResolver" ref="destinationResolver"/>
<property name="sessionTransacted" value="true"/>
<property name="concurrency" value="3-10"/>
</bean>
4.5.2. 程序化端点注册
JmsListenerEndpoint
提供 JMS 端点的模型并负责为该模型配置容器。JmsListener
除了注释检测到的端点之外,该基础架构还允许您以编程方式配置端点。以下示例显示了如何执行此操作:
@Configuration
@EnableJms
public class AppConfig implements JmsListenerConfigurer {
@Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId("myJmsEndpoint");
endpoint.setDestination("anotherQueue");
endpoint.setMessageListener(message -> {
// processing
});
registrar.registerEndpoint(endpoint);
}
}
在前面的示例中,我们使用SimpleJmsListenerEndpoint
了 ,它提供了
MessageListener
要调用的实际值。但是,您也可以构建自己的端点变体来描述自定义调用机制。
请注意,您可以@JmsListener
完全跳过使用并以编程方式仅通过JmsListenerConfigurer
.
4.5.3. 带注释的端点方法签名
到目前为止,我们一直String
在端点中注入一个简单的,但它实际上可以有一个非常灵活的方法签名。在以下示例中,我们重写它以Order
使用自定义标头注入:
@Component
public class MyService {
@JmsListener(destination = "myDestination")
public void processOrder(Order order, @Header("order_type") String orderType) {
...
}
}
您可以在 JMS 侦听器端点中注入的主要元素如下:
-
raw
javax.jms.Message
或其任何子类(前提是它与传入的消息类型匹配)。 -
用于对本机 JMS API的
javax.jms.Session
可选访问(例如,用于发送自定义回复)。 -
org.springframework.messaging.Message
表示传入的 JMS 消息。请注意,此消息包含自定义和标准标头(由 定义JmsHeaders
)。 -
@Header
-带注释的方法参数以提取特定的标头值,包括标准 JMS 标头。 -
一个
@Headers
-annotated 参数,也必须可分配给以java.util.Map
访问所有标题。 -
Message
不是受支持类型(或 )之一的未注释元素Session
被视为有效负载。您可以通过使用 注释参数来明确这一点@Payload
。您还可以通过添加额外的@Valid
.
注入 SpringMessage
抽象的能力对于受益于存储在特定于传输的消息中的所有信息而特别有用,而不依赖于特定于传输的 API。以下示例显示了如何执行此操作:
@JmsListener(destination = "myDestination")
public void processOrder(Message<Order> order) { ... }
方法参数的处理由 提供DefaultMessageHandlerMethodFactory
,您可以进一步自定义以支持其他方法参数。您也可以在那里自定义转换和验证支持。
例如,如果我们想Order
在处理它之前确保它是有效的,我们可以使用注释@Valid
并配置必要的验证器,如以下示例所示:
@Configuration
@EnableJms
public class AppConfig implements JmsListenerConfigurer {
@Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(myJmsHandlerMethodFactory());
}
@Bean
public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setValidator(myValidator());
return factory;
}
}
4.5.4. 响应管理
现有的支持MessageListenerAdapter
已经让您的方法具有非void
返回类型。在这种情况下,调用的结果被封装在 a 中javax.jms.Message
,发送JMSReplyTo
到原始消息标头中指定的目标或侦听器上配置的默认目标。您现在可以使用@SendTo
消息传递抽象的注释来设置该默认目标。
假设我们的processOrder
方法现在应该返回一个OrderStatus
,我们可以编写它来自动发送响应,如以下示例所示:
@JmsListener(destination = "myDestination")
@SendTo("status")
public OrderStatus processOrder(Order order) {
// order processing
return status;
}
如果您有多个@JmsListener -annotated 方法,您还可以将@SendTo
注释放在类级别以共享默认回复目标。
|
如果您需要以与传输无关的方式设置其他标头,则可以返回 a
Message
,方法类似于以下:
@JmsListener(destination = "myDestination")
@SendTo("status")
public Message<OrderStatus> processOrder(Order order) {
// order processing
return MessageBuilder
.withPayload(status)
.setHeader("code", 1234)
.build();
}
如果您需要在运行时计算响应目标,您可以将响应封装在一个JmsResponse
实例中,该实例还提供在运行时使用的目标。我们可以将前面的例子改写如下:
@JmsListener(destination = "myDestination")
public JmsResponse<Message<OrderStatus>> processOrder(Order order) {
// order processing
Message<OrderStatus> response = MessageBuilder
.withPayload(status)
.setHeader("code", 1234)
.build();
return JmsResponse.forQueue(response, "status");
}
最后,如果您需要为响应指定一些 QoS 值,例如优先级或生存时间,您可以进行JmsListenerContainerFactory
相应的配置,如以下示例所示:
@Configuration
@EnableJms
public class AppConfig {
@Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
QosSettings replyQosSettings = new QosSettings();
replyQosSettings.setPriority(2);
replyQosSettings.setTimeToLive(10000);
factory.setReplyQosSettings(replyQosSettings);
return factory;
}
}
4.6. JMS 命名空间支持
Spring 提供了一个 XML 命名空间来简化 JMS 配置。要使用 JMS 命名空间元素,您需要引用 JMS 模式,如以下示例所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms" (1)
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms https://www.springframework.org/schema/jms/spring-jms.xsd">
<!-- bean definitions here -->
</beans>
1 | 引用 JMS 模式。 |
命名空间由三个顶级元素组成<annotation-driven/>
:<listener-container/>
和<jca-listener-container/>
。<annotation-driven/>
启用注释驱动的侦听器端点。<listener-container/>
并<jca-listener-container/>
定义共享侦听器容器配置,并且可以包含<listener/>
子元素。以下示例显示了两个侦听器的基本配置:
<jms:listener-container>
<jms:listener destination="queue.orders" ref="orderService" method="placeOrder"/>
<jms:listener destination="queue.confirmations" ref="confirmationLogger" method="log"/>
</jms:listener-container>
前面的示例等效于创建两个不同的侦听器容器 bean 定义和两个不同的MessageListenerAdapter
bean 定义,如UsingMessageListenerAdapter
中所示。除了前面示例中显示的属性之外,该listener
元素还可以包含几个可选的属性。下表描述了所有可用的属性:
属性 | 描述 |
---|---|
|
托管侦听器容器的 bean 名称。如果未指定,则会自动生成一个 bean 名称。 |
|
此侦听器的目标名称,通过 |
|
处理程序对象的 bean 名称。 |
|
要调用的处理程序方法的名称。如果该 |
|
要将响应消息发送到的默认响应目标的名称。这适用于不携带 |
|
持久订阅的名称(如果有)。 |
|
此侦听器的可选消息选择器。 |
|
为此侦听器启动的并发会话数或使用者数。该值可以是表示最大数的简单数字(例如 |
该<listener-container/>
元素还接受几个可选属性。这允许自定义各种策略(例如taskExecutor
和
destinationResolver
)以及基本的 JMS 设置和资源引用。通过使用这些属性,您可以定义高度自定义的侦听器容器,同时仍然受益于命名空间的便利。
JmsListenerContainerFactory
您可以通过指定id
要通过属性公开的 bean 来自动公开此类设置factory-id
,如以下示例所示:
<jms:listener-container connection-factory="myConnectionFactory"
task-executor="myTaskExecutor"
destination-resolver="myDestinationResolver"
transaction-manager="myTransactionManager"
concurrency="10">
<jms:listener destination="queue.orders" ref="orderService" method="placeOrder"/>
<jms:listener destination="queue.confirmations" ref="confirmationLogger" method="log"/>
</jms:listener-container>
下表描述了所有可用的属性。AbstractMessageListenerContainer
有关各个属性的更多详细信息,请参阅 及其具体子类的类级 javadoc 。javadoc 还讨论了事务选择和消息重新传递方案。
属性 | 描述 |
---|---|
|
此侦听器容器的类型。可用选项为 |
|
自定义侦听器容器实现类作为完全限定的类名。根据属性,默认是 Spring 的标准 |
|
将此元素定义的设置公开为 |
|
对 JMS |
|
|
|
|
|
|
|
|
|
此侦听器的 JMS 目标类型: |
|
响应的 JMS 目标类型: |
|
此侦听器容器的 JMS 客户端 ID。使用持久订阅时必须指定它。 |
|
JMS 资源的缓存级别: |
|
本机 JMS 确认模式: |
|
对外部的引用 |
|
为每个侦听器启动的并发会话数或消费者数。它可以是表示最大数的简单数字(例如 |
|
加载到单个会话中的最大消息数。请注意,提高此数字可能会导致并发消费者饥饿。 |
|
用于接收呼叫的超时(以毫秒为单位)。默认值为 |
|
指定 |
|
指定恢复尝试之间的时间间隔,以毫秒为单位。它提供了一种方便的方法来创建 |
|
此容器应启动和停止的生命周期阶段。该值越低,该容器启动得越早,停止得越晚。默认值为
|
使用模式支持配置基于 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 变体的可用配置选项:
属性 | 描述 |
---|---|
|
将此元素定义的设置公开为 |
|
对 JCA |
|
对 |
|
|
|
|
|
此侦听器的 JMS 目标类型: |
|
响应的 JMS 目标类型: |
|
此侦听器容器的 JMS 客户端 ID。使用持久订阅时需要指定。 |
|
本机 JMS 确认模式: |
|
对 Spring 的引用 |
|
为每个侦听器启动的并发会话数或消费者数。它可以是表示最大数的简单数字(例如 |
|
加载到单个会话中的最大消息数。请注意,提高此数字可能会导致并发消费者饥饿。 |
5.JMX
Spring 中的 JMX(Java 管理扩展)支持提供了一些功能,可让您轻松、透明地将 Spring 应用程序集成到 JMX 基础架构中。
具体来说,Spring 的 JMX 支持提供了四个核心特性:
-
将任何 Spring bean 自动注册为 JMX MBean。
-
一种用于控制 bean 管理接口的灵活机制。
-
MBean 在远程 JSR-160 连接器上的声明性公开。
-
本地和远程 MBean 资源的简单代理。
这些功能旨在无需将应用程序组件耦合到 Spring 或 JMX 接口和类即可工作。实际上,在大多数情况下,您的应用程序类无需了解 Spring 或 JMX 即可利用 Spring JMX 特性。
5.1。将 Bean 导出到 JMX
Spring 的 JMX 框架中的核心类是MBeanExporter
. 此类负责获取您的 Spring bean 并将它们注册到 JMX MBeanServer
。例如,考虑以下类:
package org.springframework.jmx;
public class JmxTestBean implements IJmxTestBean {
private String name;
private int age;
private boolean isSuperman;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int add(int x, int y) {
return x + y;
}
public void dontExposeMe() {
throw new RuntimeException();
}
}
要将此 bean 的属性和方法公开为 MBean 的属性和操作,您可以MBeanExporter
在配置文件中配置该类的实例并传入 bean,如以下示例所示:
<beans>
<!-- this bean must not be lazily initialized if the exporting is to happen -->
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
前面的配置片段中相关的 bean 定义是exporter
bean。该beans
属性MBeanExporter
准确地告诉您哪些 bean 必须导出到 JMX MBeanServer
。在默认配置中,每个条目的 keybeans
Map
被用作ObjectName
对应条目值所引用的 bean。您可以更改此行为,如Controlling ObjectName
Instances for Your Beans中所述。
使用此配置,testBean
bean 在
ObjectName
bean:name=testBean1
. 默认情况下,public
bean 的所有属性都作为属性公开,所有public
方法(从类继承的方法除外
Object
)都作为操作公开。
MBeanExporter 是一个Lifecycle bean(请参阅启动和关闭回调)。默认情况下,在应用程序生命周期中尽可能晚地导出 MBean。您可以通过设置标志
来配置phase 导出发生的时间或禁用自动注册。autoStartup |
5.1.1。创建 MBeanServer
上一节中显示的配置假定应用程序正在运行的环境中运行一个(并且只有一个)MBeanServer
已经运行。在这种情况下,Spring 会尝试定位正在运行MBeanServer
的 bean 并将您的 bean 注册到该服务器(如果有)。当您的应用程序在具有自己的MBeanServer
.
但是,这种方法在独立环境中或在不提供MBeanServer
. 为了解决这个问题,您可以
通过将类的实例添加到您的配置来以MBeanServer
声明方式创建一个实例
。您还可以通过将
实例属性的值设置为 an 返回
的值org.springframework.jmx.support.MBeanServerFactoryBean
来确保使用特定的,如以下示例所示:MBeanServer
MBeanExporter
server
MBeanServer
MBeanServerFactoryBean
<beans>
<bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean"/>
<!--
this bean needs to be eagerly pre-instantiated in order for the exporting to occur;
this means that it must not be marked as lazily initialized
-->
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
<property name="server" ref="mbeanServer"/>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
在前面的示例中, 的实例由MBeanServer
创造,并通过属性MBeanServerFactoryBean
提供给。当您提供自己的
实例时,不会尝试查找正在运行
的实例并使用提供的实例。要使其正常工作,您的类路径上必须有 JMX 实现。MBeanExporter
server
MBeanServer
MBeanExporter
MBeanServer
MBeanServer
5.1.2. 重用现有的MBeanServer
如果未指定服务器,则MBeanExporter
尝试自动检测正在运行的
MBeanServer
. 这适用于仅MBeanServer
使用一个实例的大多数环境。但是,当存在多个实例时,导出器可能会选择错误的服务器。在这种情况下,您应该使用MBeanServer
agentId
来指示要使用的实例,如以下示例所示:
<beans>
<bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
<!-- indicate to first look for a server -->
<property name="locateExistingServerIfPossible" value="true"/>
<!-- search for the MBeanServer instance with the given agentId -->
<property name="agentId" value="MBeanServer_instance_agentId>"/>
</bean>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="server" ref="mbeanServer"/>
...
</bean>
</beans>
对于现有平台或情况,其中现有MBeanServer
具有
agentId
通过查找方法检索的动态(或未知),您应该使用
factory-method,如以下示例所示:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="server">
<!-- Custom MBeanServerLocator -->
<bean class="platform.package.MBeanServerLocator" factory-method="locateMBeanServer"/>
</property>
</bean>
<!-- other beans here -->
</beans>
5.1.3. 延迟初始化的 MBean
如果您使用MBeanExporter
也配置为延迟初始化的 bean 配置 bean,MBeanExporter
则不会破坏此协定并避免实例化 bean。相反,它向 注册一个代理,MBeanServer
并推迟从容器中获取 bean,直到对代理的第一次调用发生。
5.1.4。MBean 的自动注册
通过 导出并且已经是有效的 MBean 的任何 bean 都MBeanExporter
将按原样注册,MBeanServer
而无需 Spring 的进一步干预。MBeanExporter
您可以通过将autodetect
属性设置为 来自动检测 MBean true
,如以下示例所示:
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="autodetect" value="true"/>
</bean>
<bean name="spring:mbean=true" class="org.springframework.jmx.export.TestDynamicMBean"/>
在前面的示例中,调用的 beanspring:mbean=true
已经是一个有效的 JMX MBean,并且由 Spring 自动注册。默认情况下,自动检测到 JMX 注册的 bean 将其 bean 名称用作ObjectName
. 您可以覆盖此行为,如Controlling ObjectName
Instances for Your Beans中所述。
5.1.5。控制注册行为
考虑SpringMBeanExporter
尝试MBean
使用MBeanServer
. ObjectName
bean:name=testBean1
如果一个MBean
实例已经在相同的 下注册ObjectName
,则默认行为是失败(并抛出一个InstanceAlreadyExistsException
)。
MBean
您可以准确控制当 an注册到 an时会发生什么MBeanServer
。Spring 的 JMX 支持允许三种不同的注册行为来控制当注册过程发现一个MBean
已经在同一个ObjectName
. 下表总结了这些注册行为:
注册行为 | 解释 |
---|---|
|
这是默认注册行为。如果一个 |
|
如果一个 |
|
如果一个 |
上表中的值被定义为RegistrationPolicy
类上的枚举。如果要更改默认注册行为,则需要将定义中的
registrationPolicy
属性值设置MBeanExporter
为这些值之一。
以下示例显示了如何从默认注册行为更改为该REPLACE_EXISTING
行为:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
<property name="registrationPolicy" value="REPLACE_EXISTING"/>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
5.2. 控制 Bean 的管理界面
在上一节的示例中,您几乎无法控制 bean 的管理界面。每个导出的 bean 的所有public
属性和方法分别公开为 JMX 属性和操作。为了对导出的 bean 的哪些属性和方法实际公开为 JMX 属性和操作进行更细粒度的控制,Spring JMX 提供了一种全面且可扩展的机制来控制 bean 的管理接口。
5.2.1。使用MBeanInfoAssembler
界面
在幕后,MBeanExporter
代表
org.springframework.jmx.export.assembler.MBeanInfoAssembler
接口的实现,它负责定义每个暴露的 bean 的管理接口。默认实现
org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler
定义了一个管理接口,该接口公开所有公共属性和方法(如您在前面部分的示例中所见)。Spring 提供了两个额外的MBeanInfoAssembler
接口实现,让您可以使用源级元数据或任意接口控制生成的管理接口。
5.2.2. 使用源级元数据:Java 注释
通过使用MetadataMBeanInfoAssembler
,您可以使用源级元数据为您的 bean 定义管理接口。元数据的读取由org.springframework.jmx.export.metadata.JmxAttributeSource
接口封装。Spring JMX 提供了一个使用 Java 注解的默认实现,即
org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource
. 您必须MetadataMBeanInfoAssembler
使用接口的实现实例JmxAttributeSource
配置它才能正常运行(没有默认值)。
要将 bean 标记为导出到 JMX,您应该使用注释对 bean 类进行
ManagedResource
注释。您必须使用注解将您希望公开的每个方法标记为操作,并使用ManagedOperation
注解标记您希望公开的每个属性ManagedAttribute
。标记属性时,您可以省略 getter 或 setter 的注释以分别创建只写或只读属性。
带注释的ManagedResource bean 必须是公共的,公开操作或属性的方法也必须是公共的。
|
以下示例显示了JmxTestBean
我们在创建 MBeanServer中使用的类的注释版本:
package org.springframework.jmx;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedAttribute;
@ManagedResource(
objectName="bean:name=testBean4",
description="My Managed Bean",
log=true,
logFile="jmx.log",
currencyTimeLimit=15,
persistPolicy="OnUpdate",
persistPeriod=200,
persistLocation="foo",
persistName="bar")
public class AnnotationTestBean implements IJmxTestBean {
private String name;
private int age;
@ManagedAttribute(description="The Age Attribute", currencyTimeLimit=15)
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@ManagedAttribute(description="The Name Attribute",
currencyTimeLimit=20,
defaultValue="bar",
persistPolicy="OnUpdate")
public void setName(String name) {
this.name = name;
}
@ManagedAttribute(defaultValue="foo", persistPeriod=300)
public String getName() {
return name;
}
@ManagedOperation(description="Add two numbers")
@ManagedOperationParameters({
@ManagedOperationParameter(name = "x", description = "The first number"),
@ManagedOperationParameter(name = "y", description = "The second number")})
public int add(int x, int y) {
return x + y;
}
public void dontExposeMe() {
throw new RuntimeException();
}
}
在前面的示例中,您可以看到JmxTestBean
该类被标记了
ManagedResource
注解,并且该ManagedResource
注解配置了一组属性。这些属性可用于配置由 生成的 MBean 的各个方面,稍后将在Source-level Metadata TypesMBeanExporter
中进行更详细的说明。
age
和name
属性都使用注解进行注解ManagedAttribute
,但是,对于age
属性,只有 getter 被标记。这会导致这两个属性都作为属性包含在管理界面中,但该age
属性是只读的。
最后,add(int, int)
方法用ManagedOperation
属性标记,而dontExposeMe()
方法没有。当add(int, int)
您使用MetadataMBeanInfoAssembler
.
以下配置显示了如何配置MBeanExporter
以使用
MetadataMBeanInfoAssembler
:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="assembler" ref="assembler"/>
<property name="namingStrategy" ref="namingStrategy"/>
<property name="autodetect" value="true"/>
</bean>
<bean id="jmxAttributeSource"
class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
<!-- will create management interface using annotation metadata -->
<bean id="assembler"
class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
<property name="attributeSource" ref="jmxAttributeSource"/>
</bean>
<!-- will pick up the ObjectName from the annotation -->
<bean id="namingStrategy"
class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
<property name="attributeSource" ref="jmxAttributeSource"/>
</bean>
<bean id="testBean" class="org.springframework.jmx.AnnotationTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
在前面的例子中,一个MetadataMBeanInfoAssembler
bean 已经配置了一个类的实例,并通过
assembler 属性AnnotationJmxAttributeSource
传递给了。MBeanExporter
这就是为 Spring 公开的 MBean 利用元数据驱动的管理接口所需的全部内容。
5.2.3。源级元数据类型
下表描述了可在 Spring JMX 中使用的源级元数据类型:
目的 | 注解 | 注释类型 |
---|---|---|
将 a 的所有实例标记 |
|
班级 |
将方法标记为 JMX 操作。 |
|
方法 |
将 getter 或 setter 标记为 JMX 属性的一半。 |
|
方法(仅 getter 和 setter) |
定义操作参数的描述。 |
|
方法 |
下表描述了可用于这些源级元数据类型的配置参数:
范围 | 描述 | 适用于 |
---|---|---|
|
用于 |
|
|
设置资源、属性或操作的友好描述。 |
|
|
设置 |
|
|
设置 |
|
|
设置 |
|
|
设置 |
|
|
设置 |
|
|
设置 |
|
|
设置 |
|
|
设置 |
|
|
设置操作参数的显示名称。 |
|
|
设置操作参数的索引。 |
|
5.2.4。使用AutodetectCapableMBeanInfoAssembler
界面
为了进一步简化配置,Spring 包含了
AutodetectCapableMBeanInfoAssembler
接口,它扩展了MBeanInfoAssembler
接口以添加对 MBean 资源自动检测的支持。如果您
MBeanExporter
使用 的实例配置AutodetectCapableMBeanInfoAssembler
,则允许对包含 bean 以暴露于 JMX 进行“投票”。
该AutodetectCapableMBeanInfo
接口的唯一实现是MetadataMBeanInfoAssembler
,它投票包含任何标记有该ManagedResource
属性的 bean。在这种情况下,默认方法是使用 bean 名称作为ObjectName
,这会导致类似于以下的配置:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<!-- notice how no 'beans' are explicitly configured here -->
<property name="autodetect" value="true"/>
<property name="assembler" ref="assembler"/>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
<bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
<property name="attributeSource">
<bean class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
</property>
</bean>
</beans>
请注意,在前面的配置中,没有 bean 传递给MBeanExporter
. 然而,JmxTestBean
仍然被注册,因为它被标记了ManagedResource
属性并且MetadataMBeanInfoAssembler
检测到这个并且投票包含它。这种方法的唯一问题是JmxTestBean
现在的名称具有商业意义。您可以通过更改Controlling Instances for Your BeansObjectName
中定义的默认创建行为来解决此问题。ObjectName
5.2.5。使用 Java 接口定义管理接口
除了 之外MetadataMBeanInfoAssembler
,Spring 还包括
InterfaceBasedMBeanInfoAssembler
,它允许您根据接口集合中定义的方法集来约束公开的方法和属性。
尽管公开 MBean 的标准机制是使用接口和简单的命名方案,InterfaceBasedMBeanInfoAssembler
但通过消除对命名约定的需求、允许您使用多个接口以及消除对 bean 实现 MBean 接口的需求来扩展此功能。
考虑以下接口,它用于为
JmxTestBean
我们之前展示的类定义一个管理接口:
public interface IJmxTestBean {
public int add(int x, int y);
public long myOperation();
public int getAge();
public void setAge(int age);
public void setName(String name);
public String getName();
}
此接口定义了作为 JMX MBean 上的操作和属性公开的方法和属性。下面的代码展示了如何配置 Spring JMX 使用这个接口作为管理接口的定义:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean5" value-ref="testBean"/>
</map>
</property>
<property name="assembler">
<bean class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler">
<property name="managedInterfaces">
<value>org.springframework.jmx.IJmxTestBean</value>
</property>
</bean>
</property>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
在前面的示例中,InterfaceBasedMBeanInfoAssembler
配置为在
IJmxTestBean
为任何 bean 构造管理接口时使用该接口。重要的是要了解由 处理的 beanInterfaceBasedMBeanInfoAssembler
不需要实现用于生成 JMX 管理接口的接口。
在前面的例子中,该IJmxTestBean
接口用于为所有 bean 构造所有管理接口。在许多情况下,这不是所期望的行为,您可能希望对不同的 bean 使用不同的接口。在这种情况下,您可以通过属性传递
InterfaceBasedMBeanInfoAssembler
一个Properties
实例interfaceMappings
,其中每个条目的键是 bean 名称,每个条目的值是用于该 bean 的接口名称的逗号分隔列表。
如果没有通过managedInterfaces
或
interfaceMappings
属性指定管理接口,则InterfaceBasedMBeanInfoAssembler
反映在 bean 上并使用该 bean 实现的所有接口来创建管理接口。
5.2.6。使用MethodNameBasedMBeanInfoAssembler
MethodNameBasedMBeanInfoAssembler
允许您指定作为属性和操作公开给 JMX 的方法名称列表。以下代码显示了一个示例配置:
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean5" value-ref="testBean"/>
</map>
</property>
<property name="assembler">
<bean class="org.springframework.jmx.export.assembler.MethodNameBasedMBeanInfoAssembler">
<property name="managedMethods">
<value>add,myOperation,getName,setName,getAge</value>
</property>
</bean>
</property>
</bean>
在前面的示例中,您可以看到add
andmyOperation
方法作为 JMX 操作公开,并且getName()
, setName(String)
, 和getAge()
作为 JMX 属性的适当一半公开。在前面的代码中,方法映射适用于暴露给 JMX 的 bean。要逐个 bean 控制方法公开,您可以使用 的methodMappings
属性MethodNameMBeanInfoAssembler
将 bean 名称映射到方法名称列表。
5.3. 控制ObjectName
Bean 的实例
在幕后,MBeanExporter
委托一个实现为
它注册的每个 beanObjectNamingStrategy
获取一个ObjectName
实例。默认情况下,默认实现KeyNamingStrategy
使用 的键
beans
Map
作为ObjectName
. 此外,KeyNamingStrategy
可以将 的键映射到文件(或多个文件)中
beans
Map
的条目以解析. 除了 之外,Spring 还提供了两个额外
的实现:(基于 bean 的 JVM 标识构建)和(
使用源级元数据来获取)。Properties
ObjectName
KeyNamingStrategy
ObjectNamingStrategy
IdentityNamingStrategy
ObjectName
MetadataNamingStrategy
ObjectName
5.3.1。ObjectName
从属性中读取实例
您可以配置自己的KeyNamingStrategy
实例并将其配置为
ObjectName
从实例中读取实例,Properties
而不是使用 bean 键。KeyNamingStrategy
尝试使用与 bean 键对应的键来定位 中的条目
Properties
。如果没有找到条目或Properties
实例是
null
,则使用 bean 键本身。
以下代码显示了 的示例配置KeyNamingStrategy
:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="testBean" value-ref="testBean"/>
</map>
</property>
<property name="namingStrategy" ref="namingStrategy"/>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
<bean id="namingStrategy" class="org.springframework.jmx.export.naming.KeyNamingStrategy">
<property name="mappings">
<props>
<prop key="testBean">bean:name=testBean1</prop>
</props>
</property>
<property name="mappingLocations">
<value>names1.properties,names2.properties</value>
</property>
</bean>
</beans>
前面的示例配置了KeyNamingStrategy
一个实例,该实例由映射属性定义的实例和位于映射属性定义的路径中的属性文件Properties
合并而成。Properties
在此配置中,testBean
bean 被赋予ObjectName
of bean:name=testBean1
,因为这是Properties
实例中具有与 bean 键对应的键的条目。
如果在实例中找不到条目,Properties
则使用 bean 键名作为ObjectName
.
5.3.2. 使用MetadataNamingStrategy
MetadataNamingStrategy
使用每个 bean 上的属性objectName
的ManagedResource
属性来创建ObjectName
. 以下代码显示了 的配置MetadataNamingStrategy
:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="testBean" value-ref="testBean"/>
</map>
</property>
<property name="namingStrategy" ref="namingStrategy"/>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
<bean id="namingStrategy" class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
<property name="attributeSource" ref="attributeSource"/>
</bean>
<bean id="attributeSource"
class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
</beans>
如果没有objectName
为该ManagedResource
属性提供,
ObjectName
则使用以下格式创建一个:[fully-qualified-package-name]:type=[short-classname],name=[bean-name]。例如,ObjectName
为以下 bean 生成的将是
com.example:type=MyClass,name=myBean
:
<bean id="myBean" class="com.example.MyClass"/>
5.3.3. 配置基于注解的 MBean 导出
如果您更喜欢使用基于注解的方法来定义您的管理接口,可以使用一个方便的子类MBeanExporter
:
AnnotationMBeanExporter
. 定义此子类的实例时,您不再需要
namingStrategy
、assembler
和attributeSource
配置,因为它始终使用标准的基于 Java 注释的元数据(也始终启用自动检测)。事实上,注解MBeanExporter
支持更简单的语法,而不是定义 bean,@EnableMBeanExport
@Configuration
如以下示例所示:
@Configuration
@EnableMBeanExport
public class AppConfig {
}
如果您更喜欢基于 XML 的配置,则该<context:mbean-export/>
元素具有相同的用途,并显示在以下清单中:
<context:mbean-export/>
如有必要,您可以提供对特定 MBean 的引用server
,并且
defaultDomain
属性( 的属性AnnotationMBeanExporter
)接受生成的 MBeanObjectName
域的替代值。这用于代替上一节
MetadataNamingStrategy中描述的完全限定包名称,如以下示例所示:
@EnableMBeanExport(server="myMBeanServer", defaultDomain="myDomain")
@Configuration
ContextConfiguration {
}
以下示例显示了前面基于注释的示例的 XML 等效项:
<context:mbean-export server="myMBeanServer" default-domain="myDomain"/>
不要将基于接口的 AOP 代理与 bean 类中 JMX 注释的自动检测结合使用。基于接口的代理“隐藏”目标类,这也隐藏了 JMX 管理的资源注释。因此,在这种情况下,您应该使用目标类代理(通过设置 'proxy-target-class' 标志<aop:config/> ,
<tx:annotation-driven/> 等等)。否则,您的 JMX bean 可能会在启动时被静默忽略。
|
5.4. 使用 JSR-160 连接器
对于远程访问,Spring JMX 模块FactoryBean
在包内提供了两种实现,
org.springframework.jmx.support
用于创建服务器端和客户端连接器。
5.4.1。服务器端连接器
要让 Spring JMX 创建、启动和公开 JSR-160 JMXConnectorServer
,您可以使用以下配置:
<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean"/>
默认情况下,ConnectorServerFactoryBean
创建一个JMXConnectorServer
绑定到
service:jmx:jmxmp://localhost:9875
. 因此,该serverConnector
beanMBeanServer
通过 localhost 端口 9875 上的 JMXMP 协议向客户端公开本地。请注意,JSR 160 规范将 JMXMP 协议标记为可选。目前主要的开源 JMX 实现 MX4J 和 JDK 提供的实现不支持 JMXMP。
要指定另一个 URL 并将其JMXConnectorServer
自身
注册到MBeanServer
,您可以分别使用serviceUrl
和ObjectName
属性,如以下示例所示:
<bean id="serverConnector"
class="org.springframework.jmx.support.ConnectorServerFactoryBean">
<property name="objectName" value="connector:name=rmi"/>
<property name="serviceUrl"
value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector"/>
</bean>
如果ObjectName
设置了该属性,Spring 会自动将您的连接器注册到MBeanServer
该ObjectName
. 以下示例显示了ConnectorServerFactoryBean
在创建 时
可以传递给 的完整参数集JMXConnector
:
<bean id="serverConnector"
class="org.springframework.jmx.support.ConnectorServerFactoryBean">
<property name="objectName" value="connector:name=iiop"/>
<property name="serviceUrl"
value="service:jmx:iiop://localhost/jndi/iiop://localhost:900/myconnector"/>
<property name="threaded" value="true"/>
<property name="daemon" value="true"/>
<property name="environment">
<map>
<entry key="someKey" value="someValue"/>
</map>
</property>
</bean>
请注意,当您使用基于 RMI 的连接器时,您需要启动查找服务 (tnameserv
或
rmiregistry
) 才能完成名称注册。如果你使用 Spring 通过 RMI 为你导出远程服务,那么 Spring 已经构建了一个 RMI 注册中心。如果没有,您可以使用以下配置片段轻松启动注册表:
<bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
<property name="port" value="1099"/>
</bean>
5.4.2. 客户端连接器
要创建MBeanServerConnection
远程 JSR-160-enabled MBeanServer
,您可以使用
MBeanServerConnectionFactoryBean
,如以下示例所示:
<bean id="clientConnector" class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
<property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"/>
</bean>
5.4.3. 基于 Hessian 或 SOAP 的 JMX
JSR-160 允许扩展客户端和服务器之间的通信方式。前面部分中显示的示例使用 JSR-160 规范(IIOP 和 JRMP)和(可选)JMXMP 要求的基于 RMI 的强制实现。通过使用其他提供程序或 JMX 实现(例如MX4J),您可以利用 SOAP 或 Hessian 等协议而不是简单的 HTTP 或 SSL 等,如以下示例所示:
<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean">
<property name="objectName" value="connector:name=burlap"/>
<property name="serviceUrl" value="service:jmx:burlap://localhost:9874"/>
</bean>
在前面的示例中,我们使用了 MX4J 3.0.0。有关详细信息,请参阅官方 MX4J 文档。
5.5. 通过代理访问 MBean
Spring JMX 允许您创建代理,将调用重新路由到在本地或远程注册的 MBean MBeanServer
。这些代理为您提供了一个标准的 Java 接口,您可以通过它与您的 MBean 进行交互。以下代码显示了如何为在本地运行的 MBean 配置代理MBeanServer
:
<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
<property name="objectName" value="bean:name=testBean"/>
<property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/>
</bean>
在前面的示例中,您可以看到为在 of 下注册的 MBean 创建了一个
ObjectName
代理bean:name=testBean
。代理实现的接口集由proxyInterfaces
属性控制,将这些接口上的方法和属性映射到 MBean 上的操作和属性的规则与InterfaceBasedMBeanInfoAssembler
.
MBeanProxyFactoryBean
可以为任何可
通过MBeanServerConnection
. 默认情况下,本地MBeanServer
被定位并使用,但您可以覆盖它并提供一个MBeanServerConnection
指向远程
MBeanServer
的,以迎合指向远程 MBean 的代理:
<bean id="clientConnector"
class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
<property name="serviceUrl" value="service:jmx:rmi://remotehost:9875"/>
</bean>
<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
<property name="objectName" value="bean:name=testBean"/>
<property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/>
<property name="server" ref="clientConnector"/>
</bean>
在前面的示例中,我们创建了一个MBeanServerConnection
指向使用MBeanServerConnectionFactoryBean
. 然后MBeanServerConnection
将其传递给MBeanProxyFactoryBean
throughserver
属性。MBeanServer
创建的代理通过 this
将所有调用转发给MBeanServerConnection
。
5.6. 通知
Spring 的 JMX 产品包括对 JMX 通知的全面支持。
5.6.1。为通知注册监听器
Spring 的 JMX 支持使注册任意数量
NotificationListeners
的 MBean 变得容易(这包括 Spring 导出的MBeanExporter
MBean 和通过其他机制注册的 MBean)。例如,考虑这样一种场景:
Notification
每次目标 MBean 的属性发生更改时,都希望(通过 a )得到通知。以下示例将通知写入控制台:
package com.example;
import javax.management.AttributeChangeNotification;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
public class ConsoleLoggingNotificationListener
implements NotificationListener, NotificationFilter {
public void handleNotification(Notification notification, Object handback) {
System.out.println(notification);
System.out.println(handback);
}
public boolean isNotificationEnabled(Notification notification) {
return AttributeChangeNotification.class.isAssignableFrom(notification.getClass());
}
}
以下示例将ConsoleLoggingNotificationListener
(在前面的示例中定义)添加到notificationListenerMappings
:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
<property name="notificationListenerMappings">
<map>
<entry key="bean:name=testBean1">
<bean class="com.example.ConsoleLoggingNotificationListener"/>
</entry>
</map>
</property>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
通过上述配置,每次Notification
从目标 MBean ( bean:name=testBean1
)广播 JMX 时,都会通知通过属性ConsoleLoggingNotificationListener
注册为侦听器的 bean 。notificationListenerMappings
然后ConsoleLoggingNotificationListener
bean 可以采取任何它认为合适的动作来响应Notification
.
您还可以使用直接 bean 名称作为导出的 bean 和侦听器之间的链接,如以下示例所示:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
<property name="notificationListenerMappings">
<map>
<entry key="testBean">
<bean class="com.example.ConsoleLoggingNotificationListener"/>
</entry>
</map>
</property>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
NotificationListener
如果要为封闭导出的所有 bean注册一个实例MBeanExporter
,可以使用特殊通配符 ( *
) 作为属性映射中条目的键notificationListenerMappings
,如以下示例所示:
<property name="notificationListenerMappings">
<map>
<entry key="*">
<bean class="com.example.ConsoleLoggingNotificationListener"/>
</entry>
</map>
</property>
如果您需要执行相反的操作(即,针对 MBean 注册多个不同的侦听器),则必须改为使用notificationListeners
list 属性(优先于该notificationListenerMappings
属性)。这一次,我们不是NotificationListener
为单个 MBean 配置一个,而是配置
NotificationListenerBean
实例。A将 a和要注册的(or )NotificationListenerBean
封装
在 a 中。还封装了许多其他属性,例如可用于高级 JMX 通知场景的 a 和任意 handback 对象。NotificationListener
ObjectName
ObjectNames
MBeanServer
NotificationListenerBean
NotificationFilter
使用NotificationListenerBean
实例时的配置与之前介绍的配置并没有太大的不同,如以下示例所示:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
<property name="notificationListeners">
<list>
<bean class="org.springframework.jmx.export.NotificationListenerBean">
<constructor-arg>
<bean class="com.example.ConsoleLoggingNotificationListener"/>
</constructor-arg>
<property name="mappedObjectNames">
<list>
<value>bean:name=testBean1</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
前面的示例等效于第一个通知示例。然后,假设我们希望在每次 aNotification
被提升时都获得一个 handback 对象,并且我们还希望Notifications
通过提供 a
来过滤掉无关的NotificationFilter
。以下示例实现了这些目标:
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean1"/>
<entry key="bean:name=testBean2" value-ref="testBean2"/>
</map>
</property>
<property name="notificationListeners">
<list>
<bean class="org.springframework.jmx.export.NotificationListenerBean">
<constructor-arg ref="customerNotificationListener"/>
<property name="mappedObjectNames">
<list>
<!-- handles notifications from two distinct MBeans -->
<value>bean:name=testBean1</value>
<value>bean:name=testBean2</value>
</list>
</property>
<property name="handback">
<bean class="java.lang.String">
<constructor-arg value="This could be anything..."/>
</bean>
</property>
<property name="notificationFilter" ref="customerNotificationListener"/>
</bean>
</list>
</property>
</bean>
<!-- implements both the NotificationListener and NotificationFilter interfaces -->
<bean id="customerNotificationListener" class="com.example.ConsoleLoggingNotificationListener"/>
<bean id="testBean1" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
<bean id="testBean2" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="ANOTHER TEST"/>
<property name="age" value="200"/>
</bean>
</beans>
(有关什么是 handback 对象以及实际上是什么的完整讨论NotificationFilter
,请参阅 JMX 规范 (1.2) 中标题为“JMX 通知模型”的部分。)
5.6.2. 发布通知
Spring 不仅支持注册接收Notifications
,还支持发布Notifications
。
本节实际上只与通过MBeanExporter . 任何现有的用户定义 MBean 都应使用标准 JMX API 来发布通知。
|
Spring 的 JMX 通知发布支持中的关键接口是
NotificationPublisher
接口(在
org.springframework.jmx.export.notification
包中定义)。任何将通过实例导出为 MBean 的 bean 都MBeanExporter
可以实现相关
NotificationPublisherAware
接口以获取对NotificationPublisher
实例的访问权限。该NotificationPublisherAware
接口通过一个简单的 setter 方法向实现 bean 提供一个实例,
NotificationPublisher
然后 bean 可以使用该实例来发布Notifications
。
如接口的javadoc中所述
NotificationPublisher
,通过该NotificationPublisher
机制发布事件的托管bean不负责通知监听器的状态管理。Spring 的 JMX 支持负责处理所有 JMX 基础结构问题。作为应用程序开发人员,您需要做的就是实现
NotificationPublisherAware
接口并使用提供的NotificationPublisher
实例开始发布事件。请注意,NotificationPublisher
在托管 bean 注册后设置MBeanServer
.
使用NotificationPublisher
实例非常简单。您创建一个 JMX
Notification
实例(或适当Notification
子类的实例),使用与要发布的事件相关的数据填充通知,并sendNotification(Notification)
在
NotificationPublisher
实例上调用Notification
.
在以下示例中,每次调用操作时都会导出JmxTestBean
发布
实例:NotificationEvent
add(int, int)
package org.springframework.jmx;
import org.springframework.jmx.export.notification.NotificationPublisherAware;
import org.springframework.jmx.export.notification.NotificationPublisher;
import javax.management.Notification;
public class JmxTestBean implements IJmxTestBean, NotificationPublisherAware {
private String name;
private int age;
private boolean isSuperman;
private NotificationPublisher publisher;
// other getters and setters omitted for clarity
public int add(int x, int y) {
int answer = x + y;
this.publisher.sendNotification(new Notification("add", this, 0));
return answer;
}
public void dontExposeMe() {
throw new RuntimeException();
}
public void setNotificationPublisher(NotificationPublisher notificationPublisher) {
this.publisher = notificationPublisher;
}
}
使所有这些工作的NotificationPublisher
接口和机制是 Spring 的 JMX 支持的更好特性之一。然而,它确实伴随着将你的类耦合到 Spring 和 JMX 的代价。与往常一样,这里的建议是务实。如果您需要提供的功能,NotificationPublisher
并且您可以接受与 Spring 和 JMX 的耦合,那么就这样做。
5.7. 更多资源
本节包含指向有关 JMX 的更多资源的链接:
-
Oracle的JMX 主页。
-
JMX 规范( JSR-000003)。
-
JMX 远程 API 规范(JSR-000160) 。
-
MX4J主页。(MX4J 是各种 JMX 规范的开源实现。)
6. 电子邮件
本节介绍如何使用 Spring Framework 发送电子邮件。
Spring 框架为发送电子邮件提供了一个有用的实用程序库,它使您免受底层邮件系统的细节的影响,并代表客户端负责低级资源处理。
该org.springframework.mail
包是 Spring Framework 的电子邮件支持的根级别包。发送电子邮件的中心界面是MailSender
界面。一个简单的值对象,它封装了简单邮件的属性,例如from
和to
(以及许多其他)是SimpleMailMessage
类。这个包还包含一个已检查异常的层次结构,它提供了对较低级别邮件系统异常的更高级别的抽象,根异常是
MailException
. 有关丰富的邮件异常层次结构的更多信息,请参阅javadoc
。
该org.springframework.mail.javamail.JavaMailSender
接口添加了专门的 JavaMail 功能,例如对MailSender
接口(它继承自该接口)的 MIME 消息支持。JavaMailSender
还提供了一个回调接口
org.springframework.mail.javamail.MimeMessagePreparator
,用于准备一个MimeMessage
.
6.1。用法
假设我们有一个名为 的业务接口OrderManager
,如以下示例所示:
public interface OrderManager {
void placeOrder(Order order);
}
进一步假设我们有一个要求,说明需要生成带有订单号的电子邮件并将其发送给下达相关订单的客户。
6.1.1. 基本MailSender
和SimpleMailMessage
用法
以下示例显示了如何在有人下订单时使用MailSender
和SimpleMailMessage
发送电子邮件:
import org.springframework.mail.MailException;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
public class SimpleOrderManager implements OrderManager {
private MailSender mailSender;
private SimpleMailMessage templateMessage;
public void setMailSender(MailSender mailSender) {
this.mailSender = mailSender;
}
public void setTemplateMessage(SimpleMailMessage templateMessage) {
this.templateMessage = templateMessage;
}
public void placeOrder(Order order) {
// Do the business calculations...
// Call the collaborators to persist the order...
// Create a thread safe "copy" of the template message and customize it
SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
msg.setTo(order.getCustomer().getEmailAddress());
msg.setText(
"Dear " + order.getCustomer().getFirstName()
+ order.getCustomer().getLastName()
+ ", thank you for placing order. Your order number is "
+ order.getOrderNumber());
try {
this.mailSender.send(msg);
}
catch (MailException ex) {
// simply log it and go on...
System.err.println(ex.getMessage());
}
}
}
以下示例显示了上述代码的 bean 定义:
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="mail.mycompany.example"/>
</bean>
<!-- this is a template message that we can pre-load with default state -->
<bean id="templateMessage" class="org.springframework.mail.SimpleMailMessage">
<property name="from" value="[email protected]"/>
<property name="subject" value="Your order"/>
</bean>
<bean id="orderManager" class="com.mycompany.businessapp.support.SimpleOrderManager">
<property name="mailSender" ref="mailSender"/>
<property name="templateMessage" ref="templateMessage"/>
</bean>
6.1.2. 使用JavaMailSender
和MimeMessagePreparator
本节描述了另一个OrderManager
使用MimeMessagePreparator
回调接口的实现。在以下示例中,该mailSender
属性是类型
JavaMailSender
,以便我们能够使用 JavaMailMimeMessage
类:
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMessage;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessagePreparator;
public class SimpleOrderManager implements OrderManager {
private JavaMailSender mailSender;
public void setMailSender(JavaMailSender mailSender) {
this.mailSender = mailSender;
}
public void placeOrder(final Order order) {
// Do the business calculations...
// Call the collaborators to persist the order...
MimeMessagePreparator preparator = new MimeMessagePreparator() {
public void prepare(MimeMessage mimeMessage) throws Exception {
mimeMessage.setRecipient(Message.RecipientType.TO,
new InternetAddress(order.getCustomer().getEmailAddress()));
mimeMessage.setFrom(new InternetAddress("[email protected]"));
mimeMessage.setText("Dear " + order.getCustomer().getFirstName() + " " +
order.getCustomer().getLastName() + ", thanks for your order. " +
"Your order number is " + order.getOrderNumber() + ".");
}
};
try {
this.mailSender.send(preparator);
}
catch (MailException ex) {
// simply log it and go on...
System.err.println(ex.getMessage());
}
}
}
邮件代码是一个横切关注点,很可能是重构为自定义 Spring AOP 方面的候选者,然后可以在OrderManager 目标上的适当连接点处运行。
|
Spring Framework 的邮件支持随标准 JavaMail 实现一起提供。有关更多信息,请参阅相关的 javadoc。
6.2. 使用 JavaMailMimeMessageHelper
处理 JavaMail 消息时非常方便的一个类是
org.springframework.mail.javamail.MimeMessageHelper
,它使您不必使用冗长的 JavaMail API。使用MimeMessageHelper
,很容易创建MimeMessage
,如以下示例所示:
// of course you would use DI in any real-world cases
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");
MimeMessage message = sender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message);
helper.setTo("[email protected]");
helper.setText("Thank you for ordering!");
sender.send(message);
6.2.1. 发送附件和内联资源
多部分电子邮件消息允许使用附件和内联资源。内联资源的示例包括要在消息中使用但不希望显示为附件的图像或样式表。
附件
以下示例向您展示如何使用MimeMessageHelper
发送带有单个 JPEG 图像附件的电子邮件:
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");
MimeMessage message = sender.createMimeMessage();
// use the true flag to indicate you need a multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo("[email protected]");
helper.setText("Check out this image!");
// let's attach the infamous windows Sample file (this time copied to c:/)
FileSystemResource file = new FileSystemResource(new File("c:/Sample.jpg"));
helper.addAttachment("CoolImage.jpg", file);
sender.send(message);
内联资源
以下示例向您展示如何使用MimeMessageHelper
发送带有内嵌图像的电子邮件:
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");
MimeMessage message = sender.createMimeMessage();
// use the true flag to indicate you need a multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo("[email protected]");
// use the true flag to indicate the text included is HTML
helper.setText("<html><body><img src='cid:identifier1234'></body></html>", true);
// let's include the infamous windows Sample file (this time copied to c:/)
FileSystemResource res = new FileSystemResource(new File("c:/Sample.jpg"));
helper.addInline("identifier1234", res);
sender.send(message);
MimeMessage 通过使用指定的Content-ID
(identifier1234 在上面的示例中)
将内联资源添加到。添加文本和资源的顺序非常重要。请务必先添加文本,然后再添加资源。如果你反其道而行之,那就行不通了。
|
6.2.2. 使用模板库创建电子邮件内容
前面部分中显示的示例中的代码通过使用诸如message.setText(..)
. 这对于简单的情况很好,在上述示例的上下文中也可以,其目的是向您展示 API 的基础知识。
但是,在您的典型企业应用程序中,开发人员通常不使用前面显示的方法创建电子邮件的内容,原因有很多:
-
在 Java 代码中创建基于 HTML 的电子邮件内容既乏味又容易出错。
-
显示逻辑和业务逻辑之间没有明确的区分。
-
更改电子邮件内容的显示结构需要编写 Java 代码、重新编译、重新部署等。
通常,解决这些问题的方法是使用模板库(例如 FreeMarker)来定义电子邮件内容的显示结构。这使您的代码只负责创建要在电子邮件模板中呈现的数据并发送电子邮件。当您的电子邮件内容变得相当复杂时,这绝对是一个最佳实践,并且使用 Spring 框架对 FreeMarker 的支持类,它变得非常容易做到。
7. 任务执行与调度
Spring Framework分别使用TaskExecutor
和接口为任务的异步执行和调度提供了抽象。TaskScheduler
Spring 还具有在应用程序服务器环境中支持线程池或委托给 CommonJ 的那些接口的实现。最终,在公共接口后面使用这些实现可以抽象出 Java SE 5、Java SE 6 和 Java EE 环境之间的差异。
Spring 还具有集成类以支持使用Timer
(自 1.3 以来的 JDK 的一部分)和 Quartz 调度程序(https://www.quartz-scheduler.org/)进行调度。您可以分别使用对或实例的FactoryBean
可选引用来
设置这两个调度程序。此外,还有一个用于 Quartz Scheduler 和 的便利类,可让您调用现有目标对象的方法(类似于正常
操作)。Timer
Trigger
Timer
MethodInvokingFactoryBean
7.1。Spring的TaskExecutor
抽象
Executors 是线程池概念的 JDK 名称。“执行者”命名是因为不能保证底层实现实际上是一个池。执行器可能是单线程的,甚至是同步的。Spring 的抽象隐藏了 Java SE 和 Java EE 环境之间的实现细节。
Spring的TaskExecutor
接口与接口相同java.util.concurrent.Executor
。事实上,最初,它存在的主要原因是在使用线程池时抽象出对 Java 5 的需求。该接口有一个方法 ( execute(Runnable task)
),它根据线程池的语义和配置接受要执行的任务。
TaskExecutor
最初创建它是为了在需要时为其他 Spring 组件提供用于线程池的抽象。ApplicationEventMulticaster
、JMSAbstractMessageListenerContainer
和 Quartz 集成
等组件都使用TaskExecutor
抽象来池线程。但是,如果您的 bean 需要线程池行为,您也可以根据自己的需要使用此抽象。
7.1.1. TaskExecutor
类型
Spring 包括许多预构建的TaskExecutor
. 很可能,您永远不需要实现自己的。Spring提供的变体如下:
-
SyncTaskExecutor
:此实现不会异步运行调用。相反,每次调用都发生在调用线程中。它主要用于不需要多线程的情况,例如简单的测试用例。 -
SimpleAsyncTaskExecutor
:此实现不重用任何线程。相反,它为每次调用启动一个新线程。但是,它确实支持并发限制,该限制会阻止任何超过限制的调用,直到插槽被释放。如果您正在寻找真正的池化,请参阅ThreadPoolTaskExecutor
此列表后面的 。 -
ConcurrentTaskExecutor
:这个实现是一个java.util.concurrent.Executor
实例的适配器。有一个替代方法 (ThreadPoolTaskExecutor
) 将Executor
配置参数公开为 bean 属性。很少需要ConcurrentTaskExecutor
直接使用。但是,如果ThreadPoolTaskExecutor
它对您的需求不够灵活,ConcurrentTaskExecutor
则可以选择。 -
ThreadPoolTaskExecutor
: 这个实现是最常用的。它公开了用于配置 a 的 bean 属性java.util.concurrent.ThreadPoolExecutor
并将其包装在 a 中TaskExecutor
。如果您需要适应不同类型的java.util.concurrent.Executor
,我们建议您使用 aConcurrentTaskExecutor
来代替。 -
WorkManagerTaskExecutor
:此实现使用 CommonJWorkManager
作为其支持服务提供者,并且是在 Spring 应用程序上下文中在 WebLogic 或 WebSphere 上设置基于 CommonJ 的线程池集成的中心便利类。 -
DefaultManagedTaskExecutor
:此实现使用ManagedExecutorService
在兼容 JSR-236 的运行时环境(例如 Java EE 7+ 应用程序服务器)中获得的 JNDI,为此目的替换了 CommonJ WorkManager。
7.1.2. 用一个TaskExecutor
Spring 的TaskExecutor
实现被用作简单的 JavaBean。在下面的示例中,我们定义了一个使用ThreadPoolTaskExecutor
来异步打印出一组消息的 bean:
import org.springframework.core.task.TaskExecutor;
public class TaskExecutorExample {
private class MessagePrinterTask implements Runnable {
private String message;
public MessagePrinterTask(String message) {
this.message = message;
}
public void run() {
System.out.println(message);
}
}
private TaskExecutor taskExecutor;
public TaskExecutorExample(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
public void printMessages() {
for(int i = 0; i < 25; i++) {
taskExecutor.execute(new MessagePrinterTask("Message" + i));
}
}
}
如您所见,您不是从池中检索线程并自己执行它,而是将您的线程添加Runnable
到队列中。然后TaskExecutor
使用其内部规则来决定任务何时运行。
为了配置使用的规则TaskExecutor
,我们公开了简单的 bean 属性:
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5"/>
<property name="maxPoolSize" value="10"/>
<property name="queueCapacity" value="25"/>
</bean>
<bean id="taskExecutorExample" class="TaskExecutorExample">
<constructor-arg ref="taskExecutor"/>
</bean>
7.2. Spring的TaskScheduler
抽象
除了TaskExecutor
抽象之外,Spring 3.0 还引入了TaskScheduler
一种用于调度任务以在未来某个时间点运行的多种方法。以下清单显示了TaskScheduler
接口定义:
public interface TaskScheduler {
ScheduledFuture schedule(Runnable task, Trigger trigger);
ScheduledFuture schedule(Runnable task, Instant startTime);
ScheduledFuture schedule(Runnable task, Date startTime);
ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);
ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);
ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);
ScheduledFuture scheduleAtFixedRate(Runnable task, long period);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);
}
schedule
最简单的方法是仅采用 aRunnable
和 a的命名方法Date
。这会导致任务在指定时间后运行一次。所有其他方法都能够安排任务重复运行。固定速率和固定延迟方法用于简单的周期性执行,但接受 a 的方法Trigger
要灵活得多。
7.2.1. Trigger
界面
该Trigger
接口本质上受到 JSR-236 的启发,截至 Spring 3.0,该接口尚未正式实现。其基本思想Trigger
是可以根据过去的执行结果甚至任意条件来确定执行时间。如果这些确定确实考虑了先前执行的结果,则该信息可在TriggerContext
. Trigger
界面本身非常简单,如以下清单所示:
public interface Trigger {
Date nextExecutionTime(TriggerContext triggerContext);
}
是最TriggerContext
重要的部分。它封装了所有相关数据,并在必要时开放以供将来扩展。这
TriggerContext
是一个接口(SimpleTriggerContext
默认使用一个实现)。以下清单显示了Trigger
实现的可用方法。
public interface TriggerContext {
Date lastScheduledExecutionTime();
Date lastActualExecutionTime();
Date lastCompletionTime();
}
7.2.2. Trigger
实现
Spring 提供了两种Trigger
接口的实现。最有趣的是CronTrigger
. 它支持基于
cron 表达式的任务调度。例如,以下任务计划在每小时 15 分钟后运行,但仅在工作日的朝九晚五“营业时间”运行:
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
另一种实现是PeriodicTrigger
接受一个固定周期、一个可选的初始延迟值和一个布尔值,以指示该周期应该被解释为固定速率还是固定延迟。由于TaskScheduler
接口已经定义了以固定速率或固定延迟调度任务的方法,因此应尽可能直接使用这些方法。实现的价值
PeriodicTrigger
在于您可以在依赖Trigger
抽象的组件中使用它。例如,允许定期触发器、基于 cron 的触发器甚至自定义触发器实现可互换使用可能很方便。这样的组件可以利用依赖注入,以便您可以在Triggers
外部进行配置,从而轻松修改或扩展它们。
7.2.3. TaskScheduler
实现
与 Spring 的TaskExecutor
抽象一样,这种TaskScheduler
安排的主要好处是应用程序的调度需求与部署环境分离。当部署到不应由应用程序本身直接创建线程的应用程序服务器环境时,此抽象级别特别相关。对于这样的场景,Spring 提供了一种TimerManagerTaskScheduler
委托给TimerManager
WebLogic 或 WebSphere 上的 CommonJ 的方法,以及最近的
DefaultManagedTaskScheduler
一种委托给ManagedScheduledExecutorService
Java EE 7+ 环境中的 JSR-236 的方法。两者通常都配置有 JNDI 查找。
每当不需要外部线程管理时,更简单的替代方法是ScheduledExecutorService
在应用程序中进行本地设置,可以通过 Spring 的ConcurrentTaskScheduler
. 为方便起见,Spring 还提供了 a
ThreadPoolTaskScheduler
,它在内部委托 aScheduledExecutorService
以提供常见的 bean 样式配置,类似于ThreadPoolTaskExecutor
. 这些变体在宽松的应用程序服务器环境中也非常适合本地嵌入式线程池设置——尤其是在 Tomcat 和 Jetty 上。
7.3. 对调度和异步执行的注解支持
Spring 为任务调度和异步方法执行提供注解支持。
7.3.1. 启用计划注释
要启用对@Scheduled
和@Async
注释的支持,您可以向其中一个类添加@EnableScheduling
和
,如以下示例所示:@EnableAsync
@Configuration
@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}
您可以为您的应用程序挑选相关注释。例如,如果您只需要对 的支持@Scheduled
,则可以省略@EnableAsync
. 对于更细粒度的控制,您可以另外实现SchedulingConfigurer
接口、AsyncConfigurer
接口或两者。有关完整的详细信息,请参阅
SchedulingConfigurer
和AsyncConfigurer
javadoc。
如果您更喜欢 XML 配置,则可以使用该<task:annotation-driven>
元素,如以下示例所示:
<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="5"/>
<task:scheduler id="myScheduler" pool-size="10"/>
请注意,在前面的 XML 中,提供了一个执行器引用来处理与带有@Async
注释的方法相对应的那些任务,并且提供了调度器引用来管理那些带有注释的方法@Scheduled
。
处理@Async 注解的默认建议模式是proxy 只允许通过代理拦截调用。同一类中的本地调用不能以这种方式被拦截。对于更高级的拦截模式,请考虑切换到aspectj 结合编译时或加载时编织的模式。
|
7.3.2. @Scheduled
注释_
您可以将@Scheduled
注释与触发器元数据一起添加到方法中。例如,以下方法以固定延迟每五秒(5000 毫秒)调用一次,这意味着该周期是从每个先前调用的完成时间开始计算的。
@Scheduled(fixedDelay = 5000)
public void doSomething() {
// something that should run periodically
}
默认情况下,毫秒将用作固定延迟、固定速率和初始延迟值的时间单位。如果您想使用不同的时间单位,例如秒或分钟,您可以通过 中的 例如,前面的例子也可以写成如下。
|
如果需要固定速率执行,可以使用fixedRate
注解中的属性。以下方法每五秒调用一次(在每次调用的连续开始时间之间测量)。
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS)
public void doSomething() {
// something that should run periodically
}
对于固定延迟和固定速率任务,您可以通过指示在第一次执行方法之前等待的时间量来指定初始延迟,如以下
fixedRate
示例所示。
@Scheduled(initialDelay = 1000, fixedRate = 5000)
public void doSomething() {
// something that should run periodically
}
如果简单的周期性调度不够表达,你可以提供一个 cron 表达式。以下示例仅在工作日运行:
@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
// something that should run on weekdays only
}
您还可以使用该zone 属性来指定解析 cron 表达式的时区。
|
请注意,要调度的方法必须有 void 返回,并且不能接受任何参数。如果该方法需要与应用程序上下文中的其他对象进行交互,那么这些对象通常是通过依赖注入提供的。
从 Spring Framework 4.3 开始, 确保您没有 |
7.3.3. @Async
注释_
您可以@Async
在方法上提供注释,以便异步调用该方法。换句话说,调用者在调用时立即返回,而方法的实际执行发生在已提交给 Spring 的任务中TaskExecutor
。在最简单的情况下,您可以将注解应用于返回 的方法void
,如以下示例所示:
@Async
void doSomething() {
// this will be run asynchronously
}
与使用注解注解的方法不同@Scheduled
,这些方法可以期待参数,因为它们是由调用者在运行时以“正常”方式调用的,而不是从容器管理的计划任务中调用的。例如,以下代码是@Async
注解的合法应用:
@Async
void doSomething(String s) {
// this will be run asynchronously
}
甚至可以异步调用返回值的方法。但是,此类方法需要具有- 类型的Future
返回值。这仍然提供了异步执行的好处,以便调用者可以在调用它之前执行其他
get()
任务Future
。以下示例显示了如何@Async
在返回值的方法上使用:
@Async
Future<String> returnSomething(int i) {
// this will be run asynchronously
}
@Async 方法不仅可以声明常规java.util.concurrent.Future 返回类型,还可以声明 Springorg.springframework.util.concurrent.ListenableFuture 或从 Spring 4.2 开始的 JDK 8 java.util.concurrent.CompletableFuture ,以便与异步任务进行更丰富的交互并与进一步的处理步骤进行即时组合。
|
您不能@Async
与生命周期回调一起使用,例如
@PostConstruct
. 要异步初始化 Spring bean,您当前必须使用单独的初始化 Spring bean,然后调用@Async
目标上的注释方法,如以下示例所示:
public class SampleBeanImpl implements SampleBean {
@Async
void doSomething() {
// ...
}
}
public class SampleBeanInitializer {
private final SampleBean bean;
public SampleBeanInitializer(SampleBean bean) {
this.bean = bean;
}
@PostConstruct
public void initialize() {
bean.doSomething();
}
}
没有直接的 XML 等效@Async 项,因为此类方法首先应设计为异步执行,而不是在外部重新声明为异步。AsyncExecutionInterceptor 但是,您可以结合自定义切入点使用 Spring AOP
手动设置 Spring 。 |
7.3.4. 执行人资格与@Async
默认情况下,在指定@Async
方法时,使用的执行器是启用异步支持时配置的执行器,即如果您使用 XML 或您的AsyncConfigurer
实现(如果有),则为“注释驱动”元素。但是,
当您需要指示在执行给定方法时应该使用默认值以外的执行器时,您可以使用注解的value
属性。@Async
以下示例显示了如何执行此操作:
@Async("otherExecutor")
void doSomething(String s) {
// this will be run asynchronously by "otherExecutor"
}
在这种情况下,"otherExecutor"
可以是Executor
Spring 容器中任何 bean 的名称,也可以是与 any 关联的限定符的名称Executor
(例如,由<qualifier>
元素或 Spring 的@Qualifier
注释指定)。
7.3.5。异常管理@Async
当@Async
方法具有Future
-typed 返回值时,很容易管理在方法执行期间引发的异常,因为在调用结果时会引发此get
异常Future
。但是,对于void
返回类型,异常不会被捕获并且无法传输。您可以提供一个
AsyncUncaughtExceptionHandler
来处理此类异常。以下示例显示了如何执行此操作:
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
// handle exception
}
}
默认情况下,仅记录异常。AsyncUncaughtExceptionHandler
您可以使用AsyncConfigurer
或<task:annotation-driven/>
XML 元素定义自定义。
7.4. 命名task
空间
TaskExecutor
从 3.0 版开始,Spring 包含一个用于配置和
TaskScheduler
实例的 XML 命名空间。它还提供了一种方便的方法来配置要使用触发器安排的任务。
7.4.1。“调度器”元素
以下元素创建ThreadPoolTaskScheduler
具有指定线程池大小的实例:
<task:scheduler id="scheduler" pool-size="10"/>
为属性提供的值id
用作池中线程名称的前缀。该scheduler
元素相对简单。如果不提供pool-size
属性,则默认线程池只有一个线程。调度程序没有其他配置选项。
7.4.2. executor
元素_
下面创建一个ThreadPoolTaskExecutor
实例:
<task:executor id="executor" pool-size="10"/>
与上一节中显示的调度程序一样,为属性提供的值id
用作池中线程名称的前缀。就池大小而言,executor
元素支持的配置选项比scheduler
元素多。一方面,a 的线程池ThreadPoolTaskExecutor
本身更具可配置性。执行程序的线程池可以有不同的核心值和最大大小,而不是只有一个大小。如果您提供单个值,则执行程序具有固定大小的线程池(核心和最大大小相同)。但是,executor
元素的pool-size
属性也接受min-max
. 以下示例设置 的最小值
5
和最大值25
:
<task:executor
id="executorWithPoolSizeRange"
pool-size="5-25"
queue-capacity="100"/>
在前面的配置中,queue-capacity
还提供了一个值。线程池的配置也要结合executor的队列容量来考虑。有关池大小和队列容量之间关系的完整描述,请参阅
ThreadPoolExecutor
. 主要思想是,当提交任务时,如果当前活动线程数小于核心大小,则执行程序首先尝试使用空闲线程。如果已达到核心大小,则将任务添加到队列中,只要其容量尚未达到。只有这样,如果队列的容量已经达到,执行程序才会创建一个超出核心大小的新线程。如果也达到了最大大小,则执行者拒绝该任务。
默认情况下,队列是无界的,但这很少是所需的配置,因为OutOfMemoryErrors
如果在所有池线程都忙时将足够的任务添加到该队列中,它可能会导致。此外,如果队列是无界的,则最大大小根本没有影响。由于执行程序总是在创建超出核心大小的新线程之前尝试队列,因此队列必须具有有限的容量才能使线程池超过核心大小(这就是为什么固定大小的池是使用时唯一合理的情况一个无界队列)。
如上所述,考虑一个任务被拒绝的情况。默认情况下,当任务被拒绝时,线程池执行器会抛出一个TaskRejectedException
. 但是,拒绝策略实际上是可配置的。使用默认拒绝策略时抛出异常,也就是AbortPolicy
实现。对于在高负载下可以跳过某些任务的应用程序,您可以改为配置DiscardPolicy
或DiscardOldestPolicy
。对于需要在重负载下限制提交任务的应用程序来说,另一个适用的选项是CallerRunsPolicy
. 该策略不会引发异常或丢弃任务,而是强制调用提交方法的线程自行运行任务。这个想法是这样的调用者在运行该任务时很忙,无法立即提交其他任务。因此,它提供了一种简单的方法来限制传入负载,同时保持线程池和队列的限制。通常,这允许执行程序“赶上”它正在处理的任务,从而释放队列、池或两者中的一些容量。
您可以从可用于元素rejection-policy
属性的值的枚举中选择这些选项中的任何一个。executor
以下示例显示了一个元素,该executor
元素具有许多用于指定各种行为的属性:
<task:executor
id="executorWithCallerRunsPolicy"
pool-size="5-25"
queue-capacity="100"
rejection-policy="CALLER_RUNS"/>
最后,该keep-alive
设置确定线程在停止之前可以保持空闲的时间限制(以秒为单位)。如果当前池中的线程数超过核心数,则在等待此时间量而不处理任务后,多余的线程将停止。时间值为零会导致多余的线程在执行任务后立即停止,而任务队列中没有剩余的后续工作。以下示例将该keep-alive
值设置为两分钟:
<task:executor
id="executorWithKeepAlive"
pool-size="5-25"
keep-alive="120"/>
7.4.3. “计划任务”元素
Spring 的任务命名空间最强大的特性是支持配置要在 Spring 应用程序上下文中调度的任务。这遵循类似于 Spring 中其他“方法调用者”的方法,例如 JMS 命名空间提供的用于配置消息驱动的 POJO 的方法。基本上,ref
属性可以指向任何 Spring 管理的对象,并且该method
属性提供要在该对象上调用的方法的名称。以下清单显示了一个简单的示例:
<task:scheduled-tasks scheduler="myScheduler">
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/>
</task:scheduled-tasks>
<task:scheduler id="myScheduler" pool-size="10"/>
调度程序由外部元素引用,每个单独的任务都包括其触发器元数据的配置。在前面的示例中,该元数据定义了一个具有固定延迟的周期性触发器,该延迟指示每个任务执行完成后要等待的毫秒数。另一个选项是
fixed-rate
,指示该方法应该多久运行一次,而不管之前的执行需要多长时间。此外,对于fixed-delay
和fixed-rate
任务,您可以指定一个“initial-delay”参数,指示在第一次执行该方法之前要等待的毫秒数。要获得更多控制,您可以改为提供一个cron
属性来提供cron 表达式。以下示例显示了这些其他选项:
<task:scheduled-tasks scheduler="myScheduler">
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/>
<task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/>
<task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/>
</task:scheduled-tasks>
<task:scheduler id="myScheduler" pool-size="10"/>
7.5。Cron 表达式
所有 Spring cron 表达式都必须符合相同的格式,无论您是在
@Scheduled
annotations、
task:scheduled-tasks
elements还是其他地方使用它们。一个格式良好的 cron 表达式,例如* * * * * *
,由六个空格分隔的时间和日期字段组成,每个字段都有自己的有效值范围:
┌────────────── 秒 (0-59) │ ┌───────────── 分钟 (0 - 59) │ │ ┌───────────── 小时 (0 - 23) │ │ │ ┌───────────── 一个月中的某天 (1 - 31) │ │ │ │ ┌───────────── 月(1-12)(或JAN-DEC) │ │ │ │ │ ┌───────────── 星期几(0 - 7) │ │ │ │ │ │ (0 或 7 为星期日,或 MON-SUN) │ │ │ │ │ │ * * * * * *
有一些适用的规则:
-
一个字段可以是一个星号 (
*
),它始终代表“first-last”。对于月份或星期几字段,?
可以使用问号 ( ) 代替星号。 -
逗号 (
,
) 用于分隔列表中的项目。 -
用连字符 ( ) 分隔的两个数字
-
表示一个数字范围。指定的范围包括在内。 -
跟随范围(或
*
)/
指定数字值在范围内的间隔。 -
英文名称也可用于 day-of-month 和 day-of-week 字段。使用特定日期或月份的前三个字母(不区分大小写)。
-
day-of-month 和 day-of-week 字段可以包含一个
L
字符,其含义不同-
在 day-of-month 字段中,
L
代表该月的最后一天。如果后跟一个负偏移量(即L-n
),则表示n
该月的倒数第二天。 -
在 day-of-week 字段中,
L
代表一周的最后一天。如果以数字或三字母名称 (dL
orDDDL
) 为前缀,则表示该月中一周的最后一天 (d
orDDD
) 。
-
-
day-of-month 字段可以是
nW
,表示离该月最近的工作日n
。如果n
落在星期六,这将产生它之前的星期五。如果n
是星期天,这会产生之后的星期一,如果n
是1
并且落在星期六,也会发生这种情况(即:1W
代表该月的第一个工作日)。 -
如果 day-of-month 字段为
LW
,则表示该月的最后一个工作日。 -
day-of-week 字段可以是
d#n
(orDDD#n
),它代表月中的n
星期几d
(orDDD
) 。
这里有些例子:
cron 表达式 | 意义 |
---|---|
|
每天每个小时的顶部 |
|
每十秒 |
|
每天 8 点、9 点和 10 点 |
|
每天早上 6:00 和晚上 7:00 |
|
每天 8:00、8:30、9:00、9:30、10:00 和 10:30 |
|
工作日朝九晚五 |
|
每个圣诞节的午夜 |
|
每月最后一天的午夜 |
|
每月倒数第三天午夜 |
|
每月最后一个星期五午夜 |
|
每月最后一个星期四的午夜 |
|
每月第一个工作日的午夜 |
|
每月最后一个工作日的午夜 |
|
每月第二个星期五午夜 |
|
每月第一个星期一的午夜 |
7.6. 使用 Quartz 调度器
Quartz 使用Trigger
, Job
, 和JobDetail
对象来实现各种作业的调度。关于 Quartz 背后的基本概念,请参见
https://www.quartz-scheduler.org/。为方便起见,Spring 提供了几个类来简化在基于 Spring 的应用程序中使用 Quartz。
7.6.1. 使用JobDetailFactoryBean
QuartzJobDetail
对象包含运行作业所需的所有信息。Spring 提供了一个
JobDetailFactoryBean
,它为 XML 配置目的提供了 bean 样式的属性。考虑以下示例:
<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="example.ExampleJob"/>
<property name="jobDataAsMap">
<map>
<entry key="timeout" value="5"/>
</map>
</property>
</bean>
作业详细信息配置包含运行作业所需的所有信息 ( ExampleJob
)。超时在作业数据映射中指定。作业数据映射可通过
JobExecutionContext
(在执行时传递给您)获得,但JobDetail
也从映射到作业实例属性的作业数据中获取其属性。因此,在以下示例中,ExampleJob
包含一个名为 的 bean 属性timeout
,并且JobDetail
它会自动应用:
package example;
public class ExampleJob extends QuartzJobBean {
private int timeout;
/**
* Setter called after the ExampleJob is instantiated
* with the value from the JobDetailFactoryBean (5)
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
}
protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
// do the actual work
}
}
作业数据地图中的所有其他属性也可供您使用。
通过使用name 和group 属性,您可以分别修改作业的名称和组。默认情况下,作业的名称与JobDetailFactoryBean (exampleJob 在上面的示例中)的 bean 名称匹配。
|
7.6.2. 使用MethodInvokingJobDetailFactoryBean
通常您只需要调用特定对象的方法。通过使用
MethodInvokingJobDetailFactoryBean
,您可以完全做到这一点,如以下示例所示:
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="exampleBusinessObject"/>
<property name="targetMethod" value="doIt"/>
</bean>
前面的示例导致在doIt
方法上调用该
exampleBusinessObject
方法,如以下示例所示:
public class ExampleBusinessObject {
// properties and collaborators
public void doIt() {
// do the actual work
}
}
<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>
通过使用MethodInvokingJobDetailFactoryBean
,您无需创建仅调用方法的单行作业。您只需要创建实际的业务对象并连接详细对象。
默认情况下,Quartz Jobs 是无状态的,从而导致作业相互干扰的可能性。如果您为相同的 指定两个触发器JobDetail
,则可能在第一个作业完成之前,第二个作业开始。如果
JobDetail
类实现了Stateful
接口,则不会发生这种情况。在第一个作业完成之前,第二个作业不会开始。要使由
MethodInvokingJobDetailFactoryBean
非并发产生的作业,请将concurrent
标志设置为
false
,如以下示例所示:
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="exampleBusinessObject"/>
<property name="targetMethod" value="doIt"/>
<property name="concurrent" value="false"/>
</bean>
默认情况下,作业将以并发方式运行。 |
7.6.3. 使用触发器连接作业和SchedulerFactoryBean
我们已经创建了工作细节和工作。我们还回顾了可让您调用特定对象的方法的便利 bean。当然,我们仍然需要自己安排作业。这是通过使用触发器和SchedulerFactoryBean
. Quartz 中提供了几个触发器,Spring 提供了两个FactoryBean
具有方便默认值的 Quartz 实现:CronTriggerFactoryBean
和
SimpleTriggerFactoryBean
.
需要安排触发器。Spring 提供了一个SchedulerFactoryBean
将触发器设置为属性的方法。SchedulerFactoryBean
使用这些触发器安排实际作业。
以下清单同时使用 aSimpleTriggerFactoryBean
和 a CronTriggerFactoryBean
:
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<!-- see the example of method invoking job above -->
<property name="jobDetail" ref="jobDetail"/>
<!-- 10 seconds -->
<property name="startDelay" value="10000"/>
<!-- repeat every 50 seconds -->
<property name="repeatInterval" value="50000"/>
</bean>
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="exampleJob"/>
<!-- run every morning at 6 AM -->
<property name="cronExpression" value="0 0 6 * * ?"/>
</bean>
前面的示例设置了两个触发器,一个每 50 秒运行一次,启动延迟为 10 秒,另一个在每天早上 6 点运行。为了完成所有事情,我们需要设置
SchedulerFactoryBean
,如以下示例所示:
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger"/>
<ref bean="simpleTrigger"/>
</list>
</property>
</bean>
更多属性可用SchedulerFactoryBean
,例如作业详细信息使用的日历、自定义 Quartz 的属性以及 Spring 提供的 JDBC 数据源。有关更多信息,请参阅SchedulerFactoryBean
javadoc。
SchedulerFactoryBean quartz.properties 还根据 Quartz 属性键识别类路径中的文件,与常规 Quartz 配置一样。请注意,许多
SchedulerFactoryBean 设置与属性文件中的常见 Quartz 设置交互;因此,不建议在两个级别上指定值。例如,如果您要依赖 Spring 提供的 DataSource,请不要设置“org.quartz.jobStore.class”属性,或者指定org.springframework.scheduling.quartz.LocalDataSourceJobStore 一个完全替代标准的变体org.quartz.impl.jdbcjobstore.JobStoreTX 。
|
8.缓存抽象
从 3.1 版开始,Spring 框架支持透明地向现有 Spring 应用程序添加缓存。与事务 支持类似,缓存抽象允许一致使用各种缓存解决方案,而对代码的影响最小。
在 Spring Framework 4.1 中,缓存抽象显着扩展,支持JSR-107 注释和更多自定义选项。
8.1。理解缓存抽象
在其核心,缓存抽象将缓存应用于 Java 方法,从而根据缓存中可用的信息减少执行次数。也就是说,每次调用目标方法时,抽象都会应用缓存行为来检查是否已经为给定参数调用了该方法。如果已调用,则返回缓存的结果,而无需调用实际方法。如果没有调用该方法,则调用该方法,并将结果缓存并返回给用户,以便下次调用该方法时,返回缓存的结果。这样,对于给定的一组参数,昂贵的方法(无论是 CPU 还是 IO 绑定)只能被调用一次,并且结果可以重用,而不必再次实际调用该方法。
此方法仅适用于保证为给定输入(或参数)返回相同输出(结果)的方法,无论调用多少次。 |
缓存抽象提供了其他与缓存相关的操作,例如更新缓存内容或删除一个或所有条目的能力。如果缓存处理在应用程序过程中可能发生变化的数据,这些将非常有用。
与 Spring Framework 中的其他服务一样,缓存服务是一种抽象(不是缓存实现),需要使用实际存储来存储缓存数据——也就是说,抽象使您不必编写缓存逻辑,但确实不提供实际的数据存储。这种抽象是由
org.springframework.cache.Cache
和org.springframework.cache.CacheManager
接口实现的。
Spring 提供了该抽象的一些实现java.util.concurrent.ConcurrentMap
:基于 JDK的缓存、Ehcache 2.x、Gemfire 缓存、Caffeine和符合 JSR-107 的缓存(例如 Ehcache 3.x)。有关插入其他缓存存储和提供程序的更多信息,请参阅插入不同的后端缓存。
缓存抽象对多线程和多进程环境没有特殊处理,因为这些功能由缓存实现处理。 |
如果您有一个多进程环境(即一个应用程序部署在多个节点上),您需要相应地配置您的缓存提供程序。根据您的用例,在多个节点上复制相同数据就足够了。但是,如果您在应用程序过程中更改数据,您可能需要启用其他传播机制。
缓存特定项目直接等效于通过程序缓存交互找到的典型 get-if-not-found-then-proceed-and-put-eventually 代码块。没有应用锁,并且多个线程可能会尝试同时加载相同的项目。这同样适用于驱逐。如果多个线程同时尝试更新或逐出数据,您可能会使用陈旧数据。某些缓存提供程序在该领域提供高级功能。有关更多详细信息,请参阅缓存提供程序的文档。
要使用缓存抽象,您需要注意两个方面:
-
缓存声明:标识需要缓存的方法及其策略。
-
缓存配置:存储数据和从中读取数据的后备缓存。
8.2. 基于声明性注解的缓存
对于缓存声明,Spring的缓存抽象提供了一组Java注解:
-
@Cacheable
:触发缓存填充。 -
@CacheEvict
: 触发缓存驱逐。 -
@CachePut
:在不干扰方法执行的情况下更新缓存。 -
@Caching
:重新组合多个缓存操作以应用于一个方法。 -
@CacheConfig
:在类级别共享一些常见的缓存相关设置。
8.2.1。@Cacheable
注释_
顾名思义,您可以使用@Cacheable
来划分可缓存的方法——也就是说,将结果存储在缓存中的方法,以便在后续调用(使用相同的参数)时返回缓存中的值而无需实际调用该方法。在最简单的形式中,注解声明需要与被注解的方法关联的缓存的名称,如以下示例所示:
@Cacheable("books")
public Book findBook(ISBN isbn) {...}
在前面的代码片段中,该findBook
方法与名为 的缓存相关联books
。每次调用该方法时,都会检查缓存以查看调用是否已经运行并且不必重复。虽然在大多数情况下,只声明了一个缓存,但注释允许指定多个名称,以便使用多个缓存。在这种情况下,在调用该方法之前检查每个缓存 - 如果至少命中一个缓存,则返回关联的值。
不包含该值的所有其他缓存也会更新,即使缓存的方法实际上并未被调用。 |
以下示例用于具有多个缓存@Cacheable
的方法:findBook
@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}
默认密钥生成
由于缓存本质上是键值存储,因此缓存方法的每次调用都需要转换为适合缓存访问的键。缓存抽象使用一个简单KeyGenerator
的基于以下算法:
-
如果没有给出参数,则返回
SimpleKey.EMPTY
。 -
如果只给出一个参数,则返回该实例。
-
如果给出了多个参数,则返回
SimpleKey
包含所有参数的 a。
这种方法适用于大多数用例,只要参数具有自然键并实现有效hashCode()
的和equals()
方法。如果不是这种情况,则需要更改策略。
要提供不同的默认密钥生成器,您需要实现该
org.springframework.cache.interceptor.KeyGenerator
接口。
随着 Spring 4.0 的发布,默认的密钥生成策略发生了变化。Spring 的早期版本使用密钥生成策略,对于多个密钥参数,只考虑 如果您想继续使用以前的密钥策略,您可以配置已弃用的
|
自定义密钥生成声明
由于缓存是通用的,因此目标方法很可能具有无法轻松映射到缓存结构顶部的各种签名。当目标方法有多个参数时,这往往会变得很明显,其中只有一些适合缓存(而其余的仅由方法逻辑使用)。考虑以下示例:
@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
乍一看,虽然这两个boolean
参数会影响图书的查找方式,但它们对缓存没有用处。此外,如果两者中只有一个重要而另一个不重要怎么办?
对于这种情况,@Cacheable
注释允许您指定如何通过其key
属性生成密钥。您可以使用SpEL选择感兴趣的参数(或其嵌套属性)、执行操作,甚至调用任意方法,而无需编写任何代码或实现任何接口。这是
默认生成器的推荐方法,因为随着代码库的增长,方法的签名往往会完全不同。虽然默认策略可能适用于某些方法,但它很少适用于所有方法。
以下示例使用各种 SpEL 声明(如果您不熟悉 SpEL,请帮自己一个忙,阅读Spring Expression Language):
@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
前面的片段显示了选择某个参数、其属性之一,甚至是任意(静态)方法是多么容易。
如果负责生成密钥的算法过于具体或者需要共享,您可以keyGenerator
在操作上定义自定义。为此,请指定KeyGenerator
要使用的 bean 实现的名称,如以下示例所示:
@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
key 和keyGenerator 参数是互斥的,指定两者的操作会导致异常
。 |
默认缓存分辨率
缓存抽象使用一个简单的方法CacheResolver
来检索在操作级别定义的缓存,方法是使用配置的
CacheManager
.
要提供不同的默认缓存解析器,您需要实现该
org.springframework.cache.interceptor.CacheResolver
接口。
自定义缓存分辨率
默认缓存分辨率非常适合使用单个CacheManager
缓存且没有复杂缓存分辨率要求的应用程序。
对于使用多个缓存管理器的应用程序,您可以将 设置
cacheManager
为用于每个操作,如以下示例所示:
@Cacheable(cacheNames="books", cacheManager="anotherCacheManager") (1)
public Book findBook(ISBN isbn) {...}
1 | 指定anotherCacheManager . |
您也可以CacheResolver
完全以类似于替换key generation的方式替换。每个缓存操作都请求解析,让实现真正解析缓存以根据运行时参数使用。以下示例显示如何指定 a CacheResolver
:
@Cacheable(cacheResolver="runtimeCacheResolver") (1)
public Book findBook(ISBN isbn) {...}
1 | 指定CacheResolver . |
从 Spring 4.1 开始,
|
同步缓存
在多线程环境中,某些操作可能会同时为同一个参数调用(通常在启动时)。默认情况下,缓存抽象不会锁定任何东西,并且可能会多次计算相同的值,从而破坏了缓存的目的。
对于那些特殊情况,您可以使用该sync
属性来指示底层缓存提供程序在计算值时锁定缓存条目。结果,只有一个线程忙于计算该值,而其他线程被阻塞,直到缓存中的条目被更新。以下示例显示了如何使用该sync
属性:
@Cacheable(cacheNames="foos", sync=true) (1)
public Foo executeExpensiveOperation(String id) {...}
1 | 使用sync 属性。 |
这是一个可选功能,您最喜欢的缓存库可能不支持它。CacheManager 核心框架提供的所有实现都支持它。有关更多详细信息,请参阅缓存提供程序的文档。
|
条件缓存
有时,一个方法可能并不适合一直缓存(例如,它可能取决于给定的参数)。缓存注释通过
condition
参数支持此类用例,该参数采用SpEL
计算为true
or的表达式false
。如果true
,则缓存该方法。如果没有,它的行为就好像该方法没有被缓存(也就是说,每次调用该方法,无论缓存中有什么值或使用什么参数)。例如,仅当参数name
的长度小于 32 时才缓存以下方法:
@Cacheable(cacheNames="book", condition="#name.length() < 32") (1)
public Book findBook(String name)
1 | 设置条件@Cacheable 。 |
除了condition
参数之外,您还可以使用unless
参数来否决将值添加到缓存中。与 不同的是condition
,unless
表达式是在方法被调用后计算的。为了扩展前面的示例,也许我们只想缓存平装书,如下例所示:
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback") (1)
public Book findBook(String name)
1 | 使用该unless 属性来阻止精装本。 |
缓存抽象支持java.util.Optional
返回类型。如果存在值Optional
,它将存储在关联的缓存中。如果某个值不存在,则将存储在关联的缓存中。总是引用业务实体,而不是支持的包装器,因此前面的示例可以重写如下:Optional
null
#result
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback")
public Optional<Book> findBook(String name)
请注意,#result
仍然是指Book
而不是Optional<Book>
。既然是这样
null
,我们就使用 SpEL 的安全导航算子。
可用的缓存 SpEL 评估上下文
每个SpEL
表达式都针对一个专用的context
. 除了内置参数之外,该框架还提供了专用的缓存相关元数据,例如参数名称。下表描述了可用于上下文的项目,以便您可以将它们用于键和条件计算:
姓名 | 地点 | 描述 | 例子 |
---|---|---|---|
|
根对象 |
被调用的方法的名称 |
|
|
根对象 |
被调用的方法 |
|
|
根对象 |
被调用的目标对象 |
|
|
根对象 |
被调用的目标的类 |
|
|
根对象 |
用于调用目标的参数(作为数组) |
|
|
根对象 |
运行当前方法的缓存集合 |
|
参数名称 |
评估上下文 |
任何方法参数的名称。如果名称不可用(可能是由于没有调试信息),则参数名称也可在 |
|
|
评估上下文 |
方法调用的结果(要缓存的值)。仅在 |
|
8.2.2. @CachePut
注释_
当需要在不干扰方法执行的情况下更新缓存时,可以使用@CachePut
注解。也就是说,始终调用该方法,并将其结果放入缓存中(根据@CachePut
选项)。@Cacheable
它支持和应该用于缓存填充而不是方法流优化的相同选项。以下示例使用@CachePut
注释:
@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
通常强烈建议不要在同一方法上
使用@CachePut 和注释,因为它们具有不同的行为。@Cacheable 后者通过使用缓存导致方法调用被跳过,而前者强制调用以运行缓存更新。这会导致意外行为,并且除了特定的极端情况(例如具有将它们彼此排除的条件的注释)外,应避免此类声明。另请注意,此类条件不应依赖于结果对象(即#result 变量),因为这些条件已预先验证以确认排除。
|
8.2.3. @CacheEvict
注释_
缓存抽象不仅允许缓存存储的填充,还允许逐出。此过程对于从缓存中删除陈旧或未使用的数据很有用。与 相反
@Cacheable
,@CacheEvict
划分了执行缓存驱逐的方法(即,充当从缓存中删除数据的触发器的方法)。与其兄弟相似,@CacheEvict
需要指定一个或多个受操作影响的缓存,允许自定义缓存和键解析或指定条件,并具有一个额外的参数 ( allEntries
),指示是否需要进行缓存范围的逐出执行而不仅仅是一个条目驱逐(基于密钥)。以下示例从books
缓存中逐出所有条目:
@CacheEvict(cacheNames="books", allEntries=true) (1)
public void loadBooks(InputStream batch)
1 | 使用该allEntries 属性从缓存中逐出所有条目。 |
当需要清除整个缓存区域时,此选项会派上用场。正如前面的示例所示,不是逐出每个条目(这将花费很长时间,因为它效率低下),而是在一次操作中删除所有条目。请注意,框架会忽略此场景中指定的任何键,因为它不适用(整个缓存被逐出,而不仅仅是一个条目)。
beforeInvocation
您还可以使用属性指示驱逐是应该在(默认)之后还是在调用方法之前发生。前者提供与其余注释相同的语义:一旦方法成功完成,就会运行缓存上的操作(在本例中为驱逐)。如果方法没有运行(因为它可能被缓存)或抛出异常,则不会发生驱逐。后者 ( beforeInvocation=true
) 导致驱逐总是在调用方法之前发生。这在驱逐不需要与方法结果相关联的情况下很有用。
请注意,void
方法可以与@CacheEvict
- 一起使用,因为方法充当触发器,返回值将被忽略(因为它们不与缓存交互)。这不是@Cacheable
将数据添加到缓存或更新缓存中的数据的情况,因此需要结果。
8.2.4. @Caching
注释_
有时,需要指定多个相同类型的注解(例如@CacheEvict
or
@CachePut
)——例如,因为不同缓存之间的条件或键表达式不同。允许在同一方法上使用@Caching
多个嵌套
@Cacheable
的@CachePut
、 和注释。@CacheEvict
以下示例使用两个@CacheEvict
注释:
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)
8.2.5。@CacheConfig
注释_
到目前为止,我们已经看到缓存操作提供了许多自定义选项,并且您可以为每个操作设置这些选项。但是,如果某些自定义选项适用于类的所有操作,则配置起来可能会很繁琐。例如,指定用于类的每个缓存操作的缓存名称可以由单个类级别定义替换。这就是@CacheConfig
发挥作用的地方。以下示例用于@CacheConfig
设置缓存的名称:
@CacheConfig("books") (1)
public class BookRepositoryImpl implements BookRepository {
@Cacheable
public Book findBook(ISBN isbn) {...}
}
1 | 用于@CacheConfig 设置缓存的名称。 |
@CacheConfig
是一个类级注释,允许共享缓存名称、自定义KeyGenerator
、自定义CacheManager
和自定义CacheResolver
。将此注释放在类上不会打开任何缓存操作。
操作级自定义始终覆盖在 上设置的自定义@CacheConfig
。因此,这为每个缓存操作提供了三个级别的自定义:
-
全局配置,可用于
CacheManager
,KeyGenerator
。 -
在课堂级别,使用
@CacheConfig
. -
在操作层面。
8.2.6。启用缓存注释
重要的是要注意,即使声明缓存注释不会自动触发它们的动作——就像 Spring 中的许多事情一样,该功能必须以声明方式启用(这意味着如果你怀疑缓存是罪魁祸首,你可以通过删除来禁用它只有一个配置行,而不是代码中的所有注释)。
要启用缓存注释,请将注释添加@EnableCaching
到您的
@Configuration
类之一:
@Configuration
@EnableCaching
public class AppConfig {
}
或者,对于 XML 配置,您可以使用以下cache:annotation-driven
元素:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache.xsd">
<cache:annotation-driven/>
</beans>
cache:annotation-driven
元素和注释都@EnableCaching
允许您指定各种选项,这些选项会影响通过 AOP 将缓存行为添加到应用程序的方式。该配置有意与
@Transactional
.
处理缓存注释的默认建议模式是proxy ,它只允许通过代理拦截调用。同一类中的本地调用不能以这种方式被拦截。对于更高级的拦截模式,请考虑切换到aspectj 结合编译时或加载时编织的模式。
|
有关实现所需的高级自定义(使用 Java 配置)的更多详细信息CachingConfigurer ,请参阅
javadoc。
|
XML 属性 | 注释属性 | 默认 | 描述 |
---|---|---|---|
|
不适用(请参阅 |
|
要使用的缓存管理器的名称。使用此缓存管理器(或如果未设置) |
|
不适用(请参阅 |
A |
用于解析后备缓存的 CacheResolver 的 bean 名称。此属性不是必需的,仅需要指定为“缓存管理器”属性的替代项。 |
|
不适用(请参阅 |
|
要使用的自定义密钥生成器的名称。 |
|
不适用(请参阅 |
|
要使用的自定义缓存错误处理程序的名称。默认情况下,在与缓存相关的操作期间抛出的任何异常都会在客户端被抛出。 |
|
|
|
默认模式 |
|
|
|
仅适用于代理模式。 |
|
|
Ordered.LOWEST_PRECEDENCE |
定义应用到用
|
<cache:annotation-driven/> @Cacheable/@CachePut/@CacheEvict/@Caching
仅在定义它的同一应用程序上下文中查找bean。这意味着,如果您放入<cache:annotation-driven/> a WebApplicationContext for a
DispatcherServlet ,它只会检查您的控制器中的 bean,而不是您的服务。有关详细信息,请参阅MVC 部分。
|
Spring 建议您只使用注解来注解具体类(和具体类的方法)@Cache* ,而不是注解接口。您当然可以在接口(或接口方法)上放置@Cache* 注释,但这仅在您使用代理模式(mode="proxy" )时才有效。如果您使用基于编织的方面 ( mode="aspectj" ),则编织基础结构在接口级声明上无法识别缓存设置。
|
在代理模式(默认)下,只拦截通过代理传入的外部方法调用。这意味着自调用(实际上,目标对象中的一个方法调用目标对象的另一个方法)不会在运行时导致实际缓存,即使调用的方法标记为@Cacheable . 在这种情况下考虑使用该aspectj 模式。此外,代理必须完全初始化以提供预期的行为,因此您不应在初始化代码(即@PostConstruct )中依赖此功能。
|
8.2.7. 使用自定义注解
缓存抽象允许您使用自己的注释来识别触发缓存填充或驱逐的方法。这作为模板机制非常方便,因为它消除了重复缓存注释声明的需要,如果指定了键或条件,或者如果org.springframework
您的代码库中不允许外部导入 ( ),这将特别有用。与其他原型注释类似,您可以使用@Cacheable
、@CachePut
、@CacheEvict
和@CacheConfig
作为
元注释(即可以注释其他注释的注释)。在下面的例子中,我们
@Cacheable
用我们自己的自定义注解替换了一个通用声明:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Cacheable(cacheNames="books", key="#isbn")
public @interface SlowService {
}
在前面的例子中,我们定义了自己的SlowService
注解,它本身用@Cacheable
. 现在我们可以替换以下代码:
@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
以下示例显示了我们可以替换前面代码的自定义注解:
@SlowService
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
即使@SlowService
不是 Spring 注释,容器也会在运行时自动获取其声明并理解其含义。请注意,
如前所述,需要启用注释驱动的行为。
8.3. JCache (JSR-107) 注释
从 4.1 版本开始,Spring 的缓存抽象完全支持 JCache 标准 (JSR-107) 注释:@CacheResult
、@CachePut
、@CacheRemove
和@CacheRemoveAll
以及@CacheDefaults
、@CacheKey
和@CacheValue
伴随。即使不将缓存存储迁移到 JSR-107,您也可以使用这些注释。内部实现使用 Spring 的缓存抽象,并提供
符合规范的默认CacheResolver
和实现。KeyGenerator
换句话说,如果您已经在使用 Spring 的缓存抽象,您可以切换到这些标准注解,而无需更改缓存存储(或配置,就此而言)。
8.3.1. 功能摘要
对于熟悉 Spring 的缓存注解的人来说,下表描述了 Spring 注解与其对应的 JSR-107 注解的主要区别:
Spring | JSR-107 | 评论 |
---|---|---|
|
|
相当相似。 |
|
|
当 Spring 使用方法调用的结果更新缓存时,JCache 要求将其作为带有注释的参数传递给它 |
|
|
相当相似。 |
|
|
见 |
|
|
让您以类似的方式配置相同的概念。 |
JCache 的概念javax.cache.annotation.CacheResolver
与 Spring 的CacheResolver
接口相同,只是 JCache 仅支持单个缓存。默认情况下,一个简单的实现会根据注解上声明的名称检索要使用的缓存。需要注意的是,如果注解上没有指定缓存名称,则会自动生成一个默认值。@CacheResult#cacheName()
有关更多信息,请参阅的 javadoc 。
CacheResolver
实例由 a 检索CacheResolverFactory
。可以为每个缓存操作自定义工厂,如以下示例所示:
@CacheResult(cacheNames="books", cacheResolverFactory=MyCacheResolverFactory.class) (1)
public Book findBook(ISBN isbn)
1 | 为此操作定制工厂。 |
对于所有引用的类,Spring 尝试定位具有给定类型的 bean。如果存在多个匹配项,则会创建一个新实例并可以使用常规 bean 生命周期回调,例如依赖注入。 |
密钥由 a 生成,javax.cache.annotation.CacheKeyGenerator
其用途与 Spring 的KeyGenerator
. 默认情况下,所有方法参数都被考虑在内,除非至少有一个参数用 注释@CacheKey
。这类似于 Spring 的自定义密钥生成声明。例如,以下是相同的操作,一个使用 Spring 的抽象,另一个使用 JCache:
@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@CacheResult(cacheName="books")
public Book findBook(@CacheKey ISBN isbn, boolean checkWarehouse, boolean includeUsed)
您还可以指定CacheKeyResolver
on 操作,类似于指定CacheResolverFactory
.
JCache 可以管理带注释的方法抛出的异常。这可以防止更新缓存,但它也可以将异常缓存为失败的指示符,而不是再次调用该方法。假设InvalidIsbnNotFoundException
如果 ISBN 的结构无效则抛出该错误。这是一个永久性的失败(没有书可以用这样的参数检索)。以下缓存异常,以便进一步调用相同的、无效的 ISBN 直接抛出缓存的异常,而不是再次调用该方法:
@CacheResult(cacheName="books", exceptionCacheName="failures"
cachedExceptions = InvalidIsbnNotFoundException.class)
public Book findBook(ISBN isbn)
8.4. 声明式基于 XML 的缓存
如果注释不是一个选项(可能是由于无法访问源代码或没有外部代码),您可以使用 XML 进行声明式缓存。因此,您可以在外部指定目标方法和缓存指令,而不是注释缓存方法(类似于声明性事务管理 建议)。上一节中的示例可以翻译为以下示例:
<!-- the service we want to make cacheable -->
<bean id="bookService" class="x.y.service.DefaultBookService"/>
<!-- cache definitions -->
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
<cache:caching cache="books">
<cache:cacheable method="findBook" key="#isbn"/>
<cache:cache-evict method="loadBooks" all-entries="true"/>
</cache:caching>
</cache:advice>
<!-- apply the cacheable behavior to all BookService interfaces -->
<aop:config>
<aop:advisor advice-ref="cacheAdvice" pointcut="execution(* x.y.BookService.*(..))"/>
</aop:config>
<!-- cache manager definition omitted -->
在前面的配置中,它bookService
是可缓存的。要应用的缓存语义被封装在cache:advice
定义中,这导致findBooks
用于将数据放入缓存的loadBooks
方法和用于驱逐数据的方法。这两个定义都对books
缓存起作用。
该aop:config
定义通过使用 AspectJ 切入点表达式将缓存建议应用到程序中的适当点(更多信息可在
使用 Spring 的面向方面编程中获得)。在前面的示例中,BookService
考虑了来自 的所有方法,并将缓存建议应用于它们。
声明式 XML 缓存支持所有基于注释的模型,因此在两者之间移动应该相当容易。此外,两者都可以在同一个应用程序中使用。基于 XML 的方法不涉及目标代码。但是,它本质上更冗长。在处理具有以缓存为目标的重载方法的类时,识别正确的方法确实需要额外的努力,因为method
论据不是一个好的鉴别器。在这些情况下,您可以使用 AspectJ 切入点来挑选目标方法并应用适当的缓存功能。然而,通过 XML,更容易应用包或组或接口范围的缓存(同样,由于 AspectJ 切入点)和创建类似模板的定义(正如我们在前面的示例中所做的那样,通过cache:definitions
cache
属性定义目标缓存)。
8.5。配置缓存存储
缓存抽象提供了几个存储集成选项。要使用它们,您需要声明一个适当的(控制和管理
实例并且可用于检索这些实例以进行存储CacheManager
的实体)。Cache
8.5.1。基于JDKConcurrentMap
的缓存
基于 JDK 的Cache
实现位于
org.springframework.cache.concurrent
包下。它使您可以ConcurrentHashMap
用作后备Cache
存储。以下示例显示了如何配置两个缓存:
<!-- simple cache manager -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="books"/>
</set>
</property>
</bean>
前面的代码片段使用为名为和的两个嵌套实例SimpleCacheManager
创建一个。请注意,直接为每个缓存配置名称。CacheManager
ConcurrentMapCache
default
books
由于缓存是由应用程序创建的,因此它绑定了它的生命周期,使其适用于基本用例、测试或简单的应用程序。缓存可以很好地扩展并且速度非常快,但它不提供任何管理、持久性功能或驱逐合同。
8.5.2. 基于 Ehcache 的缓存
Ehcache 3.x 完全符合 JSR-107,不需要专门的支持。 |
Ehcache 2.x 实现位于org.springframework.cache.ehcache
包中。同样,要使用它,您需要声明适当的CacheManager
. 以下示例显示了如何执行此操作:
<bean id="cacheManager"
class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/>
<!-- EhCache library setup -->
<bean id="ehcache"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="ehcache.xml"/>
此设置引导 Spring IoC 内的 ehcache 库(通过ehcache
bean),然后将其连接到专用CacheManager
实现中。请注意,整个 Ehcache 特定配置是从ehcache.xml
.
8.5.3. 咖啡因缓存
Caffeine 是对 Guava 缓存的 Java 8 重写,其实现位于
org.springframework.cache.caffeine
包中,并提供对 Caffeine 的多个功能的访问。
以下示例配置了一个CacheManager
按需创建缓存的:
<bean id="cacheManager"
class="org.springframework.cache.caffeine.CaffeineCacheManager"/>
您还可以提供要显式使用的缓存。在这种情况下,只有那些由经理提供。以下示例显示了如何执行此操作:
<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
<property name="cacheNames">
<set>
<value>default</value>
<value>books</value>
</set>
</property>
</bean>
CaffeineCacheManager
还支持自定义Caffeine
和CacheLoader
. 有关这些内容的更多信息,请参阅咖啡因文档
。
8.5.4. 基于 GemFire 的缓存
GemFire 是一个面向内存、以磁盘为支持、弹性可扩展、持续可用、活动(具有内置基于模式的订阅通知)、全局复制的数据库,并提供功能齐全的边缘缓存。有关如何将 GemFire 用作CacheManager
(以及更多)的更多信息,请参阅
Spring Data GemFire 参考文档。
8.5.5。JSR-107 缓存
Spring 的缓存抽象也可以使用符合 JSR-107 的缓存。JCache 实现位于org.springframework.cache.jcache
包中。
同样,要使用它,您需要声明适当的CacheManager
. 以下示例显示了如何执行此操作:
<bean id="cacheManager"
class="org.springframework.cache.jcache.JCacheCacheManager"
p:cache-manager-ref="jCacheManager"/>
<!-- JSR-107 cache manager setup -->
<bean id="jCacheManager" .../>
8.5.6。在没有后备存储的情况下处理缓存
有时,在切换环境或进行测试时,您可能有缓存声明而没有配置实际的后备缓存。由于这是无效配置,因此在运行时会引发异常,因为缓存基础架构无法找到合适的存储。在这种情况下,您可以连接一个不执行缓存的简单虚拟缓存,而不是删除缓存声明(这可能很乏味)——也就是说,它强制每次调用缓存的方法。以下示例显示了如何执行此操作:
<bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
<property name="cacheManagers">
<list>
<ref bean="jdkCache"/>
<ref bean="gemfireCache"/>
</list>
</property>
<property name="fallbackToNoOpCache" value="true"/>
</bean>
前面CompositeCacheManager
的链接多个CacheManager
实例,并通过fallbackToNoOpCache
标志为所有未由配置的缓存管理器处理的定义添加一个无操作缓存。也就是说,在jdkCache
or中未找到的每个缓存定义gemfireCache
(在示例前面配置)都由 no-op 缓存处理,该缓存不存储任何信息,导致每次都调用目标方法。
8.6. 插入不同的后端缓存
显然,有很多缓存产品可以用作后备存储。对于那些不支持 JSR-107 的,您需要提供一个CacheManager
和一个
Cache
实现。这听起来可能比实际更难,因为在实践中,类往往是简单的适配器,将缓存抽象框架映射到存储 API 之上,就像ehcache
类一样。大多数CacheManager
类都可以使用
org.springframework.cache.support
包中的类(例如AbstractCacheManager
它负责样板代码,只留下实际映射来完成)。
9. 附录
9.1。XML 模式
附录的这一部分列出了与集成技术相关的 XML 模式。
9.1.1。jee
架构_
这些jee
元素处理与 Java EE(Java 企业版)配置相关的问题,例如查找 JNDI 对象和定义 EJB 引用。
要使用jee
模式中的元素,您需要在 Spring XML 配置文件的顶部有以下序言。以下代码段中的文本引用了正确的架构,以便jee
您可以使用命名空间中的元素:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jee https://www.springframework.org/schema/jee/spring-jee.xsd">
<!-- bean definitions here -->
</beans>
<jee:jndi-lookup/>(简单)
下面的例子展示了如何使用 JNDI 来查找没有jee
模式的数据源:
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/MyDataSource"/>
</bean>
<bean id="userDao" class="com.foo.JdbcUserDao">
<!-- Spring will do the cast automatically (as usual) -->
<property name="dataSource" ref="dataSource"/>
</bean>
以下示例显示了如何使用 JNDI 查找具有jee
模式的数据源:
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/MyDataSource"/>
<bean id="userDao" class="com.foo.JdbcUserDao">
<!-- Spring will do the cast automatically (as usual) -->
<property name="dataSource" ref="dataSource"/>
</bean>
<jee:jndi-lookup/>
(使用单一 JNDI 环境设置)
下面的例子展示了如何使用 JNDI 来查找没有 : 的环境变量
jee
:
<bean id="simple" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/MyDataSource"/>
<property name="jndiEnvironment">
<props>
<prop key="ping">pong</prop>
</props>
</property>
</bean>
以下示例展示了如何使用 JNDI 来查找环境变量jee
:
<jee:jndi-lookup id="simple" jndi-name="jdbc/MyDataSource">
<jee:environment>ping=pong</jee:environment>
</jee:jndi-lookup>
<jee:jndi-lookup/>
(具有多个 JNDI 环境设置)
以下示例显示了如何使用 JNDI 查找多个环境变量,而无需jee
:
<bean id="simple" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/MyDataSource"/>
<property name="jndiEnvironment">
<props>
<prop key="sing">song</prop>
<prop key="ping">pong</prop>
</props>
</property>
</bean>
下面的例子展示了如何使用 JNDI 来查找多个环境变量
jee
:
<jee:jndi-lookup id="simple" jndi-name="jdbc/MyDataSource">
<!-- newline-separated, key-value pairs for the environment (standard Properties format) -->
<jee:environment>
sing=song
ping=pong
</jee:environment>
</jee:jndi-lookup>
<jee:jndi-lookup/>
(复杂的)
以下示例显示了如何使用 JNDI 来查找数据源和许多不同的属性,而无需jee
:
<bean id="simple" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/MyDataSource"/>
<property name="cache" value="true"/>
<property name="resourceRef" value="true"/>
<property name="lookupOnStartup" value="false"/>
<property name="expectedType" value="com.myapp.DefaultThing"/>
<property name="proxyInterface" value="com.myapp.Thing"/>
</bean>
以下示例显示了如何使用 JNDI 来查找数据源和许多不同的属性jee
:
<jee:jndi-lookup id="simple"
jndi-name="jdbc/MyDataSource"
cache="true"
resource-ref="true"
lookup-on-startup="false"
expected-type="com.myapp.DefaultThing"
proxy-interface="com.myapp.Thing"/>
<jee:local-slsb/>
(简单的)
该<jee:local-slsb/>
元素配置对本地 EJB 无状态会话 Bean 的引用。
以下示例显示了如何配置对本地 EJB 无状态会话 Bean 的引用,而不使用jee
:
<bean id="simple"
class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
<property name="jndiName" value="ejb/RentalServiceBean"/>
<property name="businessInterface" value="com.foo.service.RentalService"/>
</bean>
以下示例显示如何配置对本地 EJB 无状态会话 Bean 的引用jee
:
<jee:local-slsb id="simpleSlsb" jndi-name="ejb/RentalServiceBean"
business-interface="com.foo.service.RentalService"/>
<jee:local-slsb/>
(复杂的)
该<jee:local-slsb/>
元素配置对本地 EJB 无状态会话 Bean 的引用。
以下示例显示了如何配置对本地 EJB 无状态会话 Bean 的引用和许多不带jee
:
<bean id="complexLocalEjb"
class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
<property name="jndiName" value="ejb/RentalServiceBean"/>
<property name="businessInterface" value="com.example.service.RentalService"/>
<property name="cacheHome" value="true"/>
<property name="lookupHomeOnStartup" value="true"/>
<property name="resourceRef" value="true"/>
</bean>
以下示例显示了如何配置对本地 EJB 无状态会话 Bean 的引用和许多属性jee
:
<jee:local-slsb id="complexLocalEjb"
jndi-name="ejb/RentalServiceBean"
business-interface="com.foo.service.RentalService"
cache-home="true"
lookup-home-on-startup="true"
resource-ref="true">
<jee:remote-slsb/>
该<jee:remote-slsb/>
元素配置对remote
EJB 无状态会话 Bean 的引用。
以下示例显示了如何配置对远程 EJB 无状态会话 Bean 的引用,而不使用jee
:
<bean id="complexRemoteEjb"
class="org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean">
<property name="jndiName" value="ejb/MyRemoteBean"/>
<property name="businessInterface" value="com.foo.service.RentalService"/>
<property name="cacheHome" value="true"/>
<property name="lookupHomeOnStartup" value="true"/>
<property name="resourceRef" value="true"/>
<property name="homeInterface" value="com.foo.service.RentalService"/>
<property name="refreshHomeOnConnectFailure" value="true"/>
</bean>
以下示例显示如何配置对远程 EJB 无状态会话 Bean 的引用jee
:
<jee:remote-slsb id="complexRemoteEjb"
jndi-name="ejb/MyRemoteBean"
business-interface="com.foo.service.RentalService"
cache-home="true"
lookup-home-on-startup="true"
resource-ref="true"
home-interface="com.foo.service.RentalService"
refresh-home-on-connect-failure="true">
9.1.2. jms
架构_
这些jms
元素处理配置与 JMS 相关的 bean,例如 Spring 的
Message Listener Containers。这些元素在JMS 章节中标题为JMS 命名空间支持的部分中有详细说明。jms
有关此支持和元素本身的完整详细信息,请参阅该章。
为了完整起见,要使用jms
模式中的元素,您需要在 Spring XML 配置文件的顶部有以下序言。以下代码段中的文本引用了正确的架构,以便jms
您可以使用命名空间中的元素:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms https://www.springframework.org/schema/jms/spring-jms.xsd">
<!-- bean definitions here -->
</beans>
9.1.3. 使用<context:mbean-export/>
配置基于注释的 MBean 导出中详细介绍了此元素 。
9.1.4。cache
架构_
您可以使用这些cache
元素来启用对 Spring 的@CacheEvict
、@CachePut
和@Caching
注释的支持。它还支持声明性的基于 XML 的缓存。有关详细信息,请参阅
启用缓存注释和
声明性基于 XML 的缓存。
要使用cache
模式中的元素,您需要在 Spring XML 配置文件的顶部有以下序言。以下代码段中的文本引用了正确的架构,以便cache
您可以使用命名空间中的元素:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache.xsd">
<!-- bean definitions here -->
</beans>