0%

SpringFramework官方文档翻译-集成

集成

远程和Web服务

Spring features integration classes 用于远程支持各种技术。远程支持简化了 remote-enabled 服务的开发,由您的常规(Spring)POJO 实现。目前,Spring 支持以下远程技术:

  • 远程方法调用(RMI):通过使用RmiProxyFactoryBeanRmiServiceExporter,Spring 支持传统的 RMI(带有java.rmi.Remote接口和java.rmi.RemoteException)和通过 RMI 调用程序(带有任何 Java 接口)的透明远程处理。
  • Spring 的 HTTP 调用程序:Spring 提供了一种特殊的远程处理策略,允许通过 HTTP 进行 Java 序列化,支持任何 Java 接口(如 RMI 调用程序所做的那样)。相应的支持 classes 是HttpInvokerProxyFactoryBeanHttpInvokerServiceExporter
  • Hessian:通过使用 Spring 的HessianProxyFactoryBeanHessianServiceExporter,您可以通过 Caucho 提供的轻量级二进制 HTTP-based 协议透明地公开您的服务。
  • JAX-WS:Spring 通过 JAX-WS(JAX-RPC 的后继,如 Java EE 5 和 Java 6 中引入)为 web services 提供远程支持。
  • JMS:通过JmsInvokerServiceExporterJmsInvokerProxyFactoryBean classes 支持使用 JMS 作为底层协议进行远程处理。
  • AMQP:Spring AMQP 项目支持使用 AMQP 作为基础协议进行远程处理。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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 将服务公开给 remote client,然后再谈谈使用 RMI 的缺点。然后继续使用 Hessian 作为协议的 example。

使用RMI公开服务

通过使用 Spring 对 RMI 的支持,您可以透明地通过 RMI 基础结构公开您的服务。完成此设置后,除了没有标准支持 security context 传播或 remote transaction 传播之外,您基本上都具有类似于 remote EJB 的 configuration。当您使用 RMI 调用程序时,Spring 会为此类附加调用 context 提供挂钩,因此您可以插入安全框架或自定义安全凭证。

使用RmiServiceExporter导出服务

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

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

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

接下来,我们必须使用RmiServiceExporter来公开我们的服务。以下 example 显示了如何执行此操作:

1
2
3
4
5
6
7
8
<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>

在前面的 example 中,我们覆盖了 RMI 注册表的 port。通常,您的 application 服务器还维护一个 RMI 注册表,明智的做法是不干扰那个。此外,service name 用于绑定服务。因此,在前面的 example 中,服务绑定在'rmi://HOST:1199/AccountService'。我们稍后使用此 URL 链接到 client 端的服务。

servicePort property 已被省略(默认为 0)。这意味着匿名 port 用于与服务通信。

在Client中链接服务

我们的 client 是一个简单的 object,它使用AccountService来管理帐户,如下面的示例所示:

1
2
3
4
5
6
7
8
9
10
11
public class SimpleObject {

private AccountService accountService;

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

// additional methods using the accountService

}

要链接 client 上的服务,我们创建一个单独的 Spring 容器,以包含以下简单的 object 和连接 configuration 位的服务:

1
2
3
4
5
6
7
8
<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>

这就是我们在 client 上支持 remote 帐户服务所需要做的全部工作。 Spring 透明地创建一个调用者并通过RmiServiceExporter远程启用帐户服务。在 client,我们使用RmiProxyFactoryBean链接它。

使用Hessian通过HTTP远程调用服务

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

为Hessian连接DispatcherServlet

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

1
2
3
4
5
6
7
8
9
10
<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原则,您可能知道现在必须在WEB-INF目录中创建一个名为remoting-servlet.xml的 Spring 容器配置资源(在 servlet 的 name 之后)。 application context 将在下一节中使用。

或者,考虑使用 Spring 更简单的HttpRequestHandlerServlet。这样做可以将 remote 导出器定义嵌入到根 application context 中(默认情况下,在WEB-INF/applicationContext.xml中),单个 servlet 定义指向特定的导出器 beans。在这种情况下,每个 servlet name 都需要 match 其目标导出器的 bean name。

使用HessianServiceExporter公开你的Beans

在新创建的名为remoting-servlet.xml的 application context 中,我们创建一个HessianServiceExporter来 export 我们的服务,如下面的 example 所示:

1
2
3
4
5
6
7
8
<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>

现在我们已准备好在 client 中链接服务。没有指定显式的处理程序映射(将 map 请求 URL 映射到服务上),因此我们使用了BeanNameUrlHandlerMapping。因此,服务在包含DispatcherServlet实例的映射(如前面定义)中通过 bean name 指示的 URL 导出:http://HOST:8080/remoting/AccountService

或者,您可以在根 application context 中创建HessianServiceExporter(对于 example,在WEB-INF/applicationContext.xml中),如下面的 example 所示:

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

在后一种情况下,您应该在web.xml中为此导出器定义相应的 servlet,并使用相同的最终结果:导出器将映射到/remoting/AccountService处的请求路径。请注意,servlet name 需要 match 目标导出器的 bean name。以下 example 显示了如何执行此操作:

1
2
3
4
5
6
7
8
9
<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>

在Client上链接服务

通过使用HessianProxyFactoryBean,我们可以在 client 中链接服务。同样的原则适用于 RMI example。我们创建一个单独的 bean 工厂或 application context 并通过使用AccountService来管理帐户,提及以下 beans,如下面的 example 所示:

1
2
3
4
5
6
7
8
<bean class="example.SimpleObject">
<property name="accountService" ref="accountService"/>
</bean>

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

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

Hessian 的一个优点是我们可以轻松应用 HTTP 基本身份验证,因为这两个协议都是 HTTP-based。对于 example,可以通过使用web.xml security features 应用正常的 HTTP 服务器安全性机制。通常,您无需在此处使用 per-user 安全凭证。相反,您可以使用在HessianProxyFactoryBean level(类似于 JDBC DataSource)中定义的共享凭据,如下面的 example 所示:

1
2
3
4
5
6
7
8
<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并设置了一个拦截器,只允许管理员和 operators 调用 application context 中提到的 beans。

前面的 example 没有显示灵活的安全基础结构。有关安全性的更多选项,请查看处的 Spring Security 项目。

使用HTTPInvokers公开服务

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

在引擎盖下,Spring 使用 JDK 提供的标准工具或 Apache HttpComponents来执行 HTTP calls。如果您需要更高级和 easier-to-use 功能,请使用后者。有关更多信息,请参见

请注意由于不安全的 Java 反序列化导致的漏洞:在反序列化 step 期间,操作的输入流可能导致服务器上不需要的 code 执行。因此,不要将 HTTP 调用者 endpoints 暴露给不受信任的 clients。相反,只在您自己的服务之间公开它们。通常,我们强烈建议您使用任何其他消息格式(例如 JSON)。

如果您担心由于 Java 序列化导致的安全漏洞,请考虑核心 JVM level 上的 general-purpose 序列化过滤机制,该机制最初是为 JDK 9 开发的,但同时又向后移植到 JDK 8,7 和 6。见https://blogs.oracle.com/java-platform-group/entry/incoming_filter_serialization_data_a和[http://openjdk.java.net/jeps/290](https://openjdk.java.net/jeps/290)。

公开服务Object

为服务 object 设置 HTTP 调用程序基础结构非常类似于使用 Hessian 执行相同操作的方式。由于 Hessian 支持提供HessianServiceExporter,Spring 的 HttpInvoker 支持提供org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter

要在 Spring Web MVC DispatcherServlet中公开AccountService(前面提到过),需要在调度程序的 application context 中使用以下 configuration,如下面的 example 所示:

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

这样的导出器定义通过DispatcherServlet实例的标准映射工具公开,如关于黑森州的部分中所述。

或者,您可以在根 application context 中创建HttpInvokerServiceExporter(对于 example,在'WEB-INF/applicationContext.xml'中),如下面的 example 所示:

1
2
3
4
<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 name 与目标导出器的 bean name 匹配,如下面的 example 所示:

1
2
3
4
5
6
7
8
9
<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>

在Client中链接服务

同样,来自 client 的服务链接非常类似于使用 Hessian 时的方式。通过使用代理,Spring 可以将 calls 转换为指向导出服务的 URL 的 HTTP POST 请求。以下 example 显示了如何配置此安排:

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

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

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

网页服务

Spring 提供对标准 Java web services API 的完全支持:

  • 使用 JAX-WS 公开 web services
  • 使用 JAX-WS 访问 web services

除了 Spring Core 中 JAX-WS 的库存支持外,Spring 产品组合还 features Spring Web Services,这是 contract-first,document-driven web services 的解决方案 - 强烈建议用于 building modern,future-proof web services。

使用JAX-WS公开基于Servlet的WebServices

Spring 为 JAX-WS servlet 端点 implementations 提供了方便的 base class:SpringBeanAutowiringSupport。为了公开我们的AccountService,我们扩展 Spring 的SpringBeanAutowiringSupport class 并在这里实现我们的业务逻辑,通常将调用委托给业务层。我们使用 Spring 的@Autowired annotation 来表达对 Spring-managed beans 的依赖。以下 example 显示了扩展SpringBeanAutowiringSupport的 class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* 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 5 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 context 相同的 web application 中运行,以允许访问 Spring 的工具。默认情况下,在 Java EE 5 环境中使用 JAX-WS servlet 端点部署的标准 contract 就是这种情况。有关详细信息,请参阅各种 Java EE 5 web service 教程。

使用JAX-WS导出独立的WebServices

Oracle JDK 附带的 built-in JAX-WS 提供程序通过使用 JDK 中包含的 built-in HTTP 服务器支持 web services 的曝光。 Spring 的SimpleJaxWsServiceExporter检测 Spring application context 中的所有@WebService -annotated beans 并通过默认的 JAX-WS 服务器(JDK HTTP 服务器)导出它们。

在这种情况下,端点实例被定义和管理为 Spring beans 本身。它们在 JAX-WS 引擎中注册,但它们的生命周期取决于 Spring application context。这意味着您可以将 Spring 功能(例如显式依赖项注入)应用于端点实例。通过@Autowired注入 Annotation-driven 也可以。以下 example 显示了如何定义这些 beans:

1
2
3
4
5
6
7
8
9
<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派生,因为此 example 中的端点是完全 Spring-managed bean。这意味着端点 implementation 可以如下(没有声明任何超类 - 并且仍然遵循 Spring 的@Autowired configuration annotation):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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);
}

}

使用JAX-WS远程调用的Spring支持导出WebServices

Oracle 的 JAX-WS RI 是作为 GlassFish 项目的一部分开发的,它将 Spring 支持作为其 JAX-WS Commons _project 的一部分。这允许将 JAX-WS endpoints 定义为 Spring-managed beans,类似于上一节中讨论的独立模式 - 但在 Servlet 环境中这是 time。

这在 Java EE 5 环境中不可移植。它主要用于 non-EE 环境,例如 Tomcat,它将 JAX-WS RI 作为 web application 的一部分嵌入。

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

有关设置和使用方式的详细信息,请参阅

使用JAX-WS访问WebServices

Spring 提供了两个工厂 beans 来创建 JAX-WS web service 代理,即LocalJaxWsServiceFactoryBeanJaxWsPortProxyFactoryBean。前者只能返回 JAX-WS 服务 class 供我们使用。后者是 full-fledged version,可以 return 实现我们的业务服务接口的代理。在下面的示例中,我们使用JaxWsPortProxyFactoryBeanAccountService端点创建代理(再次):

1
2
3
4
5
6
7
<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="http://example/"/>
<property name="serviceName" value="AccountService"/>
<property name="portName" value="AccountServiceEndpointPort"/>
</bean>
1 serviceInterface是 clients 使用的业务接口。

wsdlDocumentUrl是 WSDL 文件的 URL。 Spring 在启动 time 时需要这个来创建 JAX-WS 服务。 namespaceUri对应于.wsdl 文件中的targetNamespaceserviceName对应于.wsdl 文件中的服务 name。 portName对应于.wsdl 文件中的 port name。

访问 web service 很容易,因为我们有一个 bean 工厂,它将它公开为一个名为AccountService的接口。以下 example 显示了我们如何在 Spring 中连接它:

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

从 client code,我们可以访问 web service,就像它是一个普通的 class 一样,如下面的 example 所示:

1
2
3
4
5
6
7
8
9
10
11
12
public class AccountClientImpl {

private AccountService service;

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

public void foo() {
service.insertAccount(...);
}
}

上面的内容略有简化,因为 JAX-WS 需要使用@WebService@SOAPBinding etc annotations 注释端点接口和 implementation classes。这意味着您不能(轻松地)使用普通 Java 接口和 implementation classes 作为 JAX-WS endpoint artifacts;你需要先对它们进行相应的注释。有关这些要求的详细信息,请查看 JAX-WS 文档。

通过JMS公开服务

您还可以使用 JMS 作为底层通信协议透明地公开服务。 Spring Framework 中的 JMS 远程支持非常基础。它在same thread和 non-transactional Session中发送和接收。因此,吞吐量为 implementation-dependent。请注意,这些 single-threaded 和 non-transactional 约束仅适用于 Spring 的 JMS 远程支持。有关 Spring 对 JMS-based 消息传递的丰富支持的信息,请参阅JMS(Java 消息服务)

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

1
2
3
4
5
6
7
package com.foo;

public interface CheckingAccountService {

public void cancelAccount(Long accountId);

}

在 server-side 上使用了上述接口的以下简单 implementation:

1
2
3
4
5
6
7
8
9
package com.foo;

public class SimpleCheckingAccountService implements CheckingAccountService {

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

}

以下 configuration 文件包含 client 和服务器上共享的 JMS-infrastructure beans:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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
http://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>

服务端配置

在服务器上,您需要公开使用JmsInvokerServiceExporter的服务 object,如下面的 example 所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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
http://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>
1
2
3
4
5
6
7
8
9
10
11
package com.foo;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Server {

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

}

客户端配置

client 只需要创建一个实现 agreed-upon 接口(CheckingAccountService)的 client-side 代理。

以下 example 定义 beans,您可以 inject 到其他 client-side objects(并且代理负责通过 JMS 将调用转发到 server-side object):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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
http://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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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(
new String[] {"com/foo/client.xml", "com/foo/jms.xml"});
CheckingAccountService service = (CheckingAccountService) ctx.getBean("checkingAccountService");
service.cancelAccount(new Long(10));
}

}

AMQP

有关详细信息,请参阅Spring AMQP Reference Guide 的’Spring Remoting with AMQP’部分

remote 接口未实现 Auto-detection

remote 接口没有实现 auto-detection 实现接口的主要原因是避免为 remote 调用者打开太多门。目标 object 可能实现内部回调接口,例如InitializingBeanDisposableBean,这些接口不希望向调用者公开。

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

这在配置方便性和内部方法意外暴露的风险之间是 trade-off。始终指定服务接口不是太费力,并且使您在特定方法的受控暴露方面保持安全。

选择技术时的注意事项

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

使用 RMI 时,除非隧道传输 RMI 流量,否则无法通过 HTTP 协议访问 objects。 RMI 是一个相当协议,因为它支持 full-object 序列化,当您使用需要通过线路进行序列化的复杂数据 model 时,这很重要。但是,RMI-JRMP 与 Java clients 绑定。这是一个 Java-to-Java 远程解决方案。

如果你需要 HTTP-based 远程处理但是依赖于 Java 序列化,Spring 的 HTTP 调用程序是一个很好的选择。它与 RMI 调用程序共享基本基础结构,但使用 HTTP 作为传输。请注意,HTTP 调用程序不仅限于 Java-to-Java 远程处理,还包括 client 和服务器端的 Spring。 (后者也适用于 Spring 的 RMI 调用者 non-RMI interfaces.)

在异构环境中运行时,Hessian 可能会提供重要的 value,因为它们明确允许 non-Java clients。但是,non-Java 支持仍然有限。已知问题包括 Hibernate objects 与 lazily-initialized 集合的序列化。如果您有这样的数据 model,请考虑使用 RMI 或 HTTP 调用程序而不是 Hessian。

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

最后但并非最不重要的是,EJB 具有优于 RMI 的优势,因为它支持标准 role-based 身份验证和授权以及 remote transaction 传播。有可能让 RMI 调用者或 HTTP 调用者也支持安全 context 传播,尽管核心 Spring 不提供。 Spring 仅提供适当的挂钩,用于插入 third-party 或自定义解决方案。

REST-Endpoints

Spring Framework 为 Calls REST endpoints 提供了两种选择:

  • 使用 RestTemplate:原始的 Spring REST client,带有同步的模板方法 API。
  • Web 客户端:支持同步和异步以及流方案的 non-blocking,reactive 备选方案。

从 5.0 开始,non-blocking,reactive WebClient提供了RestTemplate的现代替代方案,同时有效地支持同步和异步以及流方案。 RestTemplate将在未来的 version 中弃用,并且不会在未来添加主要的新 features。

使用RestTemplate

RestTemplate提供了比 HTTP client libraries 更高的 level API。它使得在单个 line 中调用 REST endpoints 变得容易。它公开了以下重载方法组:

方法 group 描述
getForObject 通过 GET 检索表示。
getForEntity 使用 GET 检索ResponseEntity(即 status,headers 和 body)。
headForHeaders 使用 HEAD 检索资源的所有 headers。
postForLocation 使用 POST 创建新资源,并从响应中返回Location标头。
postForObject 使用 POST 创建新资源并从响应中返回表示。
postForEntity 使用 POST 创建新资源并从响应中返回表示。
put 使用 PUT 创建或更新资源。
patchForObject 使用 PATCH 更新资源并从响应中返回表示。请注意,JDK HttpURLConnection不支持PATCH,但 Apache HttpComponents 和其他人一样。
delete 使用 DELETE 删除指定 URI 处的资源。
optionsForAllow 使用 ALLOW 检索资源的允许 HTTP 方法。
exchange 对前述方法的更广泛(且不那么自以为是)version,在需要时提供额外的灵活性。它接受RequestEntity(包括 HTTP 方法,URL,headers 和 body 作为输入)并返回ResponseEntity。 这些方法允许使用ParameterizedTypeReference而不是Class来指定具有泛型的响应类型。
execute 执行请求的最通用方式,通过回调接口完全控制请求准备和响应提取。
初始化

默认构造函数使用java.net.HttpURLConnection来执行请求。您可以使用ClientHttpRequestFactory的 implementation 切换到不同的 HTTP library。以下是 built-in 支持:

  • Apache HttpComponents
  • Netty
  • OkHttp

对于 example,要切换到 Apache HttpComponents,您可以使用以下命令:

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

每个ClientHttpRequestFactory都公开特定于底层 HTTP client library 的 configuration 选项 - 用于 example,用于凭据,连接池和其他详细信息。

请注意,HTTP 请求的java.net implementation 可以在访问表示错误的响应的状态(例如 401)时引发 exception。如果这是一个问题,请切换到另一个 HTTP client library。

URIs

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

以下 example 使用String variable 参数:

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

以下 example 使用Map<String, String>

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

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

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

1
2
3
restTemplate.getForObject("http://example.com/hotel list", String.class);

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

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

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

Headers

您可以使用exchange()方法指定 request headers,如下面的 example 所示:

1
2
3
4
5
6
7
8
9
10
11
String uriTemplate = "http://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();

您可以通过_ret 的许多RestTemplate方法变体获取响应 headers。

Body

传递给RestTemplate方法并从RestTemplate方法返回的 Objects 在HttpMessageConverter的帮助下转换为原始内容和从原始内容转换。

在 POST 上,输入 object 被序列化到请求主体,如下面的 example 所示:

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

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

在 GET 上,响应的主体被反序列化为输出Object,如下面的 example 所示:

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

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

默认情况下,RestTemplate会注册所有 built-in 消息转换器,具体取决于 classpath 检查,这有助于确定存在哪些可选转换 libraries。您还可以将消息转换器设置为显式使用。

消息转换

与 Spring WebFlux 相同

spring-web模块包含HttpMessageConverter contract,用于通过InputStreamOutputStream读取和写入 HTTP 请求和响应的主体。 实例用于 client 端(用于 example,在RestTemplate中)和服务器端(用于 example,在 Spring MVC REST 控制器中)。

framework 中提供了主要媒体(MIME)类型的具体 implementation,默认情况下,在 client 端注册RestTemplate,在服务器端注册RequestMethodHandlerAdapter(请参阅配置消息转换器)。

HttpMessageConverter的 implementation 将在以下部分中介绍。对于所有转换器,使用默认媒体类型,但您可以通过设置supportedMediaTypes bean property 来覆盖它。以下 table 描述了每个 implementation:

