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