MessageConverter 描述
StringHttpMessageConverter 一个HttpMessageConverter implementation,可以从 HTTP 请求和响应中读取和写入String实例。默认情况下,此转换器支持所有文本媒体类型(text/*)并使用Content-Type text/plain进行写入。
FormHttpMessageConverter 一个HttpMessageConverter implementation,可以从 HTTP 请求和响应中读取和写入表单数据。默认情况下,此转换器读取和写入application/x-www-form-urlencoded媒体类型。表格数据从MultiValueMap<String, String>读取并写入.3_。
ByteArrayHttpMessageConverter 一个HttpMessageConverter implementation,可以从 HTTP 请求和响应中读取和写入字节数组。默认情况下,此转换器支持所有媒体类型(*/*)并使用Content-Type application/octet-stream进行写入。您可以通过设置supportedMediaTypes property 并覆盖getContentType(byte[])来覆盖它。
MarshallingHttpMessageConverter 一个HttpMessageConverter implementation,可以通过org.springframework.oxm包中使用 Spring 的MarshallerUnmarshaller抽象来读写 XML。该转换器需要MarshallerUnmarshaller才能使用。您可以通过构造函数或 bean properties 对它们进行注入。默认情况下,此转换器支持text/xmlapplication/xml
MappingJackson2HttpMessageConverter 一个HttpMessageConverter implementation,可以使用 Jackson 的ObjectMapper来读写 JSON。您可以根据需要通过使用 Jackson 提供的注释来自定义 JSON 映射。当您需要进一步控制时(对于需要为特定类型提供自定义 JSON serializers/deserializers 的情况),您可以通过ObjectMapper property 注入自定义ObjectMapper。默认情况下,此转换器支持application/json
MappingJackson2XmlHttpMessageConverter 一个HttpMessageConverter implementation,可以使用Jackson XML扩展名的XmlMapper来读写 XML。您可以根据需要通过使用 JAXB 或 Jackson 提供的注释来自定义 XML 映射。当您需要进一步控制时(对于需要为特定类型提供自定义 XML serializers/deserializers 的情况),您可以通过ObjectMapper property 注入自定义XmlMapper。默认情况下,此转换器支持application/xml
SourceHttpMessageConverter 一个HttpMessageConverter implementation,可以从 HTTP 请求和响应中读取和写入javax.xml.transform.Source。仅支持DOMSourceSAXSourceStreamSource。默认情况下,此转换器支持text/xmlapplication/xml
BufferedImageHttpMessageConverter 一个HttpMessageConverter implementation,可以从 HTTP 请求和响应中读取和写入java.awt.image.BufferedImage。此转换器读取和写入 Java I/O API 支持的媒体类型。
Jackson JSON 视图

您可以指定Jackson JSON 查看仅序列化 object properties 的子集,如下面的 example 所示:

1
2
3
4
5
6
7
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);

RequestEntity<MappingJacksonValue> requestEntity =
RequestEntity.post(new URI("http://example.com/user")).body(value);

ResponseEntity<String> response = template.exchange(requestEntity, String.class);
Multipart

要发送 multipart 数据,您需要提供MultiValueMap<String, ?>,其值为表示部件内容的Object实例或表示部件的内容和_header 的HttpEntity实例。 MultipartBodyBuilder提供了一个方便的 API 来准备 multipart 请求,如下面的 example 所示:

1
2
3
4
5
6
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fieldPart", "fieldValue");
builder.part("filePart", new FileSystemResource("...logo.png"));
builder.part("jsonPart", new Person("Jason"));

MultiValueMap<String, HttpEntity<?>> parts = builder.build();

在大多数情况下,您不必为每个零件指定Content-Type。 content type 是根据所选的HttpMessageConverter自动确定的,以便序列化它,如果是Resource,则根据文件扩展名自动确定。如有必要,您可以通过其中一个重载的构建器part方法显式提供MediaType以用于每个部件。

一旦MultiValueMap准备就绪,您可以将其传递给RestTemplate,如下面的 example 所示:

1
2
MultipartBodyBuilder builder = ...;
template.postForObject("http://example.com/upload", builder.build(), Void.class);

如果MultiValueMap包含至少一个非String value,它也可以表示常规表单数据(即application/x-www-form-urlencoded),则无需将Content-Type设置为multipart/form-data。当您使用确保HttpEntity wrapper 的MultipartBodyBuilder时,情况总是如此。

使用AsyncRestTemplate(已弃用)

AsyncRestTemplate已弃用。对于您可能考虑使用AsyncRestTemplate的所有用例,请改用Web 客户端

企业级JavaBeans(EJB)集成

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

但是,请务必注意,使用 Spring 不会阻止您使用 EJB。实际上,Spring 使得访问 EJB 以及在其中实现 EJB 和功能变得更加容易。此外,使用 Spring 访问 EJB 提供的服务允许稍后在本地 EJB,remote EJB 或 POJO(普通旧 Java object)变体之间切换这些服务的 implementation,而不必更改 client code。

在本章中,我们将了解 Spring 如何帮助您访问和实现 EJB。 Spring 在访问 stateless session beans(SLSB)时提供特定的 value,因此我们首先讨论这个 topic。

访问EJB

本节介绍如何访问 EJB。

概念

要在本地或 remote stateless session bean 上调用方法,client code 通常必须执行 JNDI 查找以获取(本地或 remote)EJB Home object,然后在该 object 上使用create方法调用来获取实际(local 或 remote) )EJB object。然后在 EJB 上调用一个或多个方法。

为避免重复 low-level code,许多 EJB applications 使用 Service Locator 和 Business Delegate 模式。这些比在整个 client code 中喷涂 JNDI 查找更好,但它们通常的 implementations 有明显的缺点:

  • 通常,使用 EJB 的 code 取决于 Service Locator 或 Business Delegate 单例,因此很难进行测试。
  • 对于没有业务委托使用的服务定位器 pattern,application code 仍然需要在 EJB 主目录上调用create()方法并处理生成的 exceptions。因此,它仍然与 EJB API 和 EJB 编程 model 的复杂性联系在一起。
  • 实现 Business Delegate pattern 通常会导致重大的 code 重复,我们必须编写许多在 EJB 上调用相同方法的方法。

Spring 方法允许创建和使用代理 objects(通常在 Spring 容器内配置),它们充当无代码业务委托。您不需要在 hand-coded Business Delegate 中编写另一个 Service Locator,另一个 JNDI 查找或重复方法,除非您在此类 code 中实际添加了实际 value。

访问本地SLSB

假设我们有一个需要使用本地 EJB 的 web 控制器。我们遵循最佳实践并使用 EJB 业务方法接口 pattern,以便 EJB 的本地接口扩展 non-EJB-specific 业务方法接口。我们将此业务方法称为接口MyComponent。以下 example 显示了这样的接口:

1
2
3
public interface MyComponent {
...
}

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

1
2
3
4
5
private MyComponent myComponent;

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

我们随后可以在控制器中的任何业务方法中使用此实例变量。现在,假设我们从 Spring 容器中获取控制器 object,我们可以(在相同的 context 中)配置LocalStatelessSessionProxyFactoryBean实例,即 EJB 代理 object。我们配置代理并使用以下 configuration 条目设置控制器的myComponent property:

1
2
3
4
5
6
7
8
9
<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>

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

myController bean 定义将控制器 class 的myComponent property 设置为 EJB 代理。

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

1
2
3
4
5
6
<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 访问机制大大简化了 application code。 web 层 code(或其他 EJB client code)不依赖于 EJB 的使用。要用 POJO 或 mock object 或其他测试存根替换此 EJB reference,我们可以在不更改 Java code 的 line 的情况下更改myComponent bean 定义。另外,我们不必编写单个 line 的 JNDI 查找或其他 EJB 管道 code 作为我们的 application 的一部分。

实际应用程序中的基准和经验表明,这种方法的性能开销(涉及目标 EJB 的反射调用)很小,在典型使用中无法检测到。请记住,我们不希望将 fine-grained calls 设置为 EJB,因为在 application 服务器中存在与 EJB 基础结构相关的成本。

关于 JNDI 查找有一点需要注意。在 bean 容器中,这个 class 通常最好用作 singleton(没有理由将它作为原型)。但是,如果 bean 容器 pre-instantiates 单例(与各种 XML ApplicationContext变体一样),如果在 EJB 容器加载目标 EJB 之前加载 bean 容器,则会出现问题。这是因为 JNDI 查找在此 class 的init()方法中执行,然后进行了缓存,但 EJB 尚未绑定到目标位置。解决方案是不要 pre-instantiate 这个工厂 object 但是要在第一次使用时创建它。在 XML 容器中,您可以使用lazy-init属性来控制它。

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

访问远程SLSB

除了使用SimpleRemoteStatelessSessionProxyFactoryBean<jee:remote-slsb> configuration 元素之外,访问 remote EJB 与访问本地 EJB 基本相同。当然,无论有没有 Spring,remote 调用语义都适用:对另一台计算机中另一台虚拟机中 object 上的方法的调用有时必须在使用场景和故障处理方面区别对待。

Spring 的 EJB client 支持比 non-Spring 方法增加了一个优势。通常,EJB client code 很容易在本地或远程调用 EJB 之间来回切换。这是因为 remote 接口方法必须声明它们抛出RemoteException,并且 client code 必须处理这个,而本地接口方法不需要。为需要移动到 remote EJB 的本地 EJB 编写的 Client code 通常必须进行修改以添加 remote exceptions 的处理,并且为需要移动到本地 EJB 的 remote EJB 编写的 client code 可以保持不变但是执行很多不必要的 remote exceptions 处理或被修改以删除该 code。使用 Spring remote EJB 代理,您可以不在业务方法接口中声明任何抛出的RemoteException并实现 EJB code,具有相同的 remote 接口(除了它会抛出RemoteException),并依赖代理动态处理两个接口好像它们是相同的。也就是说,client code 不必处理已检查的RemoteException class。在 EJB 调用期间抛出的任何实际RemoteException都是 re-thrown 作为 non-checked RemoteAccessException class,它是RuntimeException的子类。然后,您可以在本地 EJB 或 remote EJB(甚至是普通的 Java object)implementation 之间随意切换目标服务,而无需知道或关注 client code。当然,这是可选的:没有什么能阻止你在业务界面中声明RemoteException

访问EJB2.x的SLSB与EJB3的SLSB

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

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

JMS

Spring 提供了一个 JMS integration framework,它简化了 JMS API 的使用,就像 Spring 的 integration 对 JDBC API 一样。

JMS 可以大致分为两个功能区域,即消息的生产和消费。 JmsTemplate class 用于消息 production 和同步消息接收。对于类似于 Java EE 的 message-driven bean 样式的异步接收,Spring 提供了许多可用于创建 Message-Driven POJO(MDP)的 message-listener 容器。 Spring 还提供了一种创建消息 listeners 的声明方式。

org.springframework.jms.core包提供了使用 JMS 的核心功能。它包含 JMS 模板 classes,它通过处理资源的创建和释放来简化 JMS 的使用,就像JdbcTemplate对 JDBC 一样。 Spring 模板 classes 的设计原则 common 是提供帮助方法来执行 common 操作,并且为了更复杂的用法,将处理任务的本质委托给 user-implemented 回调接口。 JMS 模板遵循相同的设计。 classes 提供了各种方便的方法来发送消息,同步消耗消息,以及向用户公开 JMS session 和 messageproducer。

org.springframework.jms.support包提供JMSException转换功能。转换将已检查的JMSException层次结构转换为未检查的 exceptions 的镜像层次结构。如果已检查的javax.jms.JMSException的任何 provider-specific 子类存在,则此 exception 将包含在未选中的UncategorizedJmsException中。

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

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

org.springframework.jms.annotation包提供了必要的基础结构,通过使用@JmsListener来支持 annotation-driven listener endpoints。

org.springframework.jms.config包为jms名称空间提供解析器实现,以及为配置 listener 容器和创建 listener endpoints 的 java config 支持。

最后,org.springframework.jms.connection包提供ConnectionFactory的实现,适用于独立的 applications。它还包含一个 Spring 的PlatformTransactionManager实现 JMS(狡猾地命名为JmsTransactionManager)。这允许将 JMS 作为 transactional 资源无缝整合到 Spring 的 transaction management 机制中。

使用Spring的JMS

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

使用JmsTemplate

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

使用JmsTemplate的 Code 只需要实现回调接口,为它们提供明确定义的 high-level contract。 MessageCreator回调接口在JmsTemplate中调用 code 提供Session时创建一条消息。为了允许更复杂地使用 JMS API,SessionCallback提供 JMS session,ProducerCallback提供SessionMessageProducer对。

JMS API 公开了两种类型的发送方法,一种采用传递模式,优先级,time-to-live 作为服务质量(QOS)参数,另一种不采用 QOS 参数并使用默认值。由于JmsTemplate有许多发送方法,因此设置 QOS 参数已作为 bean properties 公开,以避免重复发送方法的数量。同样,使用setReceiveTimeout property 设置同步 receive calls 的超时值。

一些 JMS 提供程序允许通过ConnectionFactory的 configuration 以管理方式设置默认 QOS 值。这会导致对MessageProducer实例的send方法(send(Destination destination, Message message))的调用使用与 JMS 规范中指定的值不同的 QOS 默认值。在 order 中提供 QOS 值的一致 management,因此,JmsTemplate必须通过将 boolean property isExplicitQosEnabled设置为true来专门启用它自己的 QOS 值。

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

一旦配置,JmsTemplate class 的实例为 thread-safe。这很重要,因为这意味着您可以配置JmsTemplate的单个实例,然后安全地将此共享 reference 注入多个协作者。要清楚,JmsTemplate是有状态的,因为它维护的 reference,但是 state 不是会话 state。

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

连接

JmsTemplate需要参考ConnectionFactoryConnectionFactory是 JMS 规范的一部分,并作为使用 JMS 的入口点。它被 client application 用作工厂来创建与 JMS 提供程序的连接,并封装各种 configuration 参数,其中许多是 vendor-specific,例如 SSL configuration 选项。

在 EJB 中使用 JMS 时,供应商提供 JMS 接口的 implementations,以便它们可以参与声明式 transaction management 并执行连接和会话池。在 order 中使用此 implementation,Java EE 容器通常要求您在 EJB 或 servlet 部署描述符中将 JMS 连接工厂声明为resource-ref。为了确保在 EJB 内部使用JmsTemplate这些 features,client application 应该确保它__seferences ConnectionFactory的托管 implementation。

缓存消息传递资源

标准 API 涉及创建许多中间 objects。要发送消息,请执行以下“API”步骤:

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

ConnectionFactorySend操作之间,创建并销毁了三个中间 object。为了优化资源使用并提高 performance,Spring 提供了ConnectionFactory的两个 implementations。

使用SingleConnectionFactory

Spring 提供了ConnectionFactory接口的 implementation SingleConnectionFactory,它在所有createConnection() calls 上返回相同的Connection,并忽略 calls 到close()。这对于测试和独立环境非常有用,因此可以将相同的连接用于多个可以 span 任意数量的 transactions 的JmsTemplate calls。 SingleConnectionFactory对标准ConnectionFactory进行 reference,通常来自 JNDI。

使用CachingConnectionFactory

CachingConnectionFactory扩展了SingleConnectionFactory的功能,并添加了SessionMessageProducerMessageConsumer实例的缓存。初始高速缓存大小设置为1。您可以使用sessionCacheSize property 来增加缓存会话的数量。请注意,实际缓存会话的数量大于该数量,因为会话基于其确认模式进行缓存,因此当sessionCacheSize设置为 1 时,最多可以有四个缓存的 session 实例(每个确认模式一个)。 MessageProducerMessageConsumer实例在其拥有的 session 中缓存,并在缓存时考虑生产者和使用者的唯一 properties。 MessageProducers 根据其目标进行缓存。 MessageConsumers 基于由目标,选择器,noLocal delivery flag 和持久订阅 name(如果 creating 持久消费者)组成的 key 进行缓存。

目的地管理

作为ConnectionFactory实例的目标是 JMS 管理的 objects,您可以在 JNDI 中存储和检索它们。配置 Spring application context 时,可以使用 JNDI JndiObjectFactoryBean factory class 或<jee:jndi-lookup>对 object 的 references to JMS 目标执行依赖项注入。但是,如果 application 中存在大量目标,或者存在 JMS 提供程序特有的高级目标 management features,则此策略通常很麻烦。此类高级目标 management 的示例包括创建动态目标或支持目标的分层命名空间。 JmsTemplate将目标 name 的解析委托给实现DestinationResolver接口的 JMS 目标 object。 DynamicDestinationResolverJmsTemplate使用的默认 implementation,适用于解析动态目标。还提供了JndiDestinationResolver作为 JNDI 中包含的目标的服务定位器,并且可选地回退到DynamicDestinationResolver中包含的行为。

通常,JMS application 中使用的目标仅在运行时已知,因此在部署 application 时无法通过管理方式创建。这通常是因为根据 well-known 命名约定在运行时创建目标的交互系统组件之间存在共享的 application 逻辑。尽管动态目标的创建不是 JMS 规范的一部分,但大多数供应商都提供了此功能。动态目标是使用 user-defined name 创建的,它将它们与临时目标区分开来,并且通常不在 JNDI 中注册。用于创建动态目标的 API 因提供商而异,因为与目标关联的 properties 为 vendor-specific。但是,供应商有时会做出一个简单的 implementation 选择,即忽略 JMS 规范中的警告,并使用方法TopicSession createTopic(String topicName)QueueSession createQueue(String queueName)方法创建具有默认目标 properties 的新目标。根据供应商 implementation,DynamicDestinationResolver也可以创建物理目标,而不是仅解析一个。

boolean property pubSubDomain用于配置JmsTemplate,了解正在使用的 JMS 域。默认情况下,此 property 的 value 为 false,表示将使用 point-to-point 域Queues。此 property(由JmsTemplate使用)通过DestinationResolver接口的 implementations 确定动态目标解析的行为。

您还可以通过 property defaultDestinationJmsTemplate配置为默认目标。默认目标是发送和接收操作,不涉及特定目标。

消息Listener容器

EJB 世界中 JMS 消息最常见的用途之一是驱动 message-driven beans(MDB)。 Spring 提供了一种解决方案,可以以不将用户绑定到 EJB 容器的方式创建 message-driven POJO(MDP)。 (有关 Spring 的 MDP support.)的详细介绍,请参阅异步接收:Message-Driven POJO自 Spring Framework 4.1 以来,端点方法可以用@JmsListener注释 - 有关详细信息,请参阅Annotation-driven Listener Endpoints

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

有两个标准的 JMS 消息 listener 容器与 Spring 一起打包,每个容器都有其专门的 feature 集。

使用SimpleMessageListenerContainer

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

虽然SimpleMessageListenerContainer不允许参与外部管理的 transactions,但它确实支持本机 JMS transactions。要启用此 feature,可以将sessionTransacted flag 切换为true,或者在 XML 命名空间中将acknowledge属性设置为transacted。 _listener 抛出的异常然后导致回滚,并重新传递消息。或者,考虑使用CLIENT_ACKNOWLEDGE模式,它在 exception 的情况下也提供重新传递,但不使用事务Session实例,因此,不包括 transaction 协议中的任何其他Session操作(例如发送响应消息)。

默认的AUTO_ACKNOWLEDGE模式不提供适当的可靠性保证。 listener 执行失败时消息可能会丢失(因为提供程序在 listener 调用后自动确认每条消息,没有 exceptions 传播到提供程序)或者 listener 容器关闭时(可以通过设置acceptMessagesWhileStopping flag 来配置)。确保在可靠性需求的情况下使用事务处理会话(例如,用于可靠的队列处理和持久的 topic 订阅)。

使用DefaultMessageListenerContainer

在大多数情况下使用此消息 listener 容器。与SimpleMessageListenerContainer相比,此容器变体允许动态适应运行时需求,并且能够参与外部管理的 transactions。当配置JtaTransactionManager时,每个收到的消息都使用 XA transaction 注册。因此,处理可以利用 XA transaction 语义。这个 listener 容器在 JMS 提供程序的低要求,高级功能(例如参与外部管理的 transactions)和与 Java EE 环境的兼容性之间取得了良好的平衡。

您可以自定义容器的缓存 level。请注意,如果未启用缓存,则会为每个邮件接收创建新连接和新的 session。将此与高负载的 non-durable 订阅相结合可能会导致消息丢失。在这种情况下,请确保使用正确的缓存 level。

当 broker 关闭时,此容器还具有可恢复的功能。默认情况下,简单的BackOff implementation 每五秒重试一次。您可以为更多 fine-grained 恢复选项指定自定义BackOff implementation。有关 example,请参见 api-spring-framework/util/backoff/ExponentialBackOff.html [506]。

与其兄弟(SimpleMessageListenerContainer 一样DefaultMessageListenerContainer支持本机 JMS transactions,并允许自定义确认模式。如果您的方案可行,强烈建议使用外部管理的 transactions - 也就是说,如果您在 JVM 死亡的情况下偶尔会出现重复消息。业务逻辑中的自定义重复消息检测步骤可以涵盖这种情况 - 例如,以业务实体存在检查或协议 table 检查的形式。任何此类安排都比替代方案更有效:使用 XA transaction(通过使用JtaTransactionManager配置DefaultMessageListenerContainer)来包装整个处理,以覆盖 JMS 消息的接收以及消息 listener 中业务逻辑的执行(包括数据库操作等)。

默认的AUTO_ACKNOWLEDGE模式不提供适当的可靠性保证。 listener 执行失败时消息可能会丢失(因为提供程序在 listener 调用后自动确认每条消息,没有 exceptions 传播到提供程序)或者 listener 容器关闭时(可以通过设置acceptMessagesWhileStopping flag 来配置)。确保在可靠性需求的情况下使用事务处理会话(例如,用于可靠的队列处理和持久的 topic 订阅)。

事务管理

Spring 提供JmsTransactionManager来管理单个 JMS ConnectionFactory的 transactions。这使得 JMS applications 可以利用 Spring 的 managed-transaction features,如数据访问章节的 Transaction Management 部分中所述。 JmsTransactionManager执行本地资源 transactions,_绑定从指定的ConnectionFactory到线程的 JMS Connection/Session 对。 JmsTemplate自动检测此类 transactional 资源并相应地对它们进行操作。

在 Java EE 环境中,ConnectionFactory池连接 Connection 和 Session 实例,因此这些资源可以跨 transactions 有效地重用。在独立环境中,在共享 JMS Connection中使用 Spring 的SingleConnectionFactory结果,每个 transaction 都有自己独立的Session。或者,考虑使用 provider-specific 池适配器,例如 ActiveMQ 的PooledConnectionFactory class。

您还可以将JmsTemplateJtaTransactionManager和 XA-capable JMS ConnectionFactory一起使用来执行分布式 transactions。请注意,这需要使用 JTA transaction manager 以及正确的 XA-configured ConnectionFactory。 (检查您的 Java EE 服务器或 JMS 提供程序的 documentation.)

使用 JMS API 从Connection创建Session时,在托管和非托管 transactional 环境中重用 code 可能会造成混淆。这是因为 JMS API 只有一个工厂方法来创建Session,它需要 transaction 和 acknowledge 方式的值。在托管环境中,设置这些值是环境的 transactional 基础结构的责任,因此供应商的 wrapper 会忽略这些值到 JMS 连接。在非托管环境中使用JmsTemplate时,可以通过使用 properties sessionTransactedsessionAcknowledgeMode来指定这些值。当与JmsTemplate一起使用时,模板总是被赋予 transactional JMS Session

发送消息

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

以下 example 使用MessageCreator回调从提供的Session object 创建文本消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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");
}
});
}
}

在前面的 example 中,JmsTemplate是通过将 reference 传递给ConnectionFactory来构造的。作为替代方案,提供 zero-argument 构造函数和connectionFactory,可用于以 JavaBean 样式构造实例(使用BeanFactory或普通 Java code)。或者,考虑从 Spring 的JmsGatewaySupport convenience base class 派生,它为 JMS configuration 提供 pre-built bean properties。

send(String destinationName, MessageCreator creator)方法允许您使用目标的 string name 发送消息。如果这些名称在 JNDI 中注册,则应将模板的destinationResolver property 设置为JndiDestinationResolver的实例。

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

使用消息转换器

为了便于发送域 model objects,JmsTemplate具有各种发送方法,这些方法将 Java object 作为消息数据内容的参数。 JmsTemplate中的重载方法convertAndSend()receiveAndConvert()方法将转换 process 委托给MessageConverter接口的实例。此接口定义了一个简单的 contract,用于在 Java objects 和 JMS 消息之间进行转换。默认的 implementation(SimpleMessageConverter)支持StringTextMessagebyte[]BytesMesssage以及java.util.MapMapMessage之间的转换。通过使用转换器,您和您的 application code 可以专注于通过 JMS 发送或接收的业务 object,而不关心它如何表示为 JMS 消息的细节。

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

为了适应无法一般封装在转换器 class 中的消息的 properties,headers 和 body 的设置,MessagePostProcessor接口允许您在转换之后但在发送之前访问该消息。以下 example 显示了在将java.util.Map转换为消息后如何修改消息头和 property:

1
2
3
4
5
6
7
8
9
10
11
12
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;
}
});
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
MapMessage={
Header={
... standard headers ...
CorrelationID={123-00001}
}
Properties={
AccountID={Integer:1234}
}
Fields={
Name={String:Mark}
Age={Integer:47}
}
}

使用SessionCallback和ProducerCallback

虽然发送操作涵盖了许多 common 使用场景,但有时您可能希望在 JMS SessionMessageProducer上执行多个操作。 SessionCallbackProducerCallback分别公开 JMS SessionSession/MessageProducer对。 JmsTemplate上的execute()方法执行这些回调方法。

收到消息

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

同步接收

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

异步接收:消息驱动的POJO

Spring 还通过使用@JmsListener annotation 支持 annotated-listener endpoints,并提供了一个开放的基础结构来以编程方式注册 endpoints。到目前为止,这是设置异步接收器的最便捷方式。有关详细信息,请参阅启用 Listener Endpoint Annotations

在 EJB 世界中类似于 Message-Driven Bean(MDB)的方式中,Message-Driven POJO(MDP)充当 JMS 消息的接收者。 MDP 上的一个限制(但请参见使用 MessageListenerAdapter是它必须实现javax.jms.MessageListener接口。请注意,如果您的 POJO 在多个线程上收到消息,则确保 implementation 为 thread-safe 非常重要。

以下 example 显示了 MDP 的简单 implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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,就可以 time 创建一个消息 listener 容器。

以下 example 显示了如何定义和配置 Spring 附带的消息 listener 容器之一(在本例中为DefaultMessageListenerContainer):

1
2
3
4
5
6
7
8
9
<!-- 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>

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

使用SessionAwareMessageListener接口

SessionAwareMessageListener接口是一个 Spring-specific 接口,它为 JMS MessageListener接口提供类似的 contract,但也提供 message-handling 方法访问接收Message的 JMS Session。以下清单显示了SessionAwareMessageListener接口的定义:

1
2
3
4
5
6
package org.springframework.jms.listener;

public interface SessionAwareMessageListener {

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

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

请注意SessionAwareMessageListener接口的onMessage(..)方法抛出JMSException。与标准 JMS MessageListener接口相比,使用SessionAwareMessageListener接口时,client code 负责处理任何抛出的 exceptions。

使用MessageListenerAdapter

MessageListenerAdapter class 是 Spring 的异步消息支持中的最终 component。简而言之,它允许您将几乎任何 class 暴露为 MDP(尽管存在一些约束)。

请考虑以下接口定义:

1
2
3
4
5
6
7
8
9
10
public interface MessageDelegate {

void handleMessage(String message);

void handleMessage(Map message);

void handleMessage(byte[] message);

void handleMessage(Serializable message);
}

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

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

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

特别要注意MessageDelegate接口的前面 实现(DefaultMessageDelegate class)根本没有 JMS 依赖。它真的是一个 POJO,我们可以通过以下 configuration 进入 MDP:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 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>

下一个 example 显示了另一个只能处理 JMS TextMessage消息的 MDP。请注意消息处理方法实际上是如何调用receive(MessageListenerAdapter中的消息处理方法的 name 默认为handleMessage),但它是可配置的(正如您在本节后面所述)。另请注意receive(..)方法如何强类型化以接收和响应 JMS TextMessage消息。以下清单显示了TextMessageDelegate接口的定义:

1
2
3
4
public interface TextMessageDelegate {

void receive(TextMessage message);
}

以下清单显示了实现TextMessageDelegate接口的 class:

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

然后,服务员MessageListenerAdapter的配置如下:

1
2
3
4
5
6
7
8
9
10
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<constructor-arg>
<bean class="jmsexample.DefaultTextMessageDelegate"/>
</constructor-arg>
<property name="defaultListenerMethod" value="receive"/>
<!-- we don't want automatic message context extraction -->
<property name="messageConverter">
<null/>
</property>
</bean>

请注意,如果messageListener收到TextMessage以外类型的 JMS Message,则抛出IllegalStateException(并随后吞下)。 MessageListenerAdapter class 的另一个功能是,如果处理程序方法返回 non-void value,则能够自动发回响应Message。考虑以下接口和 class:

1
2
3
4
5
6
7
8
public interface ResponsiveTextMessageDelegate {

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

如果将DefaultResponsiveTextMessageDelegateMessageListenerAdapter结合使用,则从执行'receive(..)'方法返回的任何 non-null value(在默认 configuration 中)都会转换为TextMessage。然后将结果TextMessage发送到原始Message的 JMS Reply-To property 中定义的Destination(如果存在)或MessageListenerAdapter上的默认Destination(如果已配置)。如果没有找到Destination,则抛出InvalidDestinationException(请注意,不会吞下此 exception 并向上传播调用堆栈)。

在Transactions中处理消息

在 transaction 中调用消息 listener 只需要重新配置 listener 容器。

您可以通过 listener 容器定义上的sessionTransacted flag 激活本地资源 transactions。然后,每个消息 listener 调用都在 active JMS transaction 中运行,并且在 listener 执行失败的情况下回滚消息。发送响应消息(通过SessionAwareMessageListener)是同一本地 transaction 的一部分,但任何其他资源操作(例如数据库访问)都是独立运行的。这通常需要在 listener implementation 中检测重复的消息,以涵盖数据库处理已提交但消息处理无法提交的情况。

考虑以下 bean 定义:

1
2
3
4
5
6
<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>

要参与外部管理的 transaction,您需要配置 transaction manager 并使用支持外部托管 transactions 的 listener 容器(通常为DefaultMessageListenerContainer)。

要为 XA transaction 参与配置消息 listener 容器,您需要配置JtaTransactionManager(默认情况下,委托给 Java EE 服务器的 transaction 子系统)。请注意,底层 JMS ConnectionFactory需要 XA-capable 并正确注册到您的 JTA transaction 协调器。 (检查您的 Java EE 服务器的 JNDI resources.)的配置这使得消息接收以及(对于 example)数据库访问成为同一 transaction 的一部分(具有统一提交语义,代价是 XA transaction log 开销)。

以下 bean 定义创建 transaction manager:

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

然后我们需要将它添加到我们早期的容器 configuration 中。容器负责 rest。以下 example 显示了如何执行此操作:

1
2
3
4
5
6
<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 我们的 transaction manager。

支持JCA消息Endpoints

从 version 2.5 开始,Spring 还为 JCA-based MessageListener容器提供支持。 JmsMessageEndpointManager尝试从提供程序的ResourceAdapter class name 自动确定ActivationSpec class name。因此,通常可以提供 Spring 的通用JmsActivationSpecConfig,如下面的示例所示:

1
2
3
4
5
6
7
8
9
<bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
<property name="resourceAdapter" ref="resourceAdapter"/>
<property name="activationSpecConfig">
<bean class="org.springframework.jms.listener.endpoint.JmsActivationSpecConfig">
<property name="destinationName" value="myQueue"/>
</bean>
</property>
<property name="messageListener" ref="myMessageListener"/>
</bean>

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

1
2
3
4
5
6
7
8
9
10
<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,如下面的 example 所示:

1
2
3
4
5
6
7
8
9
10
<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也可以指向 environment-specific 线程池 - 通常通过SimpleTaskWorkManager实例的asyncTaskExecutor property。如果碰巧使用多个适配器,请考虑为所有ResourceAdapter实例定义共享线程池。

在某些环境(例如 WebLogic 9 或更高版本)中,您可以从 JNDI 获取整个ResourceAdapter object(通过使用<jee:jndi-lookup>)。然后 Spring-based 消息 listeners 可以与 server-hosted ResourceAdapter交互,后者也使用服务器的 built-in WorkManager

有关更多详细信息,请参阅 javadoc 以获取JmsMessageEndpointManagerJmsActivationSpecConfigResourceAdapterFactoryBean

Spring 还提供了一个与 JMS 无关的通用 JCA 消息端点 manager:org.springframework.jca.endpoint.GenericMessageEndpointManager。此 component 允许使用任何消息 listener 类型(例如 CCI MessageListener)和任何 provider-specific ActivationSpec object。请参阅 JCA 提供程序的文档以了解连接器的实际功能,并参阅GenericMessageEndpointManager javadoc 以获取 Spring-specific configuration 详细信息。

JCA-based 消息端点 management 与 EJB 2.1 Message-Driven Beans 非常类似。它使用相同的底层资源提供程序 contract。与 EJB 2.1 MDB 一样,您也可以在 Spring context 中使用 JCA 提供程序支持的任何消息 listener 接口。尽管如此,Spring 仍然为 JMS 提供了明确的“便利”支持,因为 JMS 是与 JCA 端点 management contract 一起使用的最常见的端点 API。

Annotation-driven的监听器Endpoints

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

1
2
3
4
5
6
@Component
public class MyService {

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

前面 example 的 idea 是,每当javax.jms.Destination myDestination上有消息可用时,相应地调用processOrder方法(在这种情况下,使用 JMS 消息的内容,类似于MessageListenerAdapter提供的内容)。

带注释的端点基础结构通过使用JmsListenerContainerFactory为每个带注释的方法在后台创建消息 listener 容器。这样的容器没有针对 application context 注册,但可以通过使用JmsListenerEndpointRegistry bean 轻松定位以便管理。

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

启用监听器Endpoints注解

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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 作为工厂用于创建消息 listener 容器的源。在这种情况下(并忽略 JMS 基础结构设置),您可以调用processOrder方法,其核心轮询大小为三个线程,最大池大小为十个线程。

您可以自定义 listener 容器工厂以用于每个 annotation,也可以通过实现JmsListenerConfigurer接口来配置显式默认值。仅当在没有特定容器工厂的情况下注册了至少一个端点时,才需要默认值。有关详细信息和示例,请参阅实现JmsListenerConfigurer的 classes 的 javadoc。

如果您更喜欢XML configuration,则可以使用<jms:annotation-driven>元素,如下面的 example 所示:

1
2
3
4
5
6
7
8
9
<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>

程序化端点注册

JmsListenerEndpoint提供 JMS 端点的 model,负责为该 model 配置容器。除了JmsListener annotation 检测到的 endpoints 之外,基础结构还允许您以编程方式配置 endpoints。以下 example 显示了如何执行此操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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);
}
}

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

请注意,您可以完全跳过@JmsListener的使用,并通过JmsListenerConfigurer以编程方式仅注册 endpoints。

带注解的端点方法签名

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

1
2
3
4
5
6
7
8
@Component
public class MyService {

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

您可以在 JMS listener endpoints 中输入的主要元素如下:

  • 原始javax.jms.Message或其任何子类(假设它与传入的消息类型匹配)。
  • javax.jms.Session用于对本机 JMS API 的可选访问(对于 example,用于发送自定义回复)。
  • org.springframework.messaging.Message表示传入的 JMS 消息。请注意,此消息包含自定义和标准 headers(由JmsHeaders定义)。
  • @Header -annotated 方法 arguments 提取特定的头 value,包括标准的 JMS headers。
  • 一个@Headers -annotated 参数,也必须可分配给java.util.Map才能访问所有 headers。
  • 不是受支持类型(MessageSession)之一的 non-annotated 元素被视为有效负载。您可以通过使用@Payload注释参数来使其明确。您还可以通过添加额外的@Valid来启用验证。

inject Spring Message抽象的能力对于从 transport-specific 消息中存储的所有信息中受益特别有用,而不依赖于 transport-specific API。以下 example 显示了如何执行此操作:

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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;
}
}

响应管理

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

假设我们的processOrder方法现在应该_ret ,我们可以编写它来自动发送响应,如下面的 example 显示:

1
2
3
4
5
6
@JmsListener(destination = "myDestination")
@SendTo("status")
public OrderStatus processOrder(Order order) {
// order processing
return status;
}

如果您有多个@JmsListener -annotated 方法,还可以将@SendTo annotation 放在 class level 上以共享默认回复目标。

如果您需要以 transport-independent 方式设置其他 headers,则可以使用类似于以下的方法_ret

1
2
3
4
5
6
7
8
9
@JmsListener(destination = "myDestination")
@SendTo("status")
public Message<OrderStatus> processOrder(Order order) {
// order processing
return MessageBuilder
.withPayload(status)
.setHeader("code", 1234)
.build();
}

如果需要在运行时计算响应目标,可以将响应封装在JmsResponse实例中,该实例还提供在运行时使用的目标。我们可以 rewrite 前面的 example 如下:

1
2
3
4
5
6
7
8
9
@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 值,例如优先级或生存的 time,则可以相应地配置JmsListenerContainerFactory,如下面的 example 所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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;
}
}

JMS命名空间支持

Spring 提供了一个 XML 命名空间,用于简化 JMS configuration。要使用 JMS 名称空间元素,您需要 reference JMS schema,如下面的 example 所示:

1
2
3
4
5
6
7
8
9
10
11
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms.xsd">

<!-- bean definitions here -->

</beans>
1 引用 JMS schema。

命名空间由三个 top-level 元素组成:允许使用annotation-driven listener endpoints。 定义共享 listener 容器 configuration,并且可以包含 child 元素。以下 example 显示了两个 listeners 的基本 configuration:

1
2
3
4
5
6
7
<jms:listener-container>

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

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

</jms:listener-container>

前面的 example 相当于创建两个不同的 listener 容器 bean 定义和两个不同的MessageListenerAdapter bean 定义,如使用 MessageListenerAdapter所示。除了前面的 example 中显示的属性之外,listener元素还可以包含几个可选的元素。以下 table 描述了所有可用属性:

属性 描述
id 托管 listener 容器的 bean name。如果未指定,则自动生成 bean name。
destination(必填) 此 listener 的目标 name,通过DestinationResolver策略解析。
ref(必填) 处理程序 object 的 bean name。
method 要调用的处理程序方法的 name。如果ref属性指向MessageListener或 Spring SessionAwareMessageListener,则可以省略此属性。
response-destination 要向其发送响应消息的默认响应目标的 name。这适用于请求消息不带JMSReplyTo字段的情况。此目标的类型由 listener-container 的response-destination-type属性确定。请注意,这仅适用于具有 return value 的 listener 方法,每个结果 object 都会转换为响应消息。
subscription 持久订阅的 name(如果有)。
selector 此 listener 的可选消息选择器。
concurrency 要为此 listener 启动的并发会话或使用者数。此值可以是指示最大数字的简单数字(对于 example,5),也可以是指示较低和上限的范围(对于 example,3-5)。请注意,指定的最小值只是一个提示,可能在运行时被忽略。默认值是容器提供的 value。

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

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

1
2
3
4
5
6
7
8
9
10
11
<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>

以下 table 描述了所有可用属性。有关各个 properties 的更多详细信息,请参阅用 AbstractMessageListenerContainer的 class-level javadoc 及其具体子类。 javadoc 还提供了 transaction 选择和消息重新传递方案的讨论。

属性 描述
container-type 这个 listener 容器的类型。可用选项为defaultsimpledefault102simple102(默认选项为default)。
container-class 自定义 listener 容器 implementation class 作为完全限定的 class name。根据container-type属性,默认值为 Spring 的标准DefaultMessageListenerContainerSimpleMessageListenerContainer
factory-id 将此元素定义的设置公开为具有指定idJmsListenerContainerFactory,以便它们可以与其他 endpoints 一起使用。
connection-factory __ JMS ConnectionFactory bean 的引用(默认 bean name 是connectionFactory)。
task-executor JMS listener 调用者的 Spring TaskExecutor的引用。
destination-resolver 用于解析 JMS Destination实例的DestinationResolver策略的引用。
message-converter 用于将 JMS 消息转换为 listener 方法 arguments 的MessageConverter策略的 reference。默认值为SimpleMessageConverter
error-handler 策略的引用,用于处理在执行MessageListener期间可能发生的任何未捕获的 exceptions。
destination-type 此 listener 的 JMS 目标类型:queuetopicdurableTopicsharedTopicsharedDurableTopic。这可能会启用容器的pubSubDomainsubscriptionDurablesubscriptionShared properties。默认值为queue(禁用这三个 properties)。
response-destination-type 响应的 JMS 目标类型:queuetopic。默认值是destination-type属性的 value。
client-id 此 listener 容器的 JMS client ID。您必须在使用持久订阅时指定它。
cache JMS 资源的缓存 level:noneconnectionsessionconsumerauto。默认情况下(auto),cache level 实际上是consumer,除非指定了外部 transaction manager - 在这种情况下,有效默认值为none(假设 Java EE-style transaction management,其中给定的 ConnectionFactory 是 XA-aware 池)。
acknowledge 本机 JMS 确认模式:autoclientdups-oktransacted。 的值激活本地交易的Session。作为替代方法,您可以指定属性,稍后将在 table 中进行说明。默认值为auto
transaction-manager 对外部PlatformTransactionManager的引用(通常是 XA-based transaction 协调器,例如 Spring 的JtaTransactionManager)。如果未指定,则使用本机确认(请参阅acknowledge属性)。
concurrency 每个 listener 启动的并发会话或使用者数。它可以是指示最大数字的简单数字(对于 example,5),也可以是指示下限和上限的范围(对于 example,3-5)。请注意,指定的最小值只是一个提示,可能在运行时被忽略。默认值为1。在 topic listener 的情况下,或者如果队列 ordering 很重要,您应该将并发限制为1。考虑将其提升为一般队列。
prefetch 要加载到单个 session 中的最大消息数。请注意,提高此数字可能会导致并发消费者的饥饿。
receive-timeout 用于 receive calls 的超时(以毫秒为单位)。默认值为1000(一秒)。 -1表示没有超时。
back-off 指定用于计算恢复尝试之间间隔的BackOff实例。如果BackOffExecution implementation 返回BackOffExecution#STOP,则 listener 容器不会进一步尝试恢复。设置此 property 时,将忽略recovery-interval value。默认值为FixedBackOff,间隔为 5000 毫秒(即 5 秒)。
recovery-interval 指定恢复尝试之间的间隔(以毫秒为单位)。它提供了一种使用指定间隔创建FixedBackOff的便捷方法。有关更多恢复选项,请考虑指定BackOff实例。默认值为 5000 毫秒(即 5 秒)。
phase 此容器应开始和停止的生命周期阶段。 value 越低,此容器启动越早,后者停止。默认值为Integer.MAX_VALUE,表示容器尽可能晚启动并尽快停止。

使用jms schema 支持配置 JCA-based listener 容器非常相似,如下面的 example 所示:

1
2
3
4
5
6
7
8
<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>

以下 table 描述了 JCA 变体的可用 configuration 选项:

属性 描述
factory-id 将此元素定义的设置公开为具有指定idJmsListenerContainerFactory,以便它们可以与其他 endpoints 一起使用。
resource-adapter _JCA ResourceAdapter bean 的引用(默认 bean name 是resourceAdapter)。
activation-spec-factory JmsActivationSpecFactory的参考。缺省情况是自动检测 JMS 提供程序及其ActivationSpec class(请参阅DefaultJmsActivationSpecFactory)。
destination-resolver 策略的参考用于解析 JMS Destinations
message-converter 用于将 JMS 消息转换为 listener 方法 arguments 的MessageConverter策略的 reference。默认值为SimpleMessageConverter
destination-type 此 listener 的 JMS 目标类型:queuetopicdurableTopicsharedTopic。或sharedDurableTopic。这可能会启用容器的pubSubDomainsubscriptionDurablesubscriptionShared properties。默认值为queue(禁用这三个 properties)。
response-destination-type 响应的 JMS 目标类型:queuetopic。默认值是destination-type属性的 value。
client-id 此 listener 容器的 JMS client ID。在使用持久订阅时需要指定它。
acknowledge 本机 JMS 确认模式:autoclientdups-oktransacted。 的值激活本地交易的Session。作为替代方法,您可以指定稍后描述的transaction-manager属性。默认值为auto
transaction-manager _Spring JtaTransactionManagerjavax.transaction.TransactionManager的引用,用于为每个传入消息启动 XA transaction。如果未指定,则使用本机确认(请参阅acknowledge属性)。
concurrency 每个 listener 启动的并发会话或使用者数。它可以是指示最大数字的简单数字(对于 example 5),也可以是指示下限和上限的范围(对于 example,3-5)。请注意,指定的最小值只是一个提示,在运行时通常会在使用 JCA listener 容器时被忽略。默认值为 1。
prefetch 要加载到单个 session 中的最大消息数。请注意,提高此数字可能会导致并发消费者的饥饿。

JMX

Spring 中的 JMX(Java Management Extensions)支持提供了 features,使您可以轻松,透明地将 Spring application 集成到 JMX 基础结构中。

JMX?

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

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

  • 将任何 Spring bean 自动注册为 JMX MBean。
  • 一种灵活的机制,用于控制 beans 的 management 接口。
  • MBean 在 remote,JSR-160 连接器上的声明性暴露。
  • 本地和 remote MBean 资源的简单代理。

这些 features 旨在在不将 application 组件耦合到 Spring 或 JMX 接口和 classes 的情况下工作。实际上,在大多数情况下,_ application classes 不需要知道 order 中的 Spring 或 JMX 以利用 Spring JMX features。

将Beans导出到JMX

Spring 的 JMX framework 中的核心 class 是MBeanExporter。此 class 负责获取 Spring beans 并使用 JMX MBeanServer注册它们。对于 example,请考虑以下 class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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 的 properties 和方法公开为 MBean 的属性和操作,可以在 configuration 文件中配置MBeanExporter class 的实例并传入 bean,如下面的 example 所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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>

前面的 configuration 片段中的相关 bean 定义是exporter bean。 beans property 告诉MBeanExporter究竟哪些 beans 必须导出到 JMX MBeanServer。在默认的 configuration 中,beans Map中每个条目的 key 用作相应条目 value 引用的 bean 的ObjectName。您可以更改此行为,如控制 Beans 的 ObjectName 实例中所述。

通过此 configuration,testBean bean 在ObjectName bean:name=testBean1下显示为 MBean。默认情况下,bean 的所有public properties 都作为属性公开,并且所有public方法(从Object class 继承的方法除外)都作为操作公开。

MBeanExporterLifecycle bean(见启动和关闭回调)。默认情况下,MBean 在 application 生命周期中尽可能晚地导出。您可以通过设置autoStartup flag 来配置 export 发生的phase或禁用自动注册。

创建MBeanServer

前一节中显示的 configuration 假定 application 在一个(且只有一个)MBeanServer已经 running 的环境中运行。在这种情况下,Spring 会尝试找到 running MBeanServer并在该服务器上注册 beans(如果有的话)。当 application 在具有自己的MBeanServer的容器(例如 Tomcat 或 IBM WebSphere)内运行时,此行为很有用。

但是,这种方法在独立环境中或在不提供MBeanServer的容器内运行时没有用处。要解决此问题,您可以通过在 configuration 中添加org.springframework.jmx.support.MBeanServerFactoryBean class 的实例来声明性地创建MBeanServer实例。您还可以通过将MBeanExporter实例的server property 的 value 设置为MBeanServerFactoryBean返回的MBeanServer value 来确保使用特定的MBeanServer,如下面的 example 所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<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>

在前面的 example 中,MBeanServer的一个实例由MBeanServerFactoryBean创建,并通过server property 提供给MBeanExporter。当您提供自己的MBeanServer实例时,MBeanExporter不会尝试找到 running MBeanServer并使用提供的MBeanServer实例。为了使其正常工作,您必须在 classpath 上使用 JMX implementation。

重用现有的MBeanServer

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

1
2
3
4
5
6
7
8
9
10
11
12
<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,如下面的 example 所示:

1
2
3
4
5
6
7
8
9
10
11
<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>

Lazily初始化MBean

如果配置 bean 并且也配置为延迟初始化,则MBeanExporter不会 break 这个 contract 并避免实例化 bean。相反,它使用MBeanServer注册代理并推迟从容器中获取 bean,直到代理上的第一次调用发生。

MBean的自动注册

通过MBeanExporter导出并且已经是有效 MBean 的任何 beans 都会使用MBeanServer注册 as-is,而无需 Spring 的进一步干预。您可以通过将autodetect property 设置为true来使MBeanExporter自动检测到 MBean,如下面的 example 所示:

1
2
3
4
5
<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"/>

在前面的 example 中,bean 已经是一个有效的 JMX MBean,并由 Spring 自动注册。默认情况下,为 JMX 注册自动检测的 bean 将 bean name 用作ObjectName。您可以覆盖此行为,如控制 Beans 的 ObjectName 实例中所述。

控制注册行为

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

您可以准确控制使用MBeanServer注册时会发生什么。当注册 process 发现MBean已经在相同的ObjectName下注册时,Spring 的 JMX 支持允许三种不同的注册行为来控制注册行为。以下 table 总结了这些注册行为:

注册行为 说明
FAIL_ON_EXISTING 这是默认的注册行为。如果MBean实例已在相同的ObjectName下注册,则正在注册的MBean未注册,并且抛出InstanceAlreadyExistsException。现有的MBean不受影响。
IGNORE_EXISTING 如果MBean实例已在同一ObjectName下注册,则正在注册的MBean未注册。现有的MBean不受影响,并且不会抛出Exception。这在多个 applications 想要在共享MBeanServer中共享 common MBean的设置中很有用。
REPLACE_EXISTING 如果MBean实例已在同一ObjectName下注册,则先前注册的现有MBean将被取消注册,并且新MBean将在其位置注册(新MBean将有效替换先前的实例)。

前面 table 中的值被定义为RegistrationPolicy class 上的枚举。如果要更改默认注册行为,则需要将MBeanExporter定义上的registrationPolicy property 的 value 设置为其中一个值。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<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>

控制Beans的Management 接口

前一节的 example 中,您几乎无法控制 bean 的 management 接口。每个导出的 bean 的所有public properties 和方法分别作为 JMX 属性和操作公开。要实际控制导出的 beans 的哪些 properties 和方法实际上作为 JMX 属性和操作公开,Spring JMX 提供了一个全面且可扩展的机制来控制 beans 的 management 接口。

使用MBeanInfoAssembler接口

在幕后,MBeanExporter委托org.springframework.jmx.export.assembler.MBeanInfoAssembler接口的实现,负责定义公开的每个 bean 的 management 接口。默认实现org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler定义了一个 management 接口,它公开了所有公共 properties 和方法(正如您在前面几节的示例中所看到的)。 Spring 提供了另外两个MBeanInfoAssembler接口的实现,它允许您使用 source-level 元数据或任意接口控制生成的 management 接口。

使用Source-level元数据:Java注解

通过使用MetadataMBeanInfoAssembler,您可以使用 source-level 元数据为 beans 定义 management 接口。元数据的读取由org.springframework.jmx.export.metadata.JmxAttributeSource接口封装。 Spring JMX 提供了一个使用 Java annotations 的默认 实现,即org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource。您必须使用JmxAttributeSource接口的 实现实例配置MetadataMBeanInfoAssembler才能正确功能(没有默认值)。

要将 export 标记为 export 到 JMX,您应该使用ManagedResource annotation 注释 bean class。必须使用ManagedOperation annotation 将要公开的每个方法标记为操作,并使用ManagedAttribute annotation 标记要显示的每个 property。标记 properties 时,可以省略 getter 或 setter 的 annotation 以分别创建 write-only 或 read-only 属性。

ManagedResource -annotated bean 必须是公共的,暴露操作或属性的方法也必须是公共的。

以下 example 显示了我们在Creating MBeanServer中使用的JmxTestBean class 的带注释的 version:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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();
}

}

在前面的 example 中,您可以看到JmxTestBean class 标有ManagedResource annotation,并且此ManagedResource annotation 配置了一组 properties。这些 properties 可用于配置由MBeanExporter生成的 MBean 的各个方面,稍后将在Source-level 元数据类型中进行更详细的说明。

agename properties 都使用ManagedAttribute annotation 注释,但是在age property 的情况下,只标记了 getter。这会导致这两个 properties 作为属性包含在 management 接口中,但age属性为 read-only。

最后,add(int, int)方法标有ManagedOperation属性,而dontExposeMe()方法不标记。这会导致 management 接口在使用MetadataMBeanInfoAssembler时仅包含一个操作(add(int, int))。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<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>

在前面的 example 中,MetadataMBeanInfoAssembler bean 已配置了AnnotationJmxAttributeSource class 的实例,并通过汇编程序 property 传递给MBeanExporter。这就是为 Spring-exposed MBeans 利用 metadata-driven management 接口所需的全部内容。

Source-level元数据类型

以下 table 描述了 Spring JMX 中可用的 source-level 元数据类型:

目的 注解 注释类型
Class的所有实例标记为 JMX 托管资源。 @ManagedResource
将方法标记为 JMX 操作。 @ManagedOperation 方法
将 getter 或 setter 标记为 JMX 属性的一半。 @ManagedAttribute 方法(只有 getter 和 setter)
定义操作参数的描述。 @ManagedOperationParameter@ManagedOperationParameters 方法

以下 table 描述了可用于这些 source-level 元数据类型的 configuration 参数:

参数 描述 适用于
ObjectName MetadataNamingStrategy用于确定受管资源的ObjectName ManagedResource
description 设置资源,属性或操作的友好描述。 ManagedResourceManagedAttributeManagedOperationManagedOperationParameter
currencyTimeLimit 设置currencyTimeLimit描述符字段的 value。 ManagedResourceManagedAttribute
defaultValue 设置defaultValue描述符字段的 value。 ManagedAttribute
log 设置log描述符字段的 value。 ManagedResource
logFile 设置logFile描述符字段的 value。 ManagedResource
persistPolicy 设置persistPolicy描述符字段的 value。 ManagedResource
persistPeriod 设置persistPeriod描述符字段的 value。 ManagedResource
persistLocation 设置persistLocation描述符字段的 value。 ManagedResource
persistName 设置persistName描述符字段的 value。 ManagedResource
name 设置操作参数的 display name。 ManagedOperationParameter
index 设置操作参数的索引。 ManagedOperationParameter

使用AutodetectCapableMBeanInfoAssembler接口

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

AutodetectCapableMBeanInfo接口的唯一 implementation 是MetadataMBeanInfoAssembler,它投票包括任何用ManagedResource属性标记的 bean。在这种情况下,默认方法是使用 bean name 作为ObjectName,这将导致类似于以下的 configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<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>

请注意,在前面的 configuration 中,没有 beans 传递给MBeanExporter。但是,JmxTestBean仍然是已注册的,因为它标有ManagedResource属性,MetadataMBeanInfoAssembler检测到这一点并投票包含它。这种方法的唯一问题是JmxTestBean的 name 现在具有商业意义。您可以通过更改控制 Beans 的 ObjectName 实例中定义的ObjectName创建的默认行为来解决此问题。

使用Java接口定义Management接口

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

尽管公开 MBean 的标准机制是使用接口和简单的命名 scheme,但InterfaceBasedMBeanInfoAssembler通过消除对命名约定的需要来扩展此功能,允许您使用多个接口并且不需要 beans 来实现 MBean 接口。

考虑以下接口,该接口用于为我们之前显示的JmxTestBean class 定义 management 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 上作为操作和属性公开的方法和 properties。以下 code 显示如何配置 Spring JMX 以将此接口用作 management 接口的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<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>

在前面的 example 中,InterfaceBasedMBeanInfoAssembler被配置为在为任何 bean 构造 management 接口时使用IJmxTestBean接口。重要的是要理解InterfaceBasedMBeanInfoAssembler处理的 beans 不需要实现用于生成 JMX management 接口的接口。

在前面的例子中,IJmxTestBean接口用于构造所有 beans 的所有 management 接口。在许多情况下,这不是所需的行为,您可能希望为不同的 beans 使用不同的接口。在这种情况下,您可以通过interfaceMappings property 传递InterfaceBasedMBeanInfoAssembler Properties实例,其中每个条目的 key 是 bean name,每个条目的 value 是用于该 bean 的接口名称列表。

如果没有通过managedInterfacesinterfaceMappings properties 指定 management 接口,InterfaceBasedMBeanInfoAssembler将反映在 bean 上,并使用该 bean 实现的所有接口来创建 management 接口。

使用MethodNameBasedMBeanInfoAssembler

MethodNameBasedMBeanInfoAssembler允许您指定作为属性和操作向 JMX 公开的方法名称列表。以下 code 显示 sample configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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>

在前面的 example 中,您可以看到addmyOperation方法作为 JMX 操作公开,而getName()setName(String)getAge()公开为 JMX 属性的适当一半。在前面的 code 中,方法映射适用于暴露给 JMX 的 beans。要在 bean-by-bean 的基础上控制方法曝光,可以使用

控制Beans的ObjectName实例

在幕后,MBeanExporter委托ObjectNamingStrategy的_i实现为它注册的每个 beans 获取一个ObjectName实例。默认情况下,默认 implementation,KeyNamingStrategy使用beans Map的 key 作为ObjectName。此外,KeyNamingStrategy可以将beans Map的 key 映射到Properties文件(或 files)中的条目以解析ObjectName。除了KeyNamingStrategy之外,Spring 还提供了两个额外的ObjectNamingStrategy __mplement:IdentityNamingStrategy(基于 bean 的 JVM 标识构建ObjectName)和MetadataNamingStrategy(使用 source-level 元数据来获取ObjectName)。

从Properties读取ObjectName实例

您可以配置自己的KeyNamingStrategy实例并将其配置为从Properties实例读取ObjectName实例,而不是使用 bean key。 KeyNamingStrategy尝试使用与 bean key 对应的 key 在Properties中找到一个条目。如果未找到任何条目或Properties实例为null,则使用 bean key 本身。

以下 code 显示KeyNamingStrategy的 sample configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<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>

前面的 example 配置KeyNamingStrategy的实例,其中Properties实例与映射 property 定义的Properties实例和位于映射 property 定义的_path 中的 properties files 合并。在此 configuration 中,testBean bean 被赋予ObjectName bean:name=testBean1,因为这是Properties实例中具有与 bean key 对应的 key 的条目。

如果找不到Properties实例中的条目,则 bean key name 用作ObjectName

使用MetadataNamingStrategy

MetadataNamingStrategy在每个 bean 上使用ManagedResource属性的objectName property 来创建ObjectName。以下 code 显示了MetadataNamingStrategy的 configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<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>

如果没有为ManagedResource属性提供objectName,则使用以下格式创建ObjectName:[1274] :type = [1275], name = [1276]。对于 example,以下 bean 生成的ObjectName将为com.example:type=MyClass,name=myBean

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

基于注解的MBeanExport配置

如果您更喜欢使用annotation-based 方法来定义 management 接口,则可以使用MBeanExporter的便捷子类:AnnotationMBeanExporter。定义此子类的实例时,不再需要namingStrategyassemblerattributeSource configuration,因为它始终使用标准 Java annotation-based 元数据(自动检测也始终启用)。实际上,@EnableMBeanExport 注释支持更简单的语法,而不是定义MBeanExporter bean,如下面的 example 所示:

1
2
3
4
5
@Configuration
@EnableMBeanExport
public class AppConfig {

}

如果您更喜欢 XML-based configuration,则<context:mbean-export/>元素具有相同的用途,如下所示:

1
<context:mbean-export/>

如有必要,您可以为特定 MBean server提供 reference,defaultDomain属性(AnnotationMBeanExporter的 property)接受生成的 MBean ObjectName域的备用 value。这用于代替类 MetadataNamingStrategy上一节中描述的完全限定的包 name,如下面的 example 所示:

1
2
3
4
5
@EnableMBeanExport(server="myMBeanServer", defaultDomain="myDomain")
@Configuration
ContextConfiguration {

}

以下 example 显示了前面的 annotation-based example 的 XML 等价物:

1
<context:mbean-export server="myMBeanServer" default-domain="myDomain"/>

不要将 interface-based AOP 代理与 bean classes 中的 JMX annotations 自动检测结合使用。 Interface-based proxies“隐藏”目标 class,它也隐藏了 JMX-managed 资源注释。因此,在这种情况下你应该使用 target-class 代理(通过在<aop:config/><tx:annotation-driven/>等上设置’proxy-target-class’flag)。否则,您的 JMX beans 可能会在启动时被忽略。

使用JSR-160连接器

对于 remote 访问,Spring JMX 模块在org.springframework.jmx.support包中提供两个FactoryBean implementations,用于创建 server-和 client-side 连接器。

Server-side连接器

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

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

默认情况下,ConnectorServerFactoryBean创建JMXConnectorServer绑定到service:jmx:jmxmp://localhost:9875serverConnector bean 因此通过 localhost,port 9875 上的 JMXMP 协议公开本地MBeanServer到 clients。请注意,JSR 160 规范将 JMXMP 协议标记为可选。目前,主要的 open-source JMX implementation,MX4J 和 JDK 提供的那个不支持 JMXMP。

要指定另一个 URL 并使用MBeanServer注册JMXConnectorServer本身,可以分别使用serviceUrlObjectName properties,如下面的 example 所示:

1
2
3
4
5
6
<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 property,Spring 会自动将连接器注册到ObjectName下的MBeanServer。以下 example 显示了_创建JMXConnector时可以传递给ConnectorServerFactoryBean的完整参数集:

1
2
3
4
5
6
7
8
9
10
11
12
13
<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-based 连接器时,需要在 order 中启动查找服务(tnameservrmiregistry)才能完成 name 注册。如果您通过 RMI 使用 Spring 来 export remote 服务,Spring 已经构建了一个 RMI 注册表。如果没有,您可以使用以下 configuration 片段轻松启动注册表:

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

Client-side连接器

要创建MBeanServerConnection到 remote JSR-160-enabled MBeanServer,可以使用MBeanServerConnectionFactoryBean,如下面的 example 所示:

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

用Hessian或SOAP代替JMX

JSR-160 允许 extensions 到 client 和服务器之间进行通信的方式。前面部分中显示的示例使用 JSR-160 规范(IIOP 和 JRMP)和(可选)JMXMP 所需的强制 RMI-based implementation。通过使用其他提供程序或 JMX implementations(例如MX4J),您可以利用 SOAP 或 Hessian 等协议而不是简单的 HTTP 或 SSL 等,如下面的示例所示:

1
2
3
4
<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 文档。

通过代理访问MBean

Spring JMX 允许您创建 re-route calls 到在本地或 remote MBeanServer中注册的 MBean 的代理。这些代理为您提供了标准的 Java 接口,您可以通过它与 MBean 进行交互。以下 code 显示如何在本地MBeanServer中为 MBean running 配置代理:

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

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

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

1
2
3
4
5
6
7
8
9
10
<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的 remote 机器。然后将MBeanServerConnection通过server property 传递给MBeanProxyFactoryBean。创建的代理通过此MBeanServerConnection将所有调用转发给MBeanServer

通知

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

为通知注册Listeners

Spring 的 JMX 支持可以轻松地向任意数量的 MBean 注册任意数量的NotificationListeners(这包括 Spring 的MBeanExporter导出的 MBean 和通过其他机制注册的 MBean)。例如,考虑一个场景,其中一个人希望(通过Notification)每个 time 一个目标 MBean 的属性发生变化。以下 example 将通知写入 console:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.example;

import javax.management.AttributeChangeNotification;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;

public class ConsoleLoggingNotificationListener
implements NotificationListener, NotificationFilter {

public void handleNotification(Notification notification, Object handback) {
System.out.println(notification);
System.out.println(handback);
}

public boolean isNotificationEnabled(Notification notification) {
return AttributeChangeNotification.class.isAssignableFrom(notification.getClass());
}

}

以下 example 将ConsoleLoggingNotificationListener(在前面的 example 中定义)添加到notificationListenerMappings

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<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>

使用前面的 configuration,每隔 time JMX Notification从目标 MBean(bean:name=testBean1)broadcast,通过notificationListenerMappings property 注册为 listener 的ConsoleLoggingNotificationListener bean 被通知。然后ConsoleLoggingNotificationListener bean 可以采取它认为适当的任何行动来响应Notification

您还可以使用直 bean 名称作为导出的 beans 和 listeners 之间的链接,如下面的 example 所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<beans>

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

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

</beans>

如果要为封闭MBeanExporter导出的所有 beans 注册单个NotificationListener实例,则可以使用特殊通配符(*)作为notificationListenerMappings property map 中条目的 key,如下面的 example 所示:

1
2
3
4
5
6
7
<property name="notificationListenerMappings">
<map>
<entry key="*">
<bean class="com.example.ConsoleLoggingNotificationListener"/>
</entry>
</map>
</property>

如果需要执行逆操作(即,针对 MBean 注册多个不同的 listeners),则必须使用notificationListeners list property(优先于notificationListenerMappings property)。这个 time,而不是为单个 MBean 配置NotificationListener,我们配置NotificationListenerBean实例。 封装了一个NotificationListenerObjectName(或ObjectNames),它将在MBeanServer中注册。 NotificationListenerBean还封装了许多其他 properties,例如NotificationFilter和可在高级 JMX 通知方案中使用的任意 handback object。

使用NotificationListenerBean实例时的 configuration 与之前提供的内容没有太大的不同,如下面的示例所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<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>

前面的 example 等同于第一个通知 example。那么假设我们希望每_一次被提出一个回传 object,并且我们也希望通过提供NotificationFilter来过滤掉无关的Notifications。以下 example 实现了以下目标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<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 object 是什么以及NotificationFilter是什么的完整讨论,请参阅标题为’JMX Notification Model’的 JMX 规范(1.2)部分.)

发布通知

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

此部分实际上仅与通过MBeanExporter公开为 MBean 的 Spring-managed beans 相关。任何现有的 user-defined MBean 都应使用标准 JMX API 进行通知发布。

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

正如NotificationPublisher接口的 javadoc 中所述,通过NotificationPublisher机制发布 events 的托管 beans 不负责通知 listeners 的 state management。 Spring 的 JMX 支持负责处理所有 JMX 基础架构问题。作为 application 开发人员,您需要做的就是实现NotificationPublisherAware接口并使用提供的NotificationPublisher实例开始发布 events。请注意,是在使用MBeanServer注册托管 bean 之后设置的。

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

在下面的示例中,JmxTestBean的导出实例每 time发布一次NotificationEventadd(int, int)操作被调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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 支持的更好的 features 之一。但是,它确实带有将 classes 与 Spring 和 JMX 耦合的价格标签。一如既往,这里的建议是务实。如果您需要NotificationPublisher提供的功能,并且您可以接受与 Spring 和 JMX 的耦合,那么请执行此操作。

更多资源

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

JCA-CCI

Java EE 提供了一个规范来标准化对企业信息系统(EIS)的访问:JCA(Java EE Connector Architecture)。该规范分为两个不同的部分:

  • 连接器提供程序必须实现的 SPI(服务提供程序接口)。这些接口构成可以部署在 Java EE application 服务器上的资源适配器。在这种情况下,服务器管理连接池,transactions 和安全性(托管模式)。 application 服务器还负责管理 configuration,它位于 client application 之外。也可以在没有 application 服务器的情况下使用连接器。在这种情况下,application 必须直接配置它(non-managed 模式)。
  • CCI(Common Client Interface), application 可用于与连接器交互,从而与 EIS 通信。还提供了用于本地 transaction 划分的 API。

Spring CCI 支持的目的是使用 Spring Framework 的一般资源和 transaction management 工具提供 classes 以典型的 Spring 样式访问 CCI 连接器。

连接器的 client 端并不总是使用 CCI。某些连接器公开自己的 API,提供 JCA 资源适配器以使用 Java EE 容器的系统 contracts(连接池,global transactions 和安全性)。 Spring 不为此类 connector-specific API 提供特殊支持。

配置CCI

本节介绍如何配置 Common Client 接口(CCI)。它包括以下主题:

连接器Configuration

使用 JCA CCI 的基本资源是ConnectionFactory接口。您使用的连接器必须提供此接口的 implementation。

要使用连接器,可以将其部署在 application 服务器上,并从服务器的 JNDI 环境(托管模式)获取ConnectionFactory。必须将连接器打包为 RAR 文件(资源适配器归档文件),并包含ra.xml文件以描述其部署特征。部署资源时指定资源的实际 name。要在 Spring 中访问它,可以使用 Spring 的JndiObjectFactoryBean<jee:jndi-lookup>通过其 JNDI name 获取工厂。

使用连接器的另一种方法是将其嵌入到 application(non-managed 模式)中,而不是使用 application 服务器来部署和配置它。 Spring 提供了通过FactoryBean implementation 调用(LocalConnectionFactoryBean)将连接器配置为 bean 的可能性。通过这种方式,您只需要 classpath 中的连接器 library(不需要 RAR 文件,也不需要ra.xml描述符)。如有必要,必须从连接器的 RAR 文件中提取 library。

一旦有权访问ConnectionFactory实例,就可以将其注入组件中。这些组件可以针对普通 CCI API 进行编码,也可以使用 Spring 的支持 classes 进行 CCI 访问(e.g. CciTemplate)。

在 non-managed 模式下使用连接器时,不能使用 global transactions,因为资源在当前线程的当前 global transaction 中从未登记或退市。资源不知道可能正在运行的任何 global Java EE transactions。

Spring中的ConnectionFactory配置

要建立与 EIS 的连接,您需要从 application 服务器获取ConnectionFactory(如果您处于托管模式)或直接从 Spring 获取(如果您处于 non-managed 模式)。

在托管模式下,您可以从 JNDI 访问ConnectionFactory。它的 properties 在 application 服务器中配置。以下 example 显示了如何执行此操作:

1
<jee:jndi-lookup id="eciConnectionFactory" jndi-name="eis/cicseci"/>

在 non-managed 模式下,必须将要在 Spring 的 configuration 中使用的ConnectionFactory配置为 JavaBean。 LocalConnectionFactoryBean class 提供此设置样式,传递连接器的ManagedConnectionFactory implementation,显示 application-level CCI ConnectionFactory。以下 example 显示了如何执行此操作:

1
2
3
4
5
6
7
8
9
<bean id="eciManagedConnectionFactory" class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
<property name="serverName" value="TXSERIES"/>
<property name="connectionURL" value="tcp://localhost/"/>
<property name="portNumber" value="2006"/>
</bean>

<bean id="eciConnectionFactory" class="org.springframework.jca.support.LocalConnectionFactoryBean">
<property name="managedConnectionFactory" ref="eciManagedConnectionFactory"/>
</bean>

您不能直接实例化特定的ConnectionFactory。您需要为连接器完成ManagedConnectionFactory接口的相应 implementation。该接口是 JCA SPI 规范的一部分。

配置CCI连接

JCA CCI 允许您使用连接器的ConnectionSpec implementation 配置与 EIS 的连接。要配置其 properties,需要使用专用适配器ConnectionSpecConnectionFactoryAdapter包装目标连接工厂。您可以使用connectionSpec property(作为内部 bean)配置专用ConnectionSpec

此 property 不是必需的,因为 CCI ConnectionFactory接口定义了两种不同的方法来获取 CCI 连接。您通常可以在 application 服务器(在托管模式下)或相应的本地ManagedConnectionFactory implementation 中配置一些ConnectionSpec properties。以下清单显示了ConnectionFactory接口定义的相关部分:

1
2
3
4
5
6
public interface ConnectionFactory implements Serializable, Referenceable {
...
Connection getConnection() throws ResourceException;
Connection getConnection(ConnectionSpec connectionSpec) throws ResourceException;
...
}

Spring 提供ConnectionSpecConnectionFactoryAdapter,允许您指定ConnectionSpec实例以用于给定工厂上的所有操作。如果指定了适配器的connectionSpec property,则适配器将getConnection变量与ConnectionSpec参数一起使用。否则,适配器使用不带该参数的变量。以下 example 显示了如何配置ConnectionSpecConnectionFactoryAdapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<bean id="managedConnectionFactory"
class="com.sun.connector.cciblackbox.CciLocalTxManagedConnectionFactory">
<property name="connectionURL" value="jdbc:hsqldb:hsql://localhost:9001"/>
<property name="driverName" value="org.hsqldb.jdbcDriver"/>
</bean>

<bean id="targetConnectionFactory"
class="org.springframework.jca.support.LocalConnectionFactoryBean">
<property name="managedConnectionFactory" ref="managedConnectionFactory"/>
</bean>

<bean id="connectionFactory"
class="org.springframework.jca.cci.connection.ConnectionSpecConnectionFactoryAdapter">
<property name="targetConnectionFactory" ref="targetConnectionFactory"/>
<property name="connectionSpec">
<bean class="com.sun.connector.cciblackbox.CciConnectionSpec">
<property name="user" value="sa"/>
<property name="password" value=""/>
</bean>
</property>
</bean>

使用单个CCI连接

如果要使用单个 CCI 连接,Spring 会提供另一个ConnectionFactory适配器来管理它。 SingleConnectionFactory adapter class 延迟打开一个连接,并在 application shutdown 时销毁 bean 时关闭它。此 class 公开了相应行为的特殊Connection代理,它们共享相同的底层物理连接。以下 example 显示了如何使用SingleConnectionFactory adapter class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<bean id="eciManagedConnectionFactory"
class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
<property name="serverName" value="TEST"/>
<property name="connectionURL" value="tcp://localhost/"/>
<property name="portNumber" value="2006"/>
</bean>

<bean id="targetEciConnectionFactory"
class="org.springframework.jca.support.LocalConnectionFactoryBean">
<property name="managedConnectionFactory" ref="eciManagedConnectionFactory"/>
</bean>

<bean id="eciConnectionFactory"
class="org.springframework.jca.cci.connection.SingleConnectionFactory">
<property name="targetConnectionFactory" ref="targetEciConnectionFactory"/>
</bean>

ConnectionFactory适配器无法直接配置ConnectionSpec。如果您需要针对特定ConnectionSpec的单个连接,则可以使用SingleConnectionFactory与之通信的中介ConnectionSpecConnectionFactoryAdapter

使用Spring的CCI访问支持

本节介绍如何使用 Spring 对 CCI 的支持来实现各种目的。它包括以下主题:

Record转换

Spring 的 JCA CCI 支持的目的之一是为操作 CCI 记录提供便利的设施。您可以指定策略来创建记录并从记录中提取数据,以便与 Spring 的CciTemplate一起使用。如果您不想直接在 application 中使用记录,则本节中描述的接口将策略配置为使用输入和输出记录。

要创建输入Record,您可以使用RecordCreator接口的专用 implementation。以下清单显示了RecordCreator接口定义:

1
2
3
4
5
public interface RecordCreator {

Record createRecord(RecordFactory recordFactory) throws ResourceException, DataAccessException;

}

createRecord(..)方法接收RecordFactory实例作为参数,该参数对应于ConnectionFactoryRecordFactory。您可以使用此 reference 创建IndexedRecordMappedRecord实例。以下 sample 显示了如何使用RecordCreator接口以及索引或映射记录:

1
2
3
4
5
6
7
8
9
public class MyRecordCreator implements RecordCreator {

public Record createRecord(RecordFactory recordFactory) throws ResourceException {
IndexedRecord input = recordFactory.createIndexedRecord("input");
input.add(new Integer(id));
return input;
}

}

您可以使用输出Record从 EIS 接收数据。因此,您可以将RecordExtractor接口的特定 implementation 传递给 Spring 的CciTemplate以从输出Record中提取数据。以下清单显示了RecordExtractor接口定义:

1
2
3
4
5
public interface RecordExtractor {

Object extractData(Record record) throws ResourceException, SQLException, DataAccessException;

}

以下 example 显示了如何使用RecordExtractor接口:

1
2
3
4
5
6
7
8
9
10
11
public class MyRecordExtractor implements RecordExtractor {

public Object extractData(Record record) throws ResourceException {
CommAreaRecord commAreaRecord = (CommAreaRecord) record;
String str = new String(commAreaRecord.toByteArray());
String field1 = string.substring(0,6);
String field2 = string.substring(6,1);
return new OutputObject(Long.parseLong(field1), field2);
}

}

使用CciTemplate

CciTemplate是核心 CCI 支持包(org.springframework.jca.cci.core)的核心 class。它简化了 CCI 的使用,因为它处理资源的创建和发布。这有助于避免 common 错误,例如忘记始终关闭连接。它关注连接和交互 objects 的生命周期,让 application code 专注于从 application 数据生成输入记录并从输出记录中提取 application 数据。

JCA CCI 规范定义了两种不同的方法来调用 EIS 上的操作。 CCI Interaction接口提供两个执行方法签名,如下面的清单所示:

1
2
3
4
5
6
7
8
9
10
11
public interface javax.resource.cci.Interaction {

...

boolean execute(InteractionSpec spec, Record input, Record output) throws ResourceException;

Record execute(InteractionSpec spec, Record input) throws ResourceException;

...

}

根据调用的模板方法,CciTemplate知道要在交互上调用哪个execute方法。无论如何,正确初始化的InteractionSpec实例是必需的。

您可以通过两种方式使用CciTemplate.execute(..)

  • 直接使用Record arguments。在这种情况下,您需要传入 CCI 输入 record,返回的 object 是相应的 CCI 输出 record。
  • 使用 application objects,使用 record 映射。在这种情况下,您需要提供相应的RecordCreatorRecordExtractor实例。

使用第一种方法,使用以下方法(直接对应于Interaction接口上的方法):

1
2
3
4
5
6
7
8
9
public class CciTemplate implements CciOperations {

public Record execute(InteractionSpec spec, Record inputRecord)
throws DataAccessException { ... }

public void execute(InteractionSpec spec, Record inputRecord, Record outputRecord)
throws DataAccessException { ... }

}

使用第二种方法,我们需要将 record 创建和 record 提取策略指定为 arguments。使用的接口是上一节关于 record 转换中描述的接口。以下清单显示了相应的CciTemplate方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CciTemplate implements CciOperations {

public Record execute(InteractionSpec spec,
RecordCreator inputCreator) throws DataAccessException {
// ...
}

public Object execute(InteractionSpec spec, Record inputRecord,
RecordExtractor outputExtractor) throws DataAccessException {
// ...
}

public Object execute(InteractionSpec spec, RecordCreator creator,
RecordExtractor extractor) throws DataAccessException {
// ...
}

}

除非在模板上设置了outputRecordCreator property(参见下一节),否则每个方法都会使用两个参数调用 CCI Interaction的相应execute方法:InteractionSpec和输入Record。它接收输出Record作为其 return value。

CciTemplate还提供了通过createIndexRecord(..)createMappedRecord(..)方法在RecordCreator implementation 之外创建IndexRecordMappedRecord的方法。您可以在 DAO implementations 中使用它来创建Record实例以传递到相应的CciTemplate.execute(..)方法。以下清单显示了CciTemplate接口定义:

1
2
3
4
5
6
7
public class CciTemplate implements CciOperations {

public IndexedRecord createIndexedRecord(String name) throws DataAccessException { ... }

public MappedRecord createMappedRecord(String name) throws DataAccessException { ... }

}

使用DAO支持

Spring 的 CCI 支持为 DAO 提供了一个抽象 class,支持注入ConnectionFactoryCciTemplate实例。 class 的 name 是CciDaoSupport。它提供简单的setConnectionFactorysetCciTemplate方法。在内部,此 class 为 passed-in ConnectionFactory创建CciTemplate实例,将其公开给子类中的具体数据访问 implementations。以下 example 显示了如何使用CciDaoSupport

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public abstract class CciDaoSupport {

public void setConnectionFactory(ConnectionFactory connectionFactory) {
// ...
}

public ConnectionFactory getConnectionFactory() {
// ...
}

public void setCciTemplate(CciTemplate cciTemplate) {
// ...
}

public CciTemplate getCciTemplate() {
// ...
}

}

自动输出记录生成

如果您使用的连接器仅支持带有输入和输出记录作为参数的Interaction.execute(..)方法(也就是说,它需要传入所需的输出 record 而不是返回适当的输出 record),则可以将CciTemplateoutputRecordCreator property 设置为收到响应时,自动生成一个由 JCA 连接器填充的输出 record。然后将此 record 返回给模板的调用者。

此 property 包含RecordCreator 界面的实现,用于此目的。您必须直接在CciTemplate上指定outputRecordCreator property。以下 example 显示了如何执行此操作:

1
cciTemplate.setOutputRecordCreator(new EciOutputRecordCreator());

或者(我们建议使用此方法),在 Spring configuration 中,如果将CciTemplate配置为专用的 bean 实例,则可以按以下方式定义 beans:

1
2
3
4
5
6
<bean id="eciOutputRecordCreator" class="eci.EciOutputRecordCreator"/>

<bean id="cciTemplate" class="org.springframework.jca.cci.core.CciTemplate">
<property name="connectionFactory" ref="eciConnectionFactory"/>
<property name="outputRecordCreator" ref="eciOutputRecordCreator"/>
</bean>

由于CciTemplate class 是 thread-safe,因此通常将其配置为共享实例。

CciTemplate交互摘要

以下 table 总结了CciTemplate class 的机制以及在 CCI Interaction接口上调用的相应方法:

CciTemplate方法签名 CciTemplate outputRecordCreator property 在 CCI Interaction 上调用execute方法
Record execute(InteractionSpec, Record) 没有设置 Record execute(InteractionSpec, Record)
Record execute(InteractionSpec, Record) boolean execute(InteractionSpec, Record, Record)
void execute(InteractionSpec,Record,Record) 没有设置 void execute(InteractionSpec,Record,Record)
void execute(InteractionSpec, Record, Record) void execute(InteractionSpec, Record, Record)
Record execute(InteractionSpec, RecordCreator) 没有设置 Record execute(InteractionSpec, Record)
Record execute(InteractionSpec, RecordCreator) void execute(InteractionSpec, Record, Record)
Record execute(InteractionSpec, Record, RecordExtractor) 没有设置 Record execute(InteractionSpec, Record)
Record execute(InteractionSpec, Record, RecordExtractor) void execute(InteractionSpec, Record, Record)
Record execute(InteractionSpec, RecordCreator, RecordExtractor) 没有设置 Record execute(InteractionSpec, Record)
Record execute(InteractionSpec, RecordCreator, RecordExtractor) void execute(InteractionSpec, Record, Record)

直接使用CCI连接和交互

CciTemplate还允许您直接使用 CCI 连接和交互,方式与JdbcTemplateJmsTemplate相同。如果要对 CCI 连接或交互执行多个操作,这对于 example 非常有用。

ConnectionCallback接口提供 CCI Connection作为参数(对其执行自定义操作)以及创建Connection的 CCI ConnectionFactory。后者可能很有用(例如,获取关联的RecordFactory实例并创建 indexed/mapped 记录)。以下清单显示了ConnectionCallback接口定义:

1
2
3
4
5
6
public interface ConnectionCallback {

Object doInConnection(Connection connection, ConnectionFactory connectionFactory)
throws ResourceException, SQLException, DataAccessException;

}

InteractionCallback接口提供 CCI Interaction(对其执行自定义操作)以及相应的 CCI ConnectionFactory。以下清单显示了InteractionCallback接口定义:

1
2
3
4
5
6
public interface InteractionCallback {

Object doInInteraction(Interaction interaction, ConnectionFactory connectionFactory)
throws ResourceException, SQLException, DataAccessException;

}

InteractionSpec objects 可以在多个模板 calls 之间共享,也可以在每个回调方法中新创建。这完全取决于 DAO implementation。

CciTemplate用法示例

在本节中,我们将使用 IBM CICS ECI 连接器显示CciTemplate用于使用 ECI 模式访问 CICS 的用法。

首先,我们必须对 CCI InteractionSpec进行一些初始化,以指定要访问的 CICS 程序以及如何与其进行交互,如下面的示例所示:

1
2
3
ECIInteractionSpec interactionSpec = new ECIInteractionSpec();
interactionSpec.setFunctionName("MYPROG");
interactionSpec.setInteractionVerb(ECIInteractionSpec.SYNC_SEND_RECEIVE);

然后程序可以通过 Spring 的模板使用 CCI 并指定 custom objects 和 CCI Records之间的映射,如下面的示例所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MyDaoImpl extends CciDaoSupport implements MyDao {

public OutputObject getData(InputObject input) {
ECIInteractionSpec interactionSpec = ...;

OutputObject output = (ObjectOutput) getCciTemplate().execute(interactionSpec,
new RecordCreator() {
public Record createRecord(RecordFactory recordFactory) throws ResourceException {
return new CommAreaRecord(input.toString().getBytes());
}
},
new RecordExtractor() {
public Object extractData(Record record) throws ResourceException {
CommAreaRecord commAreaRecord = (CommAreaRecord)record;
String str = new String(commAreaRecord.toByteArray());
String field1 = string.substring(0,6);
String field2 = string.substring(6,1);
return new OutputObject(Long.parseLong(field1), field2);
}
});

return output;
}
}

如前所述,您可以使用回调直接处理 CCI 连接或交互。以下 example 显示了如何执行此操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyDaoImpl extends CciDaoSupport implements MyDao {

public OutputObject getData(InputObject input) {
ObjectOutput output = (ObjectOutput) getCciTemplate().execute(
new ConnectionCallback() {
public Object doInConnection(Connection connection,
ConnectionFactory factory) throws ResourceException {

// do something...

}
});
}
return output;
}

}

使用ConnectionCallback时,使用的ConnectionCciTemplate管理和关闭,但回调 implementation 必须管理在连接上创建的任何交互。

对于更具体的回调,您可以实现InteractionCallback。如果这样做,passed-in Interaction将由CciTemplate管理和关闭。以下 example 显示了如何执行此操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyDaoImpl extends CciDaoSupport implements MyDao {

public String getData(String input) {
ECIInteractionSpec interactionSpec = ...;
String output = (String) getCciTemplate().execute(interactionSpec,
new InteractionCallback() {
public Object doInInteraction(Interaction interaction,
ConnectionFactory factory) throws ResourceException {
Record input = new CommAreaRecord(inputString.getBytes());
Record output = new CommAreaRecord();
interaction.execute(holder.getInteractionSpec(), input, output);
return new String(output.toByteArray());
}
});
return output;
}

}

对于前面的示例,所涉及的 Spring beans 的相应 configuration 可能类似于 non-managed 模式中的以下 example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<bean id="managedConnectionFactory" class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
<property name="serverName" value="TXSERIES"/>
<property name="connectionURL" value="local:"/>
<property name="userName" value="CICSUSER"/>
<property name="password" value="CICS"/>
</bean>

<bean id="connectionFactory" class="org.springframework.jca.support.LocalConnectionFactoryBean">
<property name="managedConnectionFactory" ref="managedConnectionFactory"/>
</bean>

<bean id="component" class="mypackage.MyDaoImpl">
<property name="connectionFactory" ref="connectionFactory"/>
</bean>

在托管模式下(即在 Java EE 环境中),configuration 可能类似于以下 example:

1
2
3
4
5
<jee:jndi-lookup id="connectionFactory" jndi-name="eis/cicseci"/>

<bean id="component" class="MyDaoImpl">
<property name="connectionFactory" ref="connectionFactory"/>
</bean>

将CCI访问建模为操作 Objects

org.springframework.jca.cci.object包包含支持 classes,允许您以不同的方式访问 EIS:通过可重用的操作 objects,类似于 Spring 的 JDBC 操作 objects(参见数据访问章节的 JDBC 部分)。这通常封装了 CCI API。 application-level 输入 object 被传递给操作 object,因此它可以构造输入 record,然后将接收到的 record 数据转换为 application-level 输出 object 并 return 它。

这种方法在内部基于CciTemplate class 和RecordCreatorRecordExtractor接口,重用了 Spring 核心 CCI 支持的机制。

使用MappingRecordOperation

MappingRecordOperation本质上执行与CciTemplate相同的工作,但将 pre-configured 操作表示为 object。它提供了两个模板方法来指定如何将输入 object 转换为输入 record 以及如何将输出 record 转换为输出 object(record mapping):

  • createInputRecord(..):指定如何将输入 object 转换为输入Record
  • extractOutputData(..):指定如何从输出中提取输出 object Record

以下清单显示了这些方法的签名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract class MappingRecordOperation extends EisOperation {

...

protected abstract Record createInputRecord(RecordFactory recordFactory,
Object inputObject) throws ResourceException, DataAccessException {
// ...
}

protected abstract Object extractOutputData(Record outputRecord)
throws ResourceException, SQLException, DataAccessException {
// ...
}

...

}

此后,执行 EIS 操作,您需要使用单个execute方法,传入 application-level 输入 object 并接收 application-level 输出 object 作为结果。以下 example 显示了如何执行此操作:

1
2
3
4
5
6
7
8
9
public abstract class MappingRecordOperation extends EisOperation {

...

public Object execute(Object inputObject) throws DataAccessException {
}

...
}

CciTemplate class 相反,此execute(..)方法没有InteractionSpec作为参数。相反,InteractionSpec是 global 操作。必须使用以下构造函数来实例化具有特定InteractionSpec的操作 object。以下 example 显示了如何执行此操作:

1
2
3
InteractionSpec spec = ...;
MyMappingRecordOperation eisOperation = new MyMappingRecordOperation(getConnectionFactory(), spec);
...

使用MappingCommAreaOperation

某些连接器使用基于 COMMAREA 的记录,该记录表示包含要发送到 EIS 的参数及其返回的数据的 array 字节。 Spring 提供了一个特殊的操作 class,用于直接在 COMMAREA 而不是记录上工作。 MappingCommAreaOperation class 扩展了MappingRecordOperation class 以提供这种特殊的 COMMAREA 支持。它隐式使用CommAreaRecord class 作为输入和输出 record 类型,并提供两种新方法将输入 object 转换为输入 COMMAREA,并将输出 COMMAREA 转换为输出 object。以下清单显示了相关的方法签名:

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class MappingCommAreaOperation extends MappingRecordOperation {

...

protected abstract byte[] objectToBytes(Object inObject)
throws IOException, DataAccessException;

protected abstract Object bytesToObject(byte[] bytes)
throws IOException, DataAccessException;

...

}

自动输出记录生成

由于每个MappingRecordOperation子类都在内部基于 CciTemplate,因此可以使用与CciTemplate一样自动生成输出记录的相同方法。每个操作 object 都提供相应的setOutputRecordCreator(..)方法。有关详细信息,请参阅自动输出记录生成

摘要

操作 object 方法以与CciTemplate class 相同的方式使用记录。

MappingRecordOperation方法签名 MappingRecordOperation outputRecordCreator property 在 CCI Interaction 上调用execute方法
Object execute(Object) 没有设置 Record execute(InteractionSpec, Record)
Object execute(Object) boolean execute(InteractionSpec, Record, Record)

MappingRecordOperation用法示例

在本节中,我们将展示如何使用MappingRecordOperation访问具有 Blackbox CCI 连接器的数据库。

此连接器的原始 version 由 Java EE SDK(version 1.3)提供,可从 Oracle 获得。

首先,必须对 CCI InteractionSpec进行一些初始化,以指定要执行的 SQL 请求。在下面的示例中,我们直接定义将请求的参数转换为 CCI record 的方式以及将 CCI 结果 record 转换为Person class 的实例的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class PersonMappingOperation extends MappingRecordOperation {

public PersonMappingOperation(ConnectionFactory connectionFactory) {
setConnectionFactory(connectionFactory);
CciInteractionSpec interactionSpec = new CciConnectionSpec();
interactionSpec.setSql("select * from person where person_id=?");
setInteractionSpec(interactionSpec);
}

protected Record createInputRecord(RecordFactory recordFactory,
Object inputObject) throws ResourceException {
Integer id = (Integer) inputObject;
IndexedRecord input = recordFactory.createIndexedRecord("input");
input.add(new Integer(id));
return input;
}

protected Object extractOutputData(Record outputRecord)
throws ResourceException, SQLException {
ResultSet rs = (ResultSet) outputRecord;
Person person = null;
if (rs.next()) {
Person person = new Person();
person.setId(rs.getInt("person_id"));
person.setLastName(rs.getString("person_last_name"));
person.setFirstName(rs.getString("person_first_name"));
}
return person;
}
}

然后 application 可以执行操作 object,并将 person 标识符作为参数。请注意,您可以将操作 object 设置为共享实例,因为它是 thread-safe。以下以 person 标识符作为参数执行操作 object:

1
2
3
4
5
6
7
8
public class MyDaoImpl extends CciDaoSupport implements MyDao {

public Person getPerson(int id) {
PersonMappingOperation query = new PersonMappingOperation(getConnectionFactory());
Person person = (Person) query.execute(new Integer(id));
return person;
}
}

在 non-managed 模式下 Spring beans 的相应 configuration 可以如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<bean id="managedConnectionFactory"
class="com.sun.connector.cciblackbox.CciLocalTxManagedConnectionFactory">
<property name="connectionURL" value="jdbc:hsqldb:hsql://localhost:9001"/>
<property name="driverName" value="org.hsqldb.jdbcDriver"/>
</bean>

<bean id="targetConnectionFactory"
class="org.springframework.jca.support.LocalConnectionFactoryBean">
<property name="managedConnectionFactory" ref="managedConnectionFactory"/>
</bean>

<bean id="connectionFactory"
class="org.springframework.jca.cci.connection.ConnectionSpecConnectionFactoryAdapter">
<property name="targetConnectionFactory" ref="targetConnectionFactory"/>
<property name="connectionSpec">
<bean class="com.sun.connector.cciblackbox.CciConnectionSpec">
<property name="user" value="sa"/>
<property name="password" value=""/>
</bean>
</property>
</bean>

<bean id="component" class="MyDaoImpl">
<property name="connectionFactory" ref="connectionFactory"/>
</bean>

在托管模式下(即在 Java EE 环境中),configuration 可能如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<jee:jndi-lookup id="targetConnectionFactory" jndi-name="eis/blackbox"/>

<bean id="connectionFactory"
class="org.springframework.jca.cci.connection.ConnectionSpecConnectionFactoryAdapter">
<property name="targetConnectionFactory" ref="targetConnectionFactory"/>
<property name="connectionSpec">
<bean class="com.sun.connector.cciblackbox.CciConnectionSpec">
<property name="user" value="sa"/>
<property name="password" value=""/>
</bean>
</property>
</bean>

<bean id="component" class="MyDaoImpl">
<property name="connectionFactory" ref="connectionFactory"/>
</bean>

MappingCommAreaOperation用法示例

在本节中,我们将展示如何使用MappingCommAreaOperation来使用 IBM CICS ECI 连接器访问带有 ECI 模式的 CICS。

首先,我们需要初始化 CCI InteractionSpec以指定要访问的 CICS 程序以及如何与之交互,如下面的示例所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public abstract class EciMappingOperation extends MappingCommAreaOperation {

public EciMappingOperation(ConnectionFactory connectionFactory, String programName) {
setConnectionFactory(connectionFactory);
ECIInteractionSpec interactionSpec = new ECIInteractionSpec(),
interactionSpec.setFunctionName(programName);
interactionSpec.setInteractionVerb(ECIInteractionSpec.SYNC_SEND_RECEIVE);
interactionSpec.setCommareaLength(30);
setInteractionSpec(interactionSpec);
setOutputRecordCreator(new EciOutputRecordCreator());
}

private static class EciOutputRecordCreator implements RecordCreator {
public Record createRecord(RecordFactory recordFactory) throws ResourceException {
return new CommAreaRecord();
}
}

}

然后我们可以将 abstract EciMappingOperation class 子类化为指定 custom objects 和Records之间的映射,如下面的 example 所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MyDaoImpl extends CciDaoSupport implements MyDao {

public OutputObject getData(Integer id) {
EciMappingOperation query = new EciMappingOperation(getConnectionFactory(), "MYPROG") {

protected abstract byte[] objectToBytes(Object inObject) throws IOException {
Integer id = (Integer) inObject;
return String.valueOf(id);
}

protected abstract Object bytesToObject(byte[] bytes) throws IOException;
String str = new String(bytes);
String field1 = str.substring(0,6);
String field2 = str.substring(6,1);
String field3 = str.substring(7,1);
return new OutputObject(field1, field2, field3);
}
});

return (OutputObject) query.execute(new Integer(id));
}

}

在 non-managed 模式下 Spring beans 的相应 configuration 可以如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<bean id="managedConnectionFactory" class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
<property name="serverName" value="TXSERIES"/>
<property name="connectionURL" value="local:"/>
<property name="userName" value="CICSUSER"/>
<property name="password" value="CICS"/>
</bean>

<bean id="connectionFactory" class="org.springframework.jca.support.LocalConnectionFactoryBean">
<property name="managedConnectionFactory" ref="managedConnectionFactory"/>
</bean>

<bean id="component" class="MyDaoImpl">
<property name="connectionFactory" ref="connectionFactory"/>
</bean>

在托管模式下(即在 Java EE 环境中),configuration 可能如下所示:

1
2
3
4
5
<jee:jndi-lookup id="connectionFactory" jndi-name="eis/cicseci"/>

<bean id="component" class="MyDaoImpl">
<property name="connectionFactory" ref="connectionFactory"/>
</bean>

Transactions

JCA 为资源适配器指定了几个 transaction 支持级别。资源适配器支持的 transactions 类型在其ra.xml文件中指定。基本上有三个选项:none(对于 example,使用 CICS EPI 连接器),local transactions(对于 example,使用 CICS ECI 连接器)和 global transactions(对于 example,使用 IMS 连接器)。以下 example 配置 global 选项:

1
2
3
4
5
6
7
<connector>
<resourceadapter>
<!-- <transaction-support>NoTransaction</transaction-support> -->
<!-- <transaction-support>LocalTransaction</transaction-support> -->
<transaction-support>XATransaction</transaction-support>
<resourceadapter>
<connector>

对于 global transactions,您可以使用 Spring 的通用 transaction 基础结构来划分 transactions,将JtaTransactionManager作为后端(委托给下面的 Java EE 服务器的分布式 transaction 协调器)。

对于单个 CCI ConnectionFactory上的本地 transactions,Spring 为 CCI 提供了特定的 transaction-management 策略,类似于 JDBC 的DataSourceTransactionManager。 CCI API 定义了本地 transaction object 和相应的本地 transaction 分界方法。 Spring 的CciLocalTransactionManager以完全符合 Spring 的通用PlatformTransactionManager抽象的方式执行这样的本地 CCI transactions。以下 example 配置CciLocalTransactionManager

1
2
3
4
5
6
<jee:jndi-lookup id="eciConnectionFactory" jndi-name="eis/cicseci"/>

<bean id="eciTransactionManager"
class="org.springframework.jca.cci.connection.CciLocalTransactionManager">
<property name="connectionFactory" ref="eciConnectionFactory"/>
</bean>

您可以将 transaction 策略与 Spring 的 transaction 分界设施中的任何一个一起使用,无论是声明性的还是编程式的。这是 Spring 的通用PlatformTransactionManager抽象的结果,它将 transaction 分界与实际执行策略分离。您可以根据需要在JtaTransactionManagerCciLocalTransactionManager之间切换,保持 transaction 分界 as-is。

有关 Spring 的 transaction 工具的更多信息,请参阅Transaction Management

电子邮件

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

Library 依赖

以下 JAR 需要在 order 中的 application 的 classpath 上使用 Spring Framework 的电子邮件 library:

这个 library 可以在 web 上免费获取 - 对于 example,在 Maven Central 中可以作为com.sun.mail:javax.mail

Spring Framework 提供了一个有用的实用程序 library,用于发送电子邮件,使您免受底层邮件系统的细节影响,并负责代表 client 进行 low-level 资源处理。

org.springframework.mail包是 Spring Framework 电子邮件支持的 root level 包。发送电子邮件的中央界面是MailSender界面。一个简单的 value object 封装了一个简单邮件的 properties,例如fromto(加上许多其他邮件)是SimpleMailMessage class。此包还包含已检查的 exceptions 层次结构,这些层次结构在较低的 level 邮件系统 exceptions 上提供更高级别的抽象,其中根 exception 为MailException。有关富邮件 exception 层次结构的更多信息,请参阅javadoc

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

用法

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

1
2
3
4
5
public interface OrderManager {

void placeOrder(Order order);

}

进一步假设我们有一个要求,即需要生成带有 order 编号的电子邮件消息并将其发送给放置相关 order 的客户。

基本MailSender和SimpleMailMessage用法

以下 example 显示了当有人放置 order 时如何使用MailSenderSimpleMailMessage发送电子邮件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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());
}
}

}

以下 example 显示了前面的 code 的 bean 定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="mail.mycompany.com"/>
</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="[emailprotected]"/>
<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>

使用JavaMailSender和MimeMessagePreparator

本节介绍使用MimeMessagePreparator回调接口的OrderManager的另一个_implement。在下面的示例中,mailSender property 的类型为JavaMailSender,因此我们可以使用 JavaMail MimeMessage class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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("[emailprotected]"));
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());
}
}

}

mail code 是一个横切关注的问题,很可能是重构为自定义 Spring AOP aspect的候选者,然后可以在OrderManager目标上的适当连接点执行。

Spring Framework 的邮件支持附带标准的 JavaMail implementation。有关更多信息,请参阅相关的 javadoc。

使用JavaMail MimeMessageHelper

处理 JavaMail 消息时非常方便的 class 是org.springframework.mail.javamail.MimeMessageHelper,它使您不必使用详细的 JavaMail API。使用MimeMessageHelper,创建MimeMessage非常容易,如下面的 example 所示:

1
2
3
4
5
6
7
8
9
10
// 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("[emailprotected]");
helper.setText("Thank you for ordering!");

sender.send(message);

发送附件和内联资源

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

附件

以下 example 显示如何使用MimeMessageHelper发送包含单个 JPEG 图像附件的电子邮件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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("[emailprotected]");

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);
内联资源

以下 example 显示如何使用MimeMessageHelper发送带有内嵌图像的电子邮件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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("[emailprotected]");

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

通过使用指定的Content-ID(上面的 example 中的identifier1234)将内联资源添加到MimeMessage。添加文本和资源的 order 非常重要。请务必先添加文本,然后再添加资源。如果你反过来这样做,它就行不通。

使用模板Library创建电子邮件内容

前面部分中显示的示例中的 code 通过使用方法 calls(例如message.setText(..))显式创建了电子邮件消息的内容。这对于简单的情况很好,并且在上述示例的 context 中是可以的,其目的是向您展示 API 的基础知识。

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

  • 在 Java code 中创建 HTML-based 电子邮件内容是单调乏味且容易出错的。
  • 显示逻辑和业务逻辑之间没有明确的区别。
  • 更改电子邮件内容的显示结构需要编写 Java code,重新编译,重新部署等。

通常,解决这些问题的方法是使用模板 library(例如 FreeMarker)来定义电子邮件内容的显示结构。这使得 code 的任务只是创建要在电子邮件模板中呈现的数据并发送电子邮件。当您的电子邮件内容变得相当复杂时,这绝对是一种最佳实践,并且,对于 FreeMarker,使用 Spring Framework 支持 classes,它变得非常容易。

任务执行和调度

Spring Framework 分别为TaskExecutorTaskScheduler接口提供异步执行和任务调度的抽象。 Spring 还 features implementations 那些支持线程池或在 application 服务器环境中委托给 CommonJ 的接口。最终,在 common 接口后面使用这些_implement 将抽象出 Java SE 5,Java SE 6 和 Java EE 环境之间的差异。

Spring 还 features integration classes 以支持使用Timer(自 1.3 以来的 JDK 的一部分)和 Quartz Scheduler(http://quartz-scheduler.org)进行调度。您可以分别使用带有可选 references 的FactoryBeanTimerTrigger实例的FactoryBean来设置这两个调度程序。此外,Quartz Scheduler 和Timer的便捷 class 可用于调用现有目标 object 的方法(类似于普通的MethodInvokingFactoryBean操作)。

SpringTaskExecutor抽象

执行程序是线程池概念的 JDK name。 “执行程序”命名是由于无法保证底层 implementation 实际上是一个池。执行程序可能是 single-threaded 甚至是同步的。 Spring 的抽象隐藏了 Java SE 和 Java EE 环境之间的 implementation 细节。

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

最初创建TaskExecutor是为了给其他 Spring 组件提供所需的线程池抽象。诸如ApplicationEventMulticaster,JMS 的AbstractMessageListenerContainer和 Quartz integration 之类的组件都使用TaskExecutor抽象来池化线程。但是,如果您的 beans 需要线程池行为,您也可以根据自己的需要使用此抽象。

TaskExecutor类型

Spring 包含许多 pre-built __mplempleations of TaskExecutor。很可能,你永远不需要实现自己的。 Spring 提供的变体如下:

  • SyncTaskExecutor:此 implementation 不会异步执行调用。相反,每次调用都发生在调用线程中。它主要用于不需要 multi-threading 的情况,例如在简单的测试用例中。
  • SimpleAsyncTaskExecutor:此 implementation 不会重用任何线程。相反,它为每次调用启动一个新线程。但是,它确实支持并发限制,该限制会阻止任何超出限制的调用,直到释放一个插槽。如果您正在寻找 true 池,请参阅此列表中的ThreadPoolTaskExecutor
  • ConcurrentTaskExecutor:此 implementation 是java.util.concurrent.Executor实例的适配器。有一个替代(ThreadPoolTaskExecutor)将Executor configuration 参数公开为 bean properties。很少需要直接使用ConcurrentTaskExecutor。但是,如果ThreadPoolTaskExecutor不够灵活以满足您的需求,ConcurrentTaskExecutor是另一种选择。
  • ThreadPoolTaskExecutor:此 implementation 最常用。它公开 bean properties 以配置java.util.concurrent.ThreadPoolExecutor并将其包装在TaskExecutor中。如果您需要适应不同类型的java.util.concurrent.Executor,我们建议您改用ConcurrentTaskExecutor
  • WorkManagerTaskExecutor:此 implementation 使用 CommonJ WorkManager作为其后备服务提供程序,并且是在 Spring application context 中在 WebLogic 或 WebSphere 上设置 CommonJ-based 线程池 integration 的中心便捷 class。
  • DefaultManagedTaskExecutor:此 implementation 在 JSR-236 兼容的运行时环境(例如 Java EE 7 application 服务器)中使用 JNDI-obtained ManagedExecutorService,为此目的替换 CommonJ WorkManager。

使用TaskExecutor

Spring 的TaskExecutor implementations 用作简单的 JavaBeans。在下面的示例中,我们定义一个 bean,它使用ThreadPoolTaskExecutor异步打印出一组消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 properties:

1
2
3
4
5
6
7
8
9
<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>

SpringTaskScheduler抽象

除了TaskExecutor抽象之外,Spring 3.0 引入了一个TaskScheduler,其中包含各种方法,可以在将来的某个时刻调度任务。以下清单显示了TaskScheduler接口定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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的方法,只占用RunnableDate。这导致任务在指定的 time 之后运行一次。所有其他方法都能够重复调度任务。 fixed-rate 和 fixed-delay 方法用于简单的定期执行,但接受Trigger的方法更灵活。

触发接口

Trigger界面基本上受 JSR-236 的启发,从 Spring 3.0 开始,尚未正式实施。 Trigger的基本 idea 是执行时间可以根据过去的执行结果甚至任意条件来确定。如果这些确定确实考虑了前面执行的结果,则该信息在TriggerContext内可用。 Trigger界面本身非常简单,如下面的清单所示:

1
2
3
4
public interface Trigger {

Date nextExecutionTime(TriggerContext triggerContext);
}

TriggerContext是最重要的部分。它封装了所有相关数据,如有必要,将来可以进行扩展。 TriggerContext是一个接口(默认使用SimpleTriggerContext implementation)。以下清单显示了Trigger implementations 的可用方法。

1
2
3
4
5
6
7
8
public interface TriggerContext {

Date lastScheduledExecutionTime();

Date lastActualExecutionTime();

Date lastCompletionTime();
}

触发实现

Spring 提供了Trigger接口的两个 implementations。最有趣的是CronTrigger。它支持基于 cron 表达式调度任务。例如,以下任务计划在每小时后运行 15 分钟,但仅限于工作日的 9-to-5“营业时间”:

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

另一个 implementation 是PeriodicTrigger,它接受一个固定的句点,一个可选的初始延迟 value 和一个 boolean 来指示该句点是应该被解释为 fixed-rate 还是 fixed-delay。由于TaskScheduler接口已经定义了以固定速率或固定延迟调度任务的方法,因此应尽可能直接使用这些方法。 PeriodicTrigger implementation 的 value 是你可以在依赖Trigger抽象的组件中使用它。例如,允许定期触发,cron-based 触发器甚至自定义触发器 implementations 可以互换使用可能很方便。这样的 component 可以利用依赖注入,这样你就可以在外部配置这样的Triggers,因此可以很容易地修改或扩展它们。

TaskScheduler实现

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

每当外部线程 management 不是必需的时候,更简单的替代方案是 application 中的本地ScheduledExecutorService设置,可以通过 Spring 的ConcurrentTaskScheduler进行调整。为方便起见,Spring 还提供了一个ThreadPoolTaskScheduler,它在内部委托ScheduledExecutorService沿的行提供 common bean-style configuration。这些变体适用于宽松的 application 服务器环境中的本地嵌入式线程池设置,特别是在 Tomcat 和 Jetty 上。

Annotation支持调度和异步执行

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

启用计划注解

要启用对@Scheduled@Async 注释的支持,可以将@EnableScheduling@EnableAsync添加到其中一个@Configuration classes 中,如下面的 example 所示:

1
2
3
4
5
@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}

您可以为 application 选择相关的注释。例如,如果您只需要支持@Scheduled,则可以省略@EnableAsync。对于更多 fine-grained 控件,您还可以实现SchedulingConfigurer接口,AsyncConfigurer接口或两者。有关完整详细信息,请参阅SchedulingConfigurerAsyncConfigurer javadoc。

如果您更喜欢 XML configuration,则可以使用<task:annotation-driven>元素,如下面的 example 所示:

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

请注意,使用前面的 XML,提供了 executor reference 用于处理与具有@Async annotation 的方法对应的那些任务,并且提供了 scheduler reference 用于管理用@Scheduled注释的那些方法。

处理@Async annotations 的默认建议模式是proxy,它允许仅通过代理拦截 calls。同一 class 中的本地 calls 不能以这种方式截获。对于更高级的拦截模式,请考虑结合 compile-time 或 load-time 编织切换到aspectj模式。

Scheduled注解

您可以将@Scheduled annotation 与触发器元数据一起添加到方法中。对于 example,以固定延迟每五秒调用以下方法,这意味着该周期是从每个前一次调用的完成 time 开始测量的:

1
2
3
4
@Scheduled(fixedDelay=5000)
public void doSomething() {
// something that should execute periodically
}

如果需要执行 fixed-rate,则可以更改 annotation 中指定的 property name。每五秒调用以下方法(在每次调用的连续开始时间之间测量):

1
2
3
4
@Scheduled(fixedRate=5000)
public void doSomething() {
// something that should execute periodically
}

对于 fixed-delay 和 fixed-rate 任务,您可以通过指示在第一次执行方法之前等待的毫秒数来指定初始延迟,如下面的fixedRate example 显示:

1
2
3
4
@Scheduled(initialDelay=1000, fixedRate=5000)
public void doSomething() {
// something that should execute periodically
}

如果简单的定期调度表达不够,则可以提供 cron 表达式。对于 example,以下仅在工作日执行:

1
2
3
4
@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
// something that should execute on weekdays only
}

您还可以使用zone属性指定解析 cron 表达式的 time zone。

请注意,要调度的方法必须具有 void 返回,并且不得指望任何 arguments。如果该方法需要与 application context 中的其他 object 交互,那么这些通常是通过依赖注入提供的。

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

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

Async注解

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

1
2
3
4
@Async
void doSomething() {
// this will be executed asynchronously
}

与使用@Scheduled annotation 注释的方法不同,这些方法可以期望 arguments,因为它们在运行时由调用者以“正常”方式调用,而不是由容器管理的调度任务调用。对于 example,以下 code 是@Async annotation 的合法 application:

1
2
3
4
@Async
void doSomething(String s) {
// this will be executed asynchronously
}

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

1
2
3
4
@Async
Future<String> returnSomething(int i) {
// this will be executed asynchronously
}

@Async方法不仅可以声明常规java.util.concurrent.Future return 类型,还可以声明 Spring 的org.springframework.util.concurrent.ListenableFuture,或者 Spring 4.2,JDK 8 的java.util.concurrent.CompletableFuture,以便与异步任务进行更丰富的交互,并通过进一步的处理步骤立即合成。

您不能将@Async与生命周期回调(如@PostConstruct)结合使用。要异步初始化 Spring beans,您当前必须使用单独的初始化 Spring bean,然后在目标上调用@Async带注释的方法,如下面的 example 所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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();
}

}

@Async没有直接的 XML 等价物,因为这些方法应该首先设计用于异步执行,而不是外部 re-declared 是异步的。但是,您可以使用 Spring AOP 手动设置 Spring 的AsyncExecutionInterceptor,并结合自定义切入点。

使用Async注解执行器限定

默认情况下,在方法上指定@Async时,使用的执行程序是在启用异步支持时配置,i.e。如果使用 XML 或AsyncConfigurer implementation(如果有),则为“annotation-driven”元素。但是,当您需要指示在执行给定方法时应使用非默认执行程序时,可以使用@Async annotation 的value属性。以下 example 显示了如何执行此操作:

1
2
3
4
@Async("otherExecutor")
void doSomething(String s) {
// this will be executed asynchronously by "otherExecutor"
}

在这种情况下,"otherExecutor"可以是 Spring 容器中任何Executor bean 的 name,也可以是与任何Executor关联的限定符的 name(对于 example,与<qualifier>元素或 Spring 的@Qualifier annotation 一起指定)。

使用Async注解进行异常管理

@Async方法具有Future -typed return value 时,很容易管理在方法执行期间抛出的 exception,因为在Future结果上调用get时会抛出此 exception。但是,使用void return 类型时,exception 未被捕获且无法传输。您可以提供AsyncUncaughtExceptionHandler来处理此类 exceptions。以下 example 显示了如何执行此操作:

1
2
3
4
5
6
7
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

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

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

任务命名空间

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

‘调度程序’元素

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

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

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

执行元素

以下创建ThreadPoolTaskExecutor实例:

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

上一节中显示的调度程序一样,为id属性提供的 value 用作池中线程名称的前缀。就池大小而言,executor元素支持比scheduler元素更多的 configuration 选项。首先,ThreadPoolTaskExecutor的线程池本身更易于配置。执行程序的线程池可以具有不同的核心值和最大大小,而不仅仅是单个大小。如果提供单个 value,则执行程序具有 fixed-size 线程池(核心和最大大小相同)。但是,executor元素的pool-size属性也接受min-max形式的范围。以下 example _set 设置5的最小值和25的最大值:

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

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

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

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

以下 example 显示了一个executor元素,其中包含许多属性以指定各种行为:

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

最后,keep-alive设置确定线程在终止之前可以保持 idle 的 time 限制(以秒为单位)。如果池中当前有多个线程核心数,则在等待此数量的 time 而不处理任务后,多余的线程将被终止。 time value 为零会导致多余线程在执行任务后立即终止,而不会在任务队列中保留 follow-up 工作。以下 example _set keep-alive value 为两分钟:

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

‘scheduled-tasks’元素

Spring 任务命名空间最强大的 feature 是支持在 Spring Application Context 中配置要安排的任务。这遵循类似于 Spring 中的其他“method-invokers”的方法,例如由 JMS 名称空间提供的用于配置 message-driven POJO 的方法。基本上,ref属性可以指向任何 Spring-managed object,method属性提供要在该对象上调用的方法的 name。以下清单显示了一个简单的 example:

1
2
3
4
5
<task:scheduled-tasks scheduler="myScheduler">
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/>
</task:scheduled-tasks>

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

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

1
2
3
4
5
6
7
<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"/>

使用Quartz调度程序

Quartz 使用TriggerJobJobDetail objects 来实现各种作业的调度。有关 Quartz 背后的基本概念,请参阅http://quartz-scheduler.org。为方便起见,Spring 提供了几个 classes,简化了在 Spring-based applications 中使用 Quartz 的过程。

使用JobDetailFactoryBean

Quartz JobDetail objects 包含_jun job 所需的所有信息。 Spring 提供JobDetailFactoryBean,它提供 bean-style properties 用于 XML 配置目的。考虑以下 example:

1
2
3
4
5
6
7
8
<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>

job detail configuration 具有_jun job(ExampleJob)所需的所有信息。超时在 job data map 中指定。 job 数据 map 可通过JobExecutionContext(在执行 time 时传递给您)获得,但JobDetail也从 job 数据中获取 properties 映射到 job 实例的 properties。因此,在下面的示例中,ExampleJob包含一个名为timeout的 bean property,JobDetail会自动应用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
}

}

您也可以使用 job data map 中的所有其他 properties。

通过使用namegroup properties,您可以分别修改 job 的 name 和 group。默认情况下,job 的 name 与JobDetailFactoryBean的 bean name 匹配(上面的 example 中的exampleJob)。

使用MethodInvokingJobDetailFactoryBean

通常,您只需要在特定的 object 上调用方法。通过使用MethodInvokingJobDetailFactoryBean,您可以完成此操作,如下面的 example 所示:

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

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

1
2
3
4
5
6
7
8
public class ExampleBusinessObject {

// properties and collaborators

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

通过使用MethodInvokingJobDetailFactoryBean,您无需创建仅调用方法的 one-line 作业。您只需要创建实际业务 object 并连接详细信息 object。

默认情况下,Quartz Jobs 是 stateless,导致作业可能互相干扰。如果为同一个JobDetail指定了两个触发器,则可能在第一个 job 完成之前,第二个触发器启动。如果JobDetail classes 实现Stateful接口,则不会发生这种情况。第二个 job 在第一个 job 完成之前没有启动。要使MethodInvokingJobDetailFactoryBean得到的作业为 non-concurrent,请将concurrent flag 设置为false,如下面的 example 所示:

1
2
3
4
5
<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>

默认情况下,作业将以并发方式运行。

使用触发器和SchedulerFactoryBean连接作业

我们创建了 job 细节和工作。我们还回顾了 easy bean,它允许您在特定的 object 上调用方法。当然,我们仍然需要自己安排工作。这是通过使用触发器和SchedulerFactoryBean来完成的。 Quartz 中有几个触发器,Spring 提供两个 Quartz FactoryBean implementations,默认方便:CronTriggerFactoryBeanSimpleTriggerFactoryBean

需要安排触发器。 Spring 提供SchedulerFactoryBean,公开要设置为 properties 的触发器。 SchedulerFactoryBean使用这些触发器安排实际作业。

以下列表同时使用SimpleTriggerFactoryBeanCronTriggerFactoryBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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>

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

1
2
3
4
5
6
7
8
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger"/>
<ref bean="simpleTrigger"/>
</list>
</property>
</bean>

SchedulerFactoryBean可以使用更多 properties,例如 job 详细信息使用的日历,properties 自定义 Quartz 等等。有关更多信息,请参阅SchedulerFactoryBean 负责 javadoc。

缓存抽象

从 version 3.1 开始,Spring Framework 支持透明地向现有的 Spring application 添加缓存。与交易支持类似,缓存抽象允许一致使用各种缓存解决方案,而对 code 的影响最小。

从 Spring 4.1 开始,在JSR-107 注释注解)和更多自定义选项的支持下,缓存抽象得到了显着改进。

了解缓存抽象

缓存与缓冲区

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

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

您可以找到缓冲区和缓存这里#The_difference_between_buffer_and_cache)之间差异的进一步说明。

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

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

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

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

Spring 提供了一些实现的抽象:基于 JDK java.util.concurrent.ConcurrentMap的缓存,Ehcache 2.x,Gemfire 缓存,咖啡因和 JSR-107 兼容缓存(例如 Ehcache 3.x)。有关插入其他缓存存储和提供程序的更多信息,请参阅Plugging-in 不同的 Back-end 缓存

缓存抽象对 multi-threaded 和 multi-process 环境没有特殊处理,因为 features 由 cache implementation 处理。 。

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

缓存特定 item 直接等效于程序化缓存交互中发现的典型 get-if-not-found-then - proceed-and-put-eventually code 块。没有应用锁,并且多个线程可能会尝试同时加载相同的 item。驱逐也是如此。如果多个线程试图同时更新或逐出数据,则可以使用陈旧数据。某些缓存提供程序在该区域中提供高级 features。有关更多详细信息,请参阅缓存提供程序的文档。

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

  • 缓存声明:确定需要缓存的方法及其 policy。
  • 高速缓存 configuration:存储数据并从中读取数据的后备高速缓存。

声明式Annotation-based缓存

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

  • @Cacheable:触发缓存填充。
  • @CacheEvict:触发缓存逐出。
  • @CachePut:更新缓存而不会干扰方法执行。
  • @Caching:重新组合要在方法上应用的多个缓存操作。
  • @CacheConfig:在 class-level 分享一些 common cache-related 设置。

Cacheable注解

正如 name 所暗示的那样,您可以使用@Cacheable来划分可缓存的方法 - 即,将结果存储在缓存中的方法,以便在后续调用(具有相同的 arguments)时,返回缓存中的 value 而不必须实际执行该方法。在最简单的形式中,annotation 声明需要与带注释的方法关联的缓存的 name,如下面的 example 所示:

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

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

所有其他不包含 value 的高速缓存也会更新,即使实际上没有执行高速缓存的方法。

以下 example 在findBook方法上使用@Cacheable

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

由于高速缓存本质上是 key-value stores,因此需要将每个高速缓存方法的调用转换为适合高速缓存访问的 key。缓存抽象使用基于以下算法的简单KeyGenerator

  • 如果没有给出参数,return SimpleKey.EMPTY
  • 如果只给出一个参数,则 return 该实例。
  • 如果给出了更多的参数,则 return SimpleKey包含所有参数。

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

要提供不同的默认 key generator,需要实现org.springframework.cache.interceptor.KeyGenerator接口。

随着 Spring 4.0 的发布,默认的 key 生成策略发生了变化。早期版本的 Spring 使用了 key 生成策略,对于多个 key 参数,只考虑hashCode()参数而不是equals()。这可能会导致意外的 key 碰撞(请参阅背景的SPR-10237)。新的SimpleKeyGenerator使用复合 key 来表示这种情况。

如果要继续使用以前的 key 策略,可以配置已弃用的org.springframework.cache.interceptor.DefaultKeyGenerator class 或创建自定义 hash-based KeyGenerator implementation。

自定义Key生成声明

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

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

乍一看,虽然两个boolean arguments 影响了书的发现方式,但它们对缓存没有用处。如果两个中只有一个重要而另一个不重要怎么办?

对于这种情况,@Cacheable annotation 允许您指定 key 如何通过其key属性生成。您可以使用规划环境地政司来选择感兴趣的 arguments(或它们的嵌套 properties),执行操作,甚至调用任意方法,而无需编写任何 code 或实现任何接口。这是默认 generator的推荐方法,因为随着 code 基数的增长,签名在签名方面往往会有很大不同。虽然默认策略可能适用于某些方法,但它很少适用于所有方法。

以下示例是各种 SpEL 声明(如果您不熟悉 SpEL,请自己帮忙并阅读Spring 表达语言

1
2
3
4
5
6
7
8
@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)

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

如果负责生成 key 的算法太具体或者需要共享,则可以在操作上定义自定义keyGenerator。为此,请指定要使用的KeyGenerator bean implementation 的 name,如下面的 example 所示:

1
2
@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

keykeyGenerator参数是互斥的,并且指定两者的操作都会导致 exception。

默认缓存分辨率

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

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

自定义缓存分辨率

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

对于适用于多个缓存 managers 的 applications,您可以设置cacheManager用于每个操作,如下面的 example 所示:

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

您也可以以类似于替换key 代的方式完全替换CacheResolver。为每个缓存操作请求解析,让 implementation 实际上解析基于 runtime arguments 使用的缓存。以下 example 显示了如何指定CacheResolver

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

从 Spring 4.1 开始,cache annotations 的value属性不再是必需的,因为无论 annotation 的内容如何,CacheResolver都可以提供此特定信息。

keykeyGenerator类似,cacheManagercacheResolver参数是互斥的,指定两者的操作都会导致 exception。作为自定义CacheManagerCacheResolver implementation 忽略。这可能不是你所期望的。

同步缓存

在 multi-threaded 环境中,可能会为同一参数同时调用某些操作(通常在启动时)。默认情况下,缓存抽象不会锁定任何内容,并且可能会多次计算相同的 value,从而无法实现缓存。

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

1
2
@Cacheable(cacheNames="foos", sync=true) (1)
public Foo executeExpensiveOperation(String id) {...}
1 使用sync属性。

这是一个可选的 feature,您最喜欢的缓存 library 可能不支持它。核心 framework 提供的所有CacheManager _implement 都支持它。有关更多详细信息,请参阅缓存提供程序的文档。

条件缓存

有时,方法可能不适合缓存所有 time(对于 example,它可能取决于给定的 arguments)。 cache annotations 通过condition参数支持此类功能,该参数采用SpEL表达式,该表达式被评估为truefalse。如果true,则缓存该方法。如果不是,则其行为就好像该方法未被缓存(即,无论缓存中的值是什么,或者使用了哪些 arguments,该方法每 time 执行一次)。对于 example,仅当参数name的长度小于 32 时,才会缓存以下方法:

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

除了condition参数之外,您还可以使用unless参数来否决向缓存添加 value。与condition不同,unless表达式在调用方法后进行计算。要扩展上一个 example,也许我们只想缓存平装书,如下例所示:

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

高速缓存抽象支持java.util.Optional,仅当它存在时才使用其内容作为高速缓存的 value。 #result始终引用业务实体,从不支持 wrapper,因此可以按如下方式重写上一个 example:

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

请注意,result仍然是指Book而不是Optional。因为它可能是null,我们应该使用安全导航 operator。

可用的缓存SpEL评估上下文

每个SpEL表达式都针对专用context进行求值。除 built-in 参数外,framework 还提供专用的 caching-related 元数据,例如参数名称。以下 table 描述了 context 可用的项目,以便您可以将它们用于 key 和条件计算:

名称 地点 描述
methodName Root object 要调用的方法的 name #root.methodName
method Root object 正在调用的方法 #root.method.name
target Root object 正在调用目标 object #root.target
targetClass Root object 正在调用的目标的 class #root.targetClass
args Root object arguments(as array)用于调用目标 #root.args[0]
caches Root object 执行当前方法的高速缓存的集合 #root.caches[0].name
参数 name Evaluation context 任何方法 arguments 的 Name。如果名称不可用(可能由于没有调试信息),参数名称也可以在#a<#arg>下获得,其中#arg代表参数索引(从0开始)。 #iban#a0(您也可以使用#p0#p<#arg>表示法作为别名)。
result Evaluation context 方法调用的结果(要缓存的 value)。仅在unless表达式,cache put表达式(用于计算key)或cache evict表达式(当beforeInvocationfalse时)中可用。对于受支持的包装器(例如Optional),#result指的是实际的 object,而不是 wrapper。 #result

CachePut注解

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

1
2
@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)

通常强烈建议不要在同一方法上使用@CachePut@Cacheable 注释,因为它们具有不同的行为。虽然后者导致通过使用缓存跳过方法执行,但前者强制执行 order 以执行缓存更新。这会导致意外行为,并且由于特定 corner-cases 的 exception(例如 annotations 具有将它们彼此排除的条件),应该避免这样的声明。另请注意,此类条件不应依赖于结果 object(即#result变量),因为这些条件已经过验证 up-front 以确认排除。

CacheEvict注解

缓存抽象不仅允许缓存 store 的填充,还允许驱逐。此 process 对于从缓存中删除过时或未使用的数据非常有用。与@Cacheable相反,@CacheEvict划分执行缓存逐出的方法(即,用作从缓存中删除数据的触发器的方法)。与其兄弟类似,@CacheEvict需要指定受操作影响的一个或多个缓存,允许自定义缓存和 key 解析或指定条件,并 features 一个额外参数(allEntries),指示 cache-wide 驱逐是否需要执行而不仅仅是一个条目驱逐(基于 key)。以下 example 清除books缓存中的所有条目:

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

当需要清除整个缓存区域时,此选项会派上用场。而不是驱逐每个条目(这将花费 long time,因为它效率低),所有条目在一个操作中被移除,如前面的 example 所示。请注意,framework 忽略了此方案中指定的任何 key,因为它不适用(整个缓存被驱逐,而不仅仅是一个条目)。

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

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

Caching注解

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

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

CacheConfig注解

到目前为止,我们已经看到缓存操作提供了许多自定义选项,您可以为每个操作设置这些选项。但是,如果某些自定义选项适用于 class 的所有操作,则可能需要配置一些自定义选项。例如,指定用于 class 的每个高速缓存操作的高速缓存的 name 可以由单个 class-level 定义替换。这是@CacheConfig发挥作用的地方。以下示例使用@CacheConfig来设置缓存的 name:

1
2
3
4
5
6
@CacheConfig("books") (1)
public class BookRepositoryImpl implements BookRepository {

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

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

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

  • 全局配置,可用于CacheManagerKeyGenerator
  • 在 class level 中,使用@CacheConfig
  • 在 level 操作。

启用缓存注解

重要的是要注意,尽管声明缓存注释不会自动触发它们的操作 - 就像 Spring 中的许多内容一样,feature 必须以声明方式启用(这意味着如果您怀疑缓存是责任,您可以通过删除来禁用它只有一个 configuration line 而不是 code 中的所有 annotations。

要启用缓存注释,请将注释@EnableCaching添加到@Configuration classes 中的一个:

1
2
3
4
@Configuration
@EnableCaching
public class AppConfig {
}

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

1
2
3
4
5
6
7
8
9
<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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">

<cache:annotation-driven/>
</beans>

cache:annotation-driven元素和@EnableCaching annotation 都允许您指定影响缓存行为通过 AOP 添加到 application 的方式的各种选项。 configuration 有意与@Transactional类似。

处理缓存注释的默认建议模式是proxy,它允许仅通过代理拦截 calls。同一 class 中的本地 calls 不能以这种方式截获。对于更高级的拦截模式,请考虑结合 compile-time 或 load-time 编织切换到aspectj模式。

有关实现CachingConfigurer所需的高级自定义(使用 Java configuration)的更多详细信息,请参阅javadoc

XML 属性 Annotation 属性 默认 描述
cache-manager N/A(参见CachingConfigurer javadoc) cacheManager 要使用的缓存 manager 的 name。使用此缓存 manager 在后台初始化默认CacheResolver(如果未设置,则为cacheManager)。有关缓存分辨率的更多 fine-grained 管理,请考虑设置“cache-resolver”属性。
cache-resolver N/A(参见CachingConfigurer javadoc) A SimpleCacheResolver使用配置的cacheManager 要用于解析后备高速缓存的 CacheResolver 的 bean name。此属性不是必需的,只需要指定为’cache-manager’属性的替代。
key-generator N/A(参见CachingConfigurer javadoc) SimpleKeyGenerator 要使用的自定义 key generator 的名称。
error-handler N/A(参见CachingConfigurer javadoc) SimpleCacheErrorHandler 要使用的自定义缓存错误处理程序的 name。默认情况下,在缓存相关操作期间抛出的任何 exception 都会返回到 client。
mode mode proxy 默认模式(proxy)通过使用 Spring 的 AOP framework 处理带注释的 beans 代理(遵循代理语义,如前所述,仅适用于通过代理进入的方法 calls)。替代模式(aspectj)使用 Spring 的 AspectJ 缓存 aspect 编译受影响的 classes,修改目标 class byte code 以应用于任何类型的方法调用。 AspectJ 编织需要在 classpath 中启用spring-aspects.jar以及启用 load-time 编织(或 compile-time 编织)。 (有关如何设置 load-time weaving.)的详细信息,请参阅Spring configuration
proxy-target-class proxyTargetClass false 仅适用于代理模式。控制为使用@Cacheable@CacheEvict 注释注释的 class 创建的缓存代理类型。如果proxy-target-class属性设置为true,则会创建 class-based 个代理。如果proxy-target-classfalse或者省略了该属性,则会创建标准 JDK interface-based 代理。 (有关不同代理人的详细检查,请参阅代理机制 types.)
order order Ordered.LOWEST_PRECEDENCE 定义应用于使用@Cacheable@CacheEvict注释的 beans 的缓存建议的 order。 (有关与排序 AOP 建议相关的规则的更多信息,请参阅建议订购 .)未指定 ordering 意味着 AOP 子系统确定建议的 order。

<cache:annotation-driven/>仅在定义它的同一 application context 中的 beans 上查找@Cacheable/@CachePut/@CacheEvict/@Caching。这意味着,如果您将<cache:annotation-driven/>放在WebApplicationContextDispatcherServlet,它只会在您的控制器中检查 beans,而不是您的服务。有关更多信息,请参见MVC 部分

方法可见性和缓存注释

使用代理时,应将 cache annotations 仅应用于具有公共可见性的方法。如果使用这些注释注释 protected,private 或 package-visible 方法,则不会引发错误,但带注释的方法不会显示已配置的缓存设置。如果需要注释 non-public 方法,请考虑使用 AspectJ(参见本节的 rest),因为它会更改字节码本身。

Spring 建议您只使用@Cache* annotation 注释具体的 classes(以及具体 classes 的方法),而不是注释接口。您当然可以将@Cache* annotation 放在接口(或接口方法)上,但这只能在您使用 interface-based 代理时按预期工作。 Java annotations 不是从接口继承的事实意味着,如果使用 class-based 代理(proxy-target-class="true")或 weaving-based aspect(mode="aspectj"),代理和编织基础结构无法识别缓存设置,并且 object 不包含在缓存代理,这将是非常糟糕的。

在代理模式(默认)下,只拦截通过代理进入的外部方法 calls。这意味着 self-invocation(实际上,目标 object 中的一个方法 calls 目标 object 的另一个方法)在运行时不会导致实际的缓存,即使被调用的方法用@Cacheable标记。在这种情况下,请考虑使用aspectj模式。此外,必须完全初始化代理以提供预期的行为,因此您不应该在初始化 code(即@PostConstruct)中依赖此 feature。

使用自定义注解

自定义 annotation 和 AspectJ

此 feature 仅适用于 proxy-based 方法,但可以通过使用 AspectJ 进行一些额外的工作。

spring-aspects模块仅为标准 annotations 定义 aspect。如果您已经定义了自己的注释,则还需要为这些注释定义 aspect。检查AnnotationCacheAspect是否为 example。

缓存抽象允许您使用自己的注释来标识触发缓存填充或驱逐的方法。这作为模板机制非常方便,因为它消除了复制缓存注释声明的需要,如果指定了 key 或条件或者 code 基础中不允许外部导入(org.springframework),这尤其有用。与铅板 annotations 的 rest 类似,您可以使用@Cacheable@CachePut@CacheEvict@CacheConfig作为meta-annotations(即可以注释其他注释的注释)。在下面的示例中,我们用自己的自定义 annotation 替换 common @Cacheable声明:

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

在前面的 example 中,我们定义了自己的SlowService annotation,它本身用@Cacheable注释。现在我们可以替换以下 code:

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

以下 example 显示了 custom annotation,我们可以使用它来替换前面的 code:

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

即使@SlowService不是 Spring annotation,容器也会在运行时自动获取其声明并理解其含义。请注意,如上所述,需要启用 annotation-driven 行为。

JCache(JSR-107)注解

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

Feature摘要

对于那些熟悉 Spring 的缓存注释的人,下面的 table 描述了 Spring annotations 和 JSR-107 对应物之间的主要区别:

弹簧 JSR-107 备注
@Cacheable @CacheResult 非常相似。 @CacheResult可以缓存特定的 exceptions 并强制执行该方法,而不管缓存的内容如何。
@CachePut @CachePut 当 Spring 使用方法调用的结果更新缓存时,JCache 要求将其作为使用@CacheValue注释的参数传递。由于这种差异,JCache 允许在实际方法调用之前或之后更新缓存。
@CacheEvict @CacheRemove 非常相似。当方法调用导致 exception 时,@CacheRemove支持条件驱逐。
@CacheEvict(allEntries=true) @CacheRemoveAll @CacheRemove
@CacheConfig @CacheDefaults 允许您以类似的方式配置相同的概念。

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

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

1
2
@CacheResult(cacheNames="books", cacheResolverFactory=MyCacheResolverFactory.class) (1)
public Book findBook(ISBN isbn)
1 为此操作自定义工厂。

对于所有引用的 classes,Spring 尝试找到具有给定类型的 bean。如果存在多个 match,则会创建一个新实例,并且可以使用常规 bean 生命周期回调,例如依赖项注入。

密钥由javax.cache.annotation.CacheKeyGenerator生成,其作用与 Spring 的KeyGenerator相同。默认情况下,除非至少有一个参数使用@CacheKey注释,否则将考虑所有方法 arguments。这类似于 Spring 的自定义 key 生成声明。例如,以下是相同的操作,一个使用 Spring 的抽象,另一个使用 JCache:

1
2
3
4
5
@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@CacheResult(cacheName="books")
public Book findBook(@CacheKey ISBN isbn, boolean checkWarehouse, boolean includeUsed)

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

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

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

启用JSR-107支持

除了 Spring 的声明性 annotation 支持之外,您无需执行任何特定的操作来启用 JSR-107 支持。如果 class 路径中存在 JSR-107 API 和spring-context-support模块,则@EnableCachingcache:annotation-driven元素都会自动启用 JCache 支持。

根据您的使用情况,选择基本上是您的。您甚至可以在某些服务上使用 JSR-107 API 并在其他服务器上使用 Spring 自己的注释来混合和 match 服务。但是,如果这些服务影响相同的缓存,则应使用一致且相同的 key generation implementation。

声明式XML-based缓存

如果 annotations 不是一个选项(可能是由于无法访问源或没有外部 code),您可以使用 XML 进行声明性缓存。因此,您可以在外部指定目标方法和缓存指令,而不是注释缓存方法(类似于声明式 transaction management 忠告)。上一节中的 example 可以转换为以下 example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 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 -->

在前面的 configuration 中,bookService可以缓存。要应用的缓存语义封装在cache:advice定义中,这会导致findBooks方法用于将数据放入缓存,而loadBooks方法用于驱逐数据。这两个定义都适用于books缓存。

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

声明性 XML 缓存支持所有 annotation-based model,因此在两者之间移动应该相当容易。此外,两者都可以在同一个 application 中使用。 XML-based 方法不会触及目标 code。然而,它本质上更加冗长。当处理具有重载方法的 classes 时,识别正确的方法确实需要额外的努力,因为method参数不是一个好的鉴别器。在这些情况下,您可以使用 AspectJ 切入点来挑选目标方法并应用适当的缓存功能。但是,通过 XML,更容易应用包或 group 或 interface-wide 缓存(同样,由于 AspectJ 切入点)并创建 template-like 定义(正如我们在前面的 example 中通过cache:definitions cache属性定义目标缓存所做的那样)。

配置缓存存储

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

JDK基于ConcurrentMap的缓存

JDK-based Cache implementation 位于org.springframework.cache.concurrent包下。它允许您使用ConcurrentHashMap作为后台Cache store。以下 example 显示了如何配置两个缓存:

1
2
3
4
5
6
7
8
9
<!-- 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为名为defaultbooks的两个嵌套ConcurrentMapCache实例创建CacheManager。请注意,名称是直接为每个缓存配置的。

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

Ehcache-based缓存

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

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

1
2
3
4
5
6
<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 library(通过ehcache bean),然后将其连接到专用的CacheManager implementation。请注意,从ehcache.xml读取整个 ehcache-specific configuration。

Caffeine缓存

Caffeine 是 Guava 缓存的 Java 8 rewrite,它的 implementation 位于org.springframework.cache.caffeine包中,可以访问 Caffeine 的几个 feature。

以下 example 配置CacheManager,根据需要创建缓存:

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

您还可以提供明确使用的缓存。在这种情况下,只有那些由 manager 提供。以下 example 显示了如何执行此操作:

1
2
3
4
5
6
7
8
<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
<property name="caches">
<set>
<value>default</value>
<value>books</value>
</set>
</property>
</bean>

Caffeine CacheManager还支持自定义CaffeineCacheLoader。有关这些内容的详细信息,请参阅Caffeine 文档

GemFire-based缓存

GemFire 是一个 memory-oriented,disk-backed,可弹性扩展,持续可用,active(具有 built-in pattern-based 订阅通知),全局复制数据库并提供 fully-featured 边缘缓存。有关如何将 GemFire 用作CacheManager(及更多)的更多信息,请参阅Spring Data GemFire reference 文档

JSR-107缓存

Spring 的缓存抽象也可以使用 JSR-107-compliant 缓存。 JCache implementation 位于org.springframework.cache.jcache包中。

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

1
2
3
4
5
6
<bean id="cacheManager"
class="org.springframework.cache.jcache.JCacheCacheManager"
p:cache-manager-ref="jCacheManager"/>

<!-- JSR-107 cache manager setup -->
<bean id="jCacheManager" .../>

在没有支持Store的情况下处理缓存

有时,在切换环境或进行测试时,您可能会在没有配置实际的后备缓存的情况下进行缓存声明。由于这是一个无效的 configuration,因此在运行时抛出了 exception,因为缓存基础结构无法找到合适的 store。在这种情况下,而不是删除缓存声明(这可能证明是乏味的),您可以连接一个不执行缓存的简单虚拟缓存 - 也就是说,它强制每隔 time 执行缓存的方法。以下 example 显示了如何执行此操作:

1
2
3
4
5
6
7
8
9
<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 flag 为所有未由配置的缓存 managers 处理的定义添加 no-op 缓存。也就是说,jdkCachegemfireCache(在 example 中先前配置)中找不到的每个缓存定义都由 no-op 缓存处理,该缓存不存储任何信息,导致每隔 time 执行一次目标方法。

Plugging-in不同的Back-end缓存

显然,有很多缓存产品可以用作支持 store。要插入它们,您需要提供CacheManagerCache implementation,因为遗憾的是,我们无法使用可用的标准。这可能听起来比实际上更难,因为在实践中,class 往往是简单的适配器,它将缓存抽象 framework 映射到存储 API 之上,就像ehcache classes 那样。大多数CacheManager classes 可以使用org.springframework.cache.support包中的 classes(例如AbstractCacheManager,它负责 boiler-plate code,只留下实际的映射完成)。我们希望,在 time 中,提供与 Spring 的整合的 libraries 可以填补这个小的 configuration 差距。

如何设置TTL/TTI/驱逐策略/XXX特性?

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

附录

XML模式

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

jee模式

jee元素处理与 Java EE(Java Enterprise Edition)configuration 相关的问题,例如查找 JNDI object 和定义 EJB references。

要使用jee schema 中的元素,您需要在 Spring XML configuration 文件的顶部添加以下前导码。以下代码段中的文本引用了正确的 schema,以便jee命名空间中的元素可供您使用:

1
2
3
4
5
6
7
8
9
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd">

<!-- bean definitions here -->
</beans>
<jee:jndi-lookup/> (simple)

以下 example 显示了如何使用 JNDI 在没有jee schema 的情况下查找数据源:

1
2
3
4
5
6
7
<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>

以下 example 显示了如何使用 JNDI 使用jee schema 查找数据源:

1
2
3
4
5
6
<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 环境设置)

以下 example 显示了如何使用 JNDI 在没有jee的情况下查找环境变量:

1
2
3
4
5
6
7
8
<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>

以下 example 显示了如何使用 JNDI 查找带有jee的环境变量:

1
2
3
<jee:jndi-lookup id="simple" jndi-name="jdbc/MyDataSource">
<jee:environment>ping=pong</jee:environment>
</jee:jndi-lookup>
<jee:jndi-lookup/>(具有多个JNDI环境设置)

以下 example 显示了如何使用 JNDI 在没有jee的情况下查找多个环境变量:

1
2
3
4
5
6
7
8
9
<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>

以下 example 显示了如何使用 JNDI 使用jee查找多个环境变量:

1
2
3
4
5
6
7
<jee:jndi-lookup id="simple" jndi-name="jdbc/MyDataSource">
<!-- newline-separated, key-value pairs for the environment (standard Properties format) -->
<jee:environment>
sing=song
ping=pong
</jee:environment>
</jee:jndi-lookup>
<jee:jndi-lookup/>(Complex)

以下 example 显示了如何在没有jee的情况下使用 JNDI 查找数据源和许多不同的 properties:

1
2
3
4
5
6
7
8
<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>

以下 example 显示了如何使用 JNDI 通过jee查找数据源和许多不同的 properties:

1
2
3
4
5
6
7
<jee:jndi-lookup id="simple"
jndi-name="jdbc/MyDataSource"
cache="true"
resource-ref="true"
lookup-on-startup="false"
expected-type="com.myapp.DefaultThing"
proxy-interface="com.myapp.Thing"/>
<jee:local-slsb/>(Simple)

<jee:local-slsb/>元素配置对本地 EJB Stateless SessionBean 的 reference。

以下 example 显示了如何在没有jee的情况下将 reference 配置到本地 EJB Stateless SessionBean:

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

以下 example 显示了如何使用jee配置 reference 到本地 EJB Stateless SessionBean:

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

<jee:local-slsb/>元素配置对本地 EJB Stateless SessionBean 的 reference。

以下 example 显示了如何配置 reference 到本地 EJB Stateless SessionBean 和许多没有jee的 properties:

1
2
3
4
5
6
7
8
<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>

以下 example 显示了如何使用jee配置 reference 到本地 EJB Stateless SessionBean 和多个 properties:

1
2
3
4
5
6
<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/>元素配置__ EJB Stateless SessionBean 的 reference。

以下 example 显示了如何在没有jee的情况下配置 remote EJB Stateless SessionBean 的 reference:

1
2
3
4
5
6
7
8
9
10
<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>

以下 example 显示了如何使用jee配置 remote EJB Stateless SessionBean 的 reference:

1
2
3
4
5
6
7
8
<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">

jms模式

jms元素处理配置 JMS-related beans,例如 Spring 的消息 Listener 容器。这些元素在JMS 章节标题为JMS 命名空间支持的部分详细说明。有关此支持和jms元素本身的完整详细信息,请参阅该章。

为了完整性,要使用jms schema 中的元素,您需要在 Spring XML configuration 文件的顶部包含以下前导码。以下代码段中的文本引用了正确的 schema,以便jms命名空间中的元素可供您使用:

1
2
3
4
5
6
7
8
9
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms.xsd">

<!-- bean definitions here -->
</beans>

使用<context:mbean-export/>

这个元素在配置 Annotation-based MBean Export中有详细说明。

缓存Schema

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

要使用cache schema 中的元素,您需要在 Spring XML configuration 文件的顶部添加以下前导码。以下代码段中的文本引用了正确的 schema,以便cache命名空间中的元素可供您使用:

1
2
3
4
5
6
7
8
9
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">

<!-- bean definitions here -->
</beans>