![]() |
逃跑的生菜 · 在GPU上运行MATLAB程序 - 知乎· 1 年前 · |
![]() |
爱听歌的咖啡豆 · Multipart Content :: ...· 1 年前 · |
![]() |
没人理的蚂蚁 · 龙与地下城:侠盗荣耀(2023)4K(12. ...· 1 年前 · |
![]() |
很拉风的烈酒 · 蜻蜓标志的跑车是什么车多少钱(蜻蜓的标志的跑 ...· 1 年前 · |
![]() |
心软的火锅 · 将 JavaScript ...· 1 年前 · |
参考文档的这一部分涵盖了 Spring Framework 与许多 Java EE(及相关)技术的集成。
Spring 具有集成类,用于通过各种技术来远程支持。远程支持简化了由常规(Spring)POJO 实施的启用远程服务的开发。当前,Spring 支持以下远程技术:
远程方法调用(RMI)
:通过使用
RmiProxyFactoryBean
和
RmiServiceExporter
,Spring 支持传统的 RMI(具有
java.rmi.Remote
接口和
java.rmi.RemoteException
)以及通过 RMI 调用者进行透明远程处理(具有任何 Java 接口)。
Spring 的 HTTP 调用程序
:Spring 提供了一种特殊的远程处理策略,该策略允许通过 HTTP 进行 Java 序列化,从而支持任何 Java 接口(就像 RMI 调用程序一样)。相应的支持类别为
HttpInvokerProxyFactoryBean
和
HttpInvokerServiceExporter
。
Hessian
:通过使用 Spring 的
HessianProxyFactoryBean
和
HessianServiceExporter
,您可以通过 Caucho 提供的基于 HTTP 的轻量级二进制协议透明地公开您的服务。
JAX-WS :Spring 通过 JAX-WS(Java EE 5 和 Java 6 中引入的 JAX-RPC 的继承者)为 Web 服务提供远程支持。
JMS
:通过
JmsInvokerServiceExporter
和
JmsInvokerProxyFactoryBean
类支持使用 JMS 作为基础协议进行远程处理。
AMQP :Spring AMQP 项目支持使用 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 将服务公开给远程 Client 端,然后再谈谈使用 RMI 的缺点。然后 continue 以使用 Hessian 作为协议的示例。
1.1. 使用 RMI 公开服务
通过使用 Spring 对 RMI 的支持,您可以通过 RMI 基础结构透明地公开服务。进行了此设置之后,除了不存在对安全上下文传播或远程事务传播的标准支持这一事实之外,您基本上具有与远程 EJB 相似的配置。当您使用 RMI 调用程序时,Spring 确实为此类附加调用上下文提供了钩子,因此,例如,您可以插入安全框架或自定义安全凭证。
1.1.1. 使用 RmiServiceExporter 导出服务
使用
RmiServiceExporter
,我们可以将 AccountService 对象的接口公开为 RMI 对象。可以使用RmiProxyFactoryBean
来访问该接口,或者在传统 RMI 服务的情况下可以通过普通 RMI 来访问该接口。RmiServiceExporter
明确支持通过 RMI 调用程序公开任何非 RMI 服务。我们首先必须在 Spring 容器中设置服务。以下示例显示了如何执行此操作:
<bean id="accountService" class="example.AccountServiceImpl"> <!-- any additional properties, maybe a DAO? --> </bean>
接下来,我们必须使用
RmiServiceExporter
公开我们的服务。以下示例显示了如何执行此操作:
<bean class="org.springframework.remoting.rmi.RmiServiceExporter"> <!-- does not necessarily have to be the same name as the bean to be exported --> <property name="serviceName" value="AccountService"/> <property name="service" ref="accountService"/> <property name="serviceInterface" value="example.AccountService"/> <!-- defaults to 1099 --> <property name="registryPort" value="1199"/> </bean>
在前面的示例中,我们覆盖了 RMI 注册表的端口。通常,您的应用服务器还维护一个 RMI 注册表,因此最好不要干涉该注册表。此外,服务名称用于绑定服务。因此,在前面的示例中,服务绑定在
'rmi://HOST:1199/AccountService'
处。稍后,我们将使用此 URL 链接到 Client 端的服务。
servicePort
属性已被省略(默认为 0)。这意味着将使用匿名端口与服务进行通信。
1.1.2. 在 Client 端链接服务
我们的 Client 是一个简单的对象,它使用
AccountService
来 Management 帐户,如以下示例所示:public class SimpleObject { private AccountService accountService; public void setAccountService(AccountService accountService) { this.accountService = accountService; // additional methods using the accountService
为了在 Client 端上链接服务,我们创建了一个单独的 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>
这就是我们要在 Client 端上支持远程帐户服务所需要做的一切。 Spring 透明地创建一个调用程序,并通过
RmiServiceExporter
远程启用帐户服务。在 Client 端,我们使用RmiProxyFactoryBean
将其链接。
1.2. 使用 Hessian 通过 HTTP 远程调用服务
Hessian 提供了一个基于 HTTP 的二进制远程协议。它由 Caucho 开发,您可以在http://www.caucho.com上找到有关 Hessian 本身的更多信息。
1.2.1. 为 Hessian 连接 DispatcherServlet
Hessian 通过 HTTP 进行通信,并通过使用自定义 servlet 进行通信。通过使用 Spring 的
DispatcherServlet
原理(请参阅[webmvc#mvc-servlet]),我们可以连接这样的 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
原理,那么您可能知道现在必须在WEB-INF
目录中创建一个名为remoting-servlet.xml
(在 Servlet 名称之后)的 Spring 容器配置资源。下一节将使用应用程序上下文。或者,考虑使用 Spring 的简单
HttpRequestHandlerServlet
。这样做使您可以将远程导出程序定义嵌入到根应用程序上下文中(默认情况下,在WEB-INF/applicationContext.xml
中),而单个 Servlet 定义则指向特定的导出程序 bean。在这种情况下,每个 servlet 名称都必须与其目标导出器的 bean 名称相匹配。1.2.2. 使用 HessianServiceExporter 公开您的 Bean
在新创建的名为
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>
现在,我们准备在 Client 端链接服务。没有指定显式处理程序 Map(将请求 URLMap 到服务),因此我们使用
BeanNameUrlHandlerMapping
。因此,该服务将在包含DispatcherServlet
实例的 Map(如先前定义)http://HOST:8080/remoting/AccountService
中通过其 bean 名称指示的 URL 导出。另外,您可以在根应用程序上下文中(例如,在
WEB-INF/applicationContext.xml
中)创建HessianServiceExporter
,如以下示例所示:
<bean name="accountExporter" class="org.springframework.remoting.caucho.HessianServiceExporter"> <property name="service" ref="accountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean>
在后一种情况下,您应该在
web.xml
中为此导出程序定义一个相应的 servlet,最终结果相同:导出程序 Map 到/remoting/AccountService
的请求路径。注意,servlet 名称需要与目标导出器的 bean 名称匹配。以下示例显示了如何执行此操作:
<servlet> <servlet-name>accountExporter</servlet-name> <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>accountExporter</servlet-name> <url-pattern>/remoting/AccountService</url-pattern> </servlet-mapping>
1.2.3. 在 Client 端上链接服务
通过使用
HessianProxyFactoryBean
,我们可以在 Client 端链接服务。与 RMI 示例相同的原理适用。我们创建一个单独的 bean 工厂或应用程序上下文,并通过使用AccountService
来 Management 帐户来提及SimpleObject
所在的以下 bean,如以下示例所示:<bean class="example.SimpleObject"> <property name="accountService" ref="accountService"/> </bean> <bean id="accountService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean"> <property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean>
1.2.4. 将 HTTP 基本身份验证应用于通过 Hessian 公开的服务
Hessian 的优点之一是我们可以轻松地应用 HTTP 基本身份验证,因为这两种协议都是基于 HTTP 的。例如,可以通过使用
web.xml
安全功能来应用常规的 HTTP 服务器安全性机制。通常,您无需在此处使用每个用户的安全凭证。相反,您可以使用在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
并设置了一个拦截器,以仅让 Management 员和操作员调用此应用程序上下文中提到的 bean。
前面的示例未显示灵活的安全基础结构。有关安全性的更多选项,请查看位于http://projects.spring.io/spring-security/的 Spring Security 项目。
1.3. 使用 HTTP 调用程序公开服务
与 Hessian 相反,Spring HTTP 调用程序都是轻量级协议,它们使用自己的苗条序列化机制,并使用标准 Java 序列化机制通过 HTTP 公开服务。如果您的参数和返回类型是无法通过使用 Hessian 使用的序列化机制进行序列化的复杂类型,则这将具有巨大的优势(选择远程处理技术时,请参阅下一节以获得更多注意事项)。
在幕后,Spring 使用 JDK 或 Apache
HttpComponents
提供的标准功能来执行 HTTP 调用。如果您需要更高级且更易于使用的功能,请使用后者。有关更多信息,请参见hc.apache.org/httpcomponents-client-ga/。Warning
注意由于不安全的 Java 反序列化而导致的漏洞:在反序列化步骤中,操纵的 Importing 流可能导致服务器上有害的代码执行。因此,请勿将 HTTP 调用方终结点暴露给不受信任的 Client 端。而是仅在您自己的服务之间公开它们。通常,强烈建议您改用其他任何消息格式(例如 JSON)。
如果您担心由 Java 序列化引起的安全漏洞,请考虑在核心 JVM 级别上使用通用序列化筛选器机制,该机制最初是为 JDK 9 开发的,但同时又移植到 JDK 8、7 和 6.参见https://blogs.oracle.com/java-platform-group/entry/incoming_filter_serialization_data_a和http://openjdk.java.net/jeps/290。
1.3.1. 公开服务对象
为服务对象设置 HTTP 调用程序基础结构与使用 Hessian 进行设置的方法非常相似。由于 Hessian 支持提供了
HessianServiceExporter
,Spring 的 HttpInvoker 支持提供了org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter
。为了在 Spring Web MVC
DispatcherServlet
中公开AccountService
(前面提到),需要在调度程序的应用程序上下文中进行以下配置,如以下示例所示:<bean name="/AccountService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"> <property name="service" ref="accountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean>
如关于黑森 State 的部分所述,此类导出程序定义通过
DispatcherServlet
实例的标准 Map 工具公开。另外,您可以在根应用程序上下文中(例如,在
'WEB-INF/applicationContext.xml'
中)创建HttpInvokerServiceExporter
,如以下示例所示:<bean name="accountExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"> <property name="service" ref="accountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean>
另外,您可以在
web.xml
中为此导出程序定义一个相应的 servlet,该 servlet 名称与目标导出程序的 bean 名称匹配,如以下示例所示:<servlet> <servlet-name>accountExporter</servlet-name> <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>accountExporter</servlet-name> <url-pattern>/remoting/AccountService</url-pattern> </servlet-mapping>
1.3.2. 在 Client 端链接服务
同样,从 Client 端链接服务与使用 Hessian 时的方式非常相似。通过使用代理,Spring 可以将您对 HTTP POST 请求的调用转换为指向导出服务的 URL。以下示例显示如何配置此安排:
<bean id="httpInvokerProxy" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"> <property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean>
如前所述,您可以选择要使用的 HTTPClient 端。默认情况下,
HttpInvokerProxy
使用 JDK 的 HTTP 功能,但是您也可以通过设置httpInvokerRequestExecutor
属性来使用 ApacheHttpComponents
Client 端。以下示例显示了如何执行此操作:
<property name="httpInvokerRequestExecutor"> <bean class="org.springframework.remoting.httpinvoker.HttpComponentsHttpInvokerRequestExecutor"/> </property>
1.4. Web Services
Spring 提供了对标准 Java Web 服务 API 的全面支持:
使用 JAX-WS 公开 Web 服务
使用 JAX-WS 访问 Web 服务
除了在 Spring Core 中对 JAX-WS 的库存支持之外,Spring 产品组合还具有SpringWeb Service,这是一种针对 Contract 优先,文档驱动的 Web 服务的解决方案。
1.4.1. 使用 JAX-WS 公开基于 Servlet 的 Web 服务
Spring 为 JAX-WS servlet 端点实现提供了一个方便的 Base Class:
* JAX-WS compliant AccountService implementation that simply delegates * to the AccountService implementation in the root web application context. * This wrapper class is necessary because JAX-WS requires working with dedicated * endpoint classes. If an existing service needs to be exported, a wrapper that * extends SpringBeanAutowiringSupport for simple Spring bean autowiring (through * the @Autowired annotation) is the simplest JAX-WS compliant way. * This is the class registered with the server-side JAX-WS implementation. * In the case of a Java EE 5 server, this would simply be defined as a servlet * in web.xml, with the server detecting that this is a JAX-WS endpoint and reacting * accordingly. The servlet name usually needs to match the specified WS service name. * The web service engine manages the lifecycle of instances of this class. * Spring bean references will just be wired in here. import org.springframework.web.context.support.SpringBeanAutowiringSupport; @WebService(serviceName="AccountService") public class AccountServiceEndpoint extends SpringBeanAutowiringSupport { @Autowired private AccountService biz; @WebMethod public void insertAccount(Account acc) { biz.insertAccount(acc); @WebMethod public Account[] getAccounts(String name) { return biz.getAccounts(name);SpringBeanAutowiringSupport
。为了公开AccountService
,我们扩展 Spring 的SpringBeanAutowiringSupport
类并在此处实现我们的业务逻辑,通常将调用委派给业务层。我们使用 Spring 的@Autowired
注解来表达对 SpringManagement 的 bean 的依赖。以下示例显示了扩展SpringBeanAutowiringSupport
的类:我们的
AccountServiceEndpoint
需要在与 Spring 上下文相同的 Web 应用程序中运行,以允许访问 Spring 的设施。在 Java EE 5 环境中,默认情况下就是这种情况,使用用于 JAX-WS servlet 端点部署的标准协定。有关详细信息,请参见各种 Java EE 5 Web 服务教程。1.4.2. 使用 JAX-WS 导出独立的 Web 服务
Oracle JDK 随附的内置 JAX-WS 提供程序通过使用 JDK 中也包含的内置 HTTP 服务器来支持 Web 服务公开。 Spring 的
SimpleJaxWsServiceExporter
检测到 Spring 应用程序上下文中所有带有@WebService
Comments 的 bean,并将它们通过默认的 JAX-WS 服务器(JDK HTTP 服务器)导出。在这种情况下,端点实例被定义和 Management 为 Spring Bean 本身。它们已在 JAX-WS 引擎中注册,但是它们的生命周期取决于 Spring 应用程序上下文。这意味着您可以将 Spring 功能(例如显式依赖项注入)应用于端点实例。通过
@Autowired
进行 Comments 驱动的注入也有效。以下示例显示了如何定义这些 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
派生,因为此示例中的端点是完全由 SpringManagement 的 bean。这意味着端点实现可以如下所示(不声明任何超类,并且仍然采用 Spring 的@Autowired
配置 Comments):
@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 端点定义为 SpringManagement 的 bean,类似于previous section中讨论的独立模式,但这次是在 Servlet 环境中。
这在 Java EE 5 环境中不可移植。它主要用于将 JAX-WS RI 嵌入为 Web 应用程序一部分的非 EE 环境,例如 Tomcat。
与导出基于 servlet 的端点的标准样式的不同之处在于,端点实例本身的生命周期由 SpringManagement,并且在
web.xml
中仅定义了一个 JAX-WS servlet。使用标准的 Java EE 5 样式(如前所示),每个服务端点都有一个 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 服务类供我们使用。后者是完整版本,可以返回实现我们的业务服务接口的代理。在以下示例中,我们再次使用JaxWsPortProxyFactoryBean
为AccountService
端点创建代理:<bean id="accountWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean"> <property name="serviceInterface" value="example.AccountService"/> (1) <property name="wsdlDocumentUrl" value="http://localhost:8888/AccountServiceEndpoint?WSDL"/> <property name="namespaceUri" value="http://example/"/> <property name="serviceName" value="AccountService"/> <property name="portName" value="AccountServiceEndpointPort"/> </bean>
(1) 其中 serviceInterface
是 Client 使用的我们的业务界面。
wsdlDocumentUrl
是 WSDL 文件的 URL。 Spring 在启动时需要使用它来创建 JAX-WS 服务。namespaceUri
对应于.wsdl 文件中的targetNamespace
。serviceName
对应于.wsdl 文件中的服务名称。portName
对应于.wsdl 文件中的端口名称。访问 Web 服务很容易,因为我们有一个供其使用的 bean 工厂,它将它公开为名为
AccountService
的接口。以下示例说明了如何在 Spring 中进行连接:
<bean id="client" class="example.AccountClientImpl"> <property name="service" ref="accountWebService"/> </bean>
从 Client 端代码,我们可以像访问普通类一样访问 Web 服务,如以下示例所示:
public class AccountClientImpl { private AccountService service; public void setService(AccountService service) { this.service = service; public void foo() { service.insertAccount(...);
上面的内容略有简化,因为 JAX-WS 需要使用
@WebService
,@SOAPBinding
etcComments 对端点接口和实现类进行 Comments。这意味着您不能(轻松)使用纯 Java 接口和实现类作为 JAX-WS 端点工件。您需要首先对它们进行 Comments。查看 JAX-WS 文档以获取有关这些需求的详细信息。
1.5. 通过 JMS 公开服务
您还可以通过使用 JMS 作为基础通信协议来透明地公开服务。 Spring 框架中的 JMS 远程支持非常基本。它在
same thread
上和同一非事务Session
中发送和接收。结果,吞吐量取决于实现方式。请注意,这些单线程和非事务性约束仅适用于 Spring 的 JMS 远程支持。请参阅JMS(Java 消息服务)以获取有关 Spring 对基于 JMS 的消息传递的丰富支持的信息。服务器和 Client 端均使用以下接口:
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 + "]");
以下配置文件包含在 Client 机和服务器上共享的 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 http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://ep-t43:61616"/> </bean> <bean id="queue" class="org.apache.activemq.command.ActiveMQQueue"> <constructor-arg value="mmm"/> </bean> </beans>
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 http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="checkingAccountService" class="org.springframework.jms.remoting.JmsInvokerServiceExporter"> <property name="serviceInterface" value="com.foo.CheckingAccountService"/> <property name="service"> <bean class="com.foo.SimpleCheckingAccountService"/> </property> </bean> <bean class="org.springframework.jms.listener.SimpleMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory"/> <property name="destination" ref="queue"/> <property name="concurrentConsumers" value="3"/> <property name="messageListener" ref="checkingAccountService"/> </bean> </beans>
package com.foo; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Server { public static void main(String[] args) throws Exception { new ClassPathXmlApplicationContext(new String[]{"com/foo/server.xml", "com/foo/jms.xml"});
1.5.2. Client 端配置
Client 端只需要创建一个 Client 端代理即可实现约定的接口(
CheckingAccountService
)。以下示例定义了可以注入到其他 Client 端对象中的 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 http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="checkingAccountService" class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean"> <property name="serviceInterface" value="com.foo.CheckingAccountService"/> <property name="connectionFactory" ref="connectionFactory"/> <property name="queue" ref="queue"/> </bean> </beans>
package com.foo; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Client { public static void main(String[] args) throws Exception { ApplicationContext ctx = new ClassPathXmlApplicationContext( new String[] {"com/foo/client.xml", "com/foo/jms.xml"}); CheckingAccountService service = (CheckingAccountService) ctx.getBean("checkingAccountService"); service.cancelAccount(new Long(10));
1.6. AMQP
有关更多信息,请参见Spring AMQP 参考指南的“使用 AMQP 进行远程处理”部分。
远程接口未实现自动检测
对于远程接口,不会自动检测已实现接口的主要原因是为了避免为远程调用者打开太多门。目标对象可能实现内部回调接口,例如
InitializingBean
或DisposableBean
,而这些接口将不会向调用者公开。在本地情况下,提供具有目标所实现的所有接口的代理通常无关紧要。但是,当导出远程服务时,应公开特定的服务接口,并提供用于远程使用的特定操作。除了内部回调接口之外,目标还可以实现多个业务接口,其中只有一个用于远程公开。由于这些原因,我们需要指定这样的服务接口。
这是配置便利性与内部方法意外暴露风险之间的折衷方案。始终指定服务接口并不会花费太多精力,这使您可以安全地控制特定方法的使用。
1.7. 选择技术时的注意事项
这里介绍的每种技术都有其缺点。选择一种技术时,应仔细考虑您的需求,公开的服务以及通过网络发送的对象。
使用 RMI 时,除非通过隧道传送 RMI 流量,否则无法通过 HTTP 协议访问对象。 RMI 是一个重量级协议,因为它支持全对象序列化,当您使用需要通过网络进行序列化的复杂数据模型时,RMI 非常重要。但是,RMI-JRMP 绑定到 JavaClient 端。它是 Java 到 Java 的远程解决方案。
如果您需要基于 HTTP 的远程处理而且还依赖 Java 序列化,那么 Spring 的 HTTP 调用程序是一个不错的选择。它与 RMI 调用程序共享基本的基础结构,但使用 HTTP 作为传输。请注意,HTTP 调用程序不仅限于 Java 到 Java 远程处理,还不仅限于 Client 端和服务器端的 Spring。 (后者也适用于非 RMI 接口的 Spring RMI 调用程序.)
在异类环境中运行时,Hessian 可能会提供重要的价值,因为它们明确允许使用非 JavaClient 端。但是,非 Java 支持仍然有限。已知的问题包括 Hibernate 对象的序列化以及延迟初始化的集合。如果您有这样的数据模型,请考虑使用 RMI 或 HTTP 调用程序而不是 Hessian。
JMS 可用于提供服务集群,并使 JMS 代理负责负载平衡,发现和自动故障转移。默认情况下,Java 序列化用于 JMS 远程处理,但是 JMS 提供程序可以使用其他机制进行线路格式化,例如 XStream,以使服务器可以用其他技术实现。
最后但并非最不重要的一点是,EJB 具有优于 RMI 的优势,因为它支持基于标准角色的身份验证和授权以及远程事务传播。尽管核心 Spring 并没有提供 RMI 调用程序或 HTTP 调用程序来支持安全上下文传播,但也有可能。 Spring 仅提供用于插入第三方或自定义解决方案的钩子。
1.8. REST 端点
Spring 框架提供了两种选择来调用 REST 端点:
Using RestTemplate:具有同步模板方法 API 的原始 Spring RESTClient 端。
WebClient:一种非阻塞的,Reactive 的替代方案,它支持同步和异步以及流方案。
从 5.0 开始,无阻塞,响应式
WebClient
提供了RestTemplate
的现代替代方案,并有效支持同步和异步以及流方案。RestTemplate
将在将来的版本中弃用,并且以后将不会添加主要的新功能。1.8.1. 使用 RestTemplate
RestTemplate
通过 HTTPClient 端库提供了更高级别的 API。它使在一行中轻松调用 REST 端点变得容易。它公开了以下几组重载方法:表 1. RestTemplate 方法
patchForObject
通过使用 PATCH 更新资源,并从响应中返回表示形式。请注意,JDKHttpURLConnection
不支持PATCH
,但是 Apache HttpComponents 和其他支持。delete
使用 DELETE 删除指定 URI 处的资源。optionsForAllow
通过使用 ALLOW 检索资源的允许的 HTTP 方法。exchange
前述方法的通用性强(且不那么固执)版本,可在需要时提供额外的灵 Active。它接受RequestEntity
(包括 HTTP 方法,URL,Headers 和正文作为 Importing),并返回ResponseEntity
。
这些方法允许使用ParameterizedTypeReference
而不是Class
来指定具有泛型的响应类型。
|execute
|执行请求的最通用方法,完全控制通过回调接口进行的请求准备和响应提取。Initialization
默认构造函数使用
java.net.HttpURLConnection
执行请求。您可以使用ClientHttpRequestFactory
实现切换到其他 HTTP 库。内置支持以下内容:Apache HttpComponents
Netty
OkHttp
例如,要切换到 Apache HttpComponents,可以使用以下命令:
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
每个
ClientHttpRequestFactory
都公开特定于基础 HTTPClient 端库的配置选项,例如用于凭证,连接池和其他详细信息。请注意,访问表示错误的响应状态(例如 401)时,HTTP 请求的
java.net
实现可能引发异常。如果这是一个问题,请切换到另一个 HTTPClient 端库。许多
RestTemplate
方法都接受 URI 模板和 URI 模板变量,它们可以作为String
变量参数或Map<String,String>
。下面的示例使用一个
String
变量参数:
String result = restTemplate.getForObject( "http://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( "http://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
请注意,URI 模板是自动编码的,如以下示例所示:
restTemplate.getForObject("http://example.com/hotel list", String.class); // Results in request to "http://example.com/hotel%20list"
您可以使用
RestTemplate
的uriTemplateHandler
属性来自定义 URI 的 encodings。或者,您可以准备java.net.URI
并将其传递到接受URI
的RestTemplate
方法之一。
有关使用和编码 URI 的更多详细信息,请参见URI Links。
Headers
您可以使用
exchange()
方法来指定请求 Headers,如以下示例所示:String uriTemplate = "http://example.com/hotels/{hotel}"; URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42); RequestEntity<Void> requestEntity = RequestEntity.get(uri) .header(("MyRequestHeader", "MyValue") .build(); ResponseEntity<String> response = template.exchange(requestEntity, String.class); String responseHeader = response.getHeaders().getFirst("MyResponseHeader"); String body = response.getBody();
您可以通过许多返回
ResponseEntity
的RestTemplate
方法变体来获取响应 Headers。在
HttpMessageConverter
的帮助下,通过RestTemplate
方法传递和返回的对象将与原始内容进行转换。在 POST 上,Importing 对象被序列化到请求主体,如以下示例所示:
URI location = template.postForLocation("http://example.com/people", person);
您无需显式设置请求的 Content-TypeHeaders。在大多数情况下,您可以找到基于源
Object
类型的兼容消息转换器,并且所选消息转换器会相应地设置 Content Type。如有必要,可以使用exchange
方法显式提供Content-Type
请求 Headers,从而影响选择哪个消息转换器。在 GET 上,响应的主体反序列化为输出
Object
,如以下示例所示:
Person person = restTemplate.getForObject("http://example.com/people/{id}", Person.class, 42);
不需要明确设置请求的
Accept
Headers。在大多数情况下,可以根据预期的响应类型找到兼容的消息转换器,这有助于填充Accept
Headers。如有必要,可以使用exchange
方法显式提供Accept
Headers。
默认情况下,
RestTemplate
注册所有内置的message converters,具体取决于有助于确定存在哪些可选转换库的 Classpath 检查。您还可以将消息转换器设置为显式使用。Message Conversion
spring-web
模块包含HttpMessageConverter
协定,用于通过InputStream
和OutputStream
读写 HTTP 请求和响应的正文。HttpMessageConverter
实例用于 Client 端(例如RestTemplate
)和服务器端(例如 Spring MVC REST 控制器)。框架中提供了主要媒体(MIME)类型的具体实现,默认情况下,它们在 Client 端的
RestTemplate
和服务器端的RequestMethodHandlerAdapter
注册(请参见配置消息转换器)。以下各节介绍了
HttpMessageConverter
的实现。对于所有转换器,都使用默认的媒体类型,但是您可以通过设置supportedMediaTypes
bean 属性来覆盖它。下表描述了每种实现:表 2. HttpMessageConverter 实现
StringHttpMessageConverter
可以从 HTTP 请求和响应读取和写入String
实例的HttpMessageConverter
实现。默认情况下,此转换器支持所有文本媒体类型(text/*
)并以Content-Type
或text/plain
写入。FormHttpMessageConverter
可以从 HTTP 请求和响应中读取和写入表单数据的HttpMessageConverter
实现。默认情况下,此转换器读取和写入application/x-www-form-urlencoded
媒体类型。从MultiValueMap<String, String>
读取表格数据并将其写入。ByteArrayHttpMessageConverter
可以从 HTTP 请求和响应读取和写入字节数组的HttpMessageConverter
实现。默认情况下,此转换器支持所有媒体类型(*/*
)并以Content-Type
application/octet-stream
写入。您可以通过设置supportedMediaTypes
属性并覆盖getContentType(byte[])
来覆盖它。MarshallingHttpMessageConverter
可以使用org.springframework.oxm
包中的 Spring 的Marshaller
和Unmarshaller
抽象来读取和写入 XML 的HttpMessageConverter
实现。该转换器需要使用Marshaller
和Unmarshaller
才能使用。您可以通过构造函数或 bean 属性注入它们。默认情况下,此转换器支持text/xml
和application/xml
。MappingJackson2HttpMessageConverter
可以使用 Jackson 的ObjectMapper
读取和写入 JSON 的HttpMessageConverter
实现。您可以根据需要使用 Jackson 提供的 Comments 来自定义 JSONMap。当您需要进一步控制时(对于需要为特定类型提供自定义 JSON 序列化器/反序列化器的情况),可以通过ObjectMapper
属性注入自定义ObjectMapper
。默认情况下,此转换器支持application/json
。MappingJackson2XmlHttpMessageConverter
可以使用Jackson XMLextensionsXmlMapper
读写 XML 的HttpMessageConverter
实现。您可以根据需要使用 JAXB 或 Jackson 提供的 Comments 来自定义 XMLMap。当您需要进一步控制时(对于需要为特定类型提供自定义 XML 序列化器/反序列化器的情况),可以通过ObjectMapper
属性注入自定义XmlMapper
。默认情况下,此转换器支持application/xml
。SourceHttpMessageConverter
可以从 HTTP 请求和响应中读取和写入javax.xml.transform.Source
的HttpMessageConverter
实现。仅支持DOMSource
,SAXSource
和StreamSource
。默认情况下,此转换器支持text/xml
和application/xml
。BufferedImageHttpMessageConverter
可以从 HTTP 请求和响应中读取和写入java.awt.image.BufferedImage
的HttpMessageConverter
实现。该转换器读取和写入 Java I/O API 支持的媒体类型。Jackson JSON 视图
您可以指定JacksonJSON 视图来仅序列化对象属性的一个子集,如以下示例所示:
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23")); value.setSerializationView(User.WithoutPasswordView.class); RequestEntity<MappingJacksonValue> requestEntity = RequestEntity.post(new URI("http://example.com/user")).body(value); ResponseEntity<String> response = template.exchange(requestEntity, String.class);
Multipart
要发送 Multipart 数据,您需要提供一个
MultiValueMap<String, ?>
,其值要么是代表 Component 内容的Object
实例,要么是代表 Component 内容和头的HttpEntity
实例。MultipartBodyBuilder
提供了一个方便的 API 来准备 Multipart 请求,如以下示例所示:MultipartBodyBuilder builder = new MultipartBodyBuilder(); builder.part("fieldPart", "fieldValue"); builder.part("filePart", new FileSystemResource("...logo.png")); builder.part("jsonPart", new Person("Jason")); MultiValueMap<String, HttpEntity<?>> parts = builder.build();
在大多数情况下,您不必为每个部分指定
Content-Type
。Content Type 是根据要序列化的HttpMessageConverter
自动确定的,对于Resource
则根据文件 extensions 自动确定。如有必要,您可以通过重载的生成器part
方法之一显式提供MediaType
供每个 Component 使用。
MultiValueMap
准备就绪后,您可以将其传递给RestTemplate
,如以下示例所示:
MultipartBodyBuilder builder = ...; template.postForObject("http://example.com/upload", builder.build(), Void.class);
如果
MultiValueMap
包含至少一个非String
值,该值也可以表示常规表单数据(即application/x-www-form-urlencoded
),则无需将Content-Type
设置为multipart/form-data
。当您使用MultipartBodyBuilder
来确保HttpEntity
包装器时,总是如此。
1.8.2. 使用 AsyncRestTemplate(不建议使用)
AsyncRestTemplate
已弃用。对于所有您可能考虑使用AsyncRestTemplate
的用例,请改用WebClient。2.企业 JavaBeans(EJB)集成
作为轻量级容器,Spring 通常被认为是 EJB 的替代品。我们确实相信,对于许多(即使不是大多数)应用程序和用例,Spring 作为容器,结合其在事务,ORM 和 JDBC 访问领域的丰富支持功能,比通过 EJB 实现等效功能是更好的选择。容器和 EJB。
但是,请务必注意,使用 Spring 不会阻止您使用 EJB。实际上,Spring 使访问 EJB 以及在其中实现 EJB 和功能变得更加容易。另外,使用 Spring 访问 EJB 提供的服务可以使这些服务的实现稍后在本地 EJB,远程 EJB 或 POJO(普通旧 Java 对象)变体之间透明切换,而不必更改 Client 端代码。
在本章中,我们将研究 Spring 如何帮助您访问和实现 EJB。当访问 Stateless 会话 Bean(SLSB)时,Spring 提供了特殊的价值,因此我们从讨论这个主题开始。
2.1. 访问 EJB
本节介绍如何访问 EJB。
2.1.1. Concepts
要在本地或远程 Stateless 会话 Bean 上调用方法,Client 端代码通常必须执行 JNDI 查找以获取(本地或远程)EJB Home 对象,然后对该对象使用
create
方法调用以获取实际的(本地或远程)Bean。 )EJB 对象。然后在 EJB 上调用一种或多种方法。为了避免重复的低级代码,许多 EJB 应用程序都使用服务定位器和业务委托模式。这些比在整个 Client 端代码中喷射 JNDI 查找要好,但是它们的常规实现有很多缺点:
通常,使用 EJB 的代码取决于 Service Locator 或 Business Delegate 单例,使其难以测试。
在不使用业务委托的情况下使用服务定位器模式的情况下,应用程序代码仍然最终必须在 EJB home 上调用
create()
方法并处理产生的异常。因此,它仍然与 EJB API 和 EJB 编程模型的复杂性联系在一起。实现业务委托模式通常会导致大量的代码重复,我们必须编写许多在 EJB 上调用相同方法的方法。
Spring 的方法是允许创建和使用代理对象(通常在 Spring 容器内配置),这些代理对象充当无代码的业务委托。除非您在此类代码中实际添加了实际价值,否则您无需在手动编码的业务委托中编写另一个 Service Locator,另一个 JNDI 查找或重复方法。
2.1.2. 访问本地 SLSB
假设我们有一个需要使用本地 EJB 的 Web 控制器。我们遵循最佳实践,并使用 EJB 业务方法接口模式,以便 EJB 的本地接口扩展了非 EJB 特定的业务方法接口。我们将此业务方法界面称为
MyComponent
。以下示例显示了这样的接口:public interface MyComponent {
使用业务方法接口模式的主要原因之一是确保本地接口中的方法签名与 bean 实现类之间的同步是自动的。另一个原因是,如果有必要的话,以后可以使我们更轻松地切换到服务的 POJO(普通旧 Java 对象)实现。我们还需要实现本地 home 接口,并提供实现
SessionBean
和MyComponent
业务方法接口的实现类。现在,将 Web 层控制器连接到 EJB 实现所需要做的唯一 Java 编码是在控制器上公开MyComponent
类型的 setter 方法。这会将引用另存为控制器中的实例变量。以下示例显示了如何执行此操作:
private MyComponent myComponent; public void setMyComponent(MyComponent myComponent) { this.myComponent = myComponent;
随后,我们可以在控制器中的任何业务方法中使用此实例变量。现在,假设我们从 Spring 容器中获取控制器对象,我们可以(在相同上下文中)配置
LocalStatelessSessionProxyFactoryBean
实例,它是 EJB 代理对象。我们配置代理,并使用以下配置条目设置控制器的myComponent
属性:
<bean id="myComponent" class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean"> <property name="jndiName" value="ejb/myBean"/> <property name="businessInterface" value="com.mycom.MyComponent"/> </bean> <bean id="myController" class="com.mycom.myController"> <property name="myComponent" ref="myComponent"/> </bean>
尽管没有强迫您使用 AOP 概念来欣赏结果,但仍要依靠 Spring AOP 框架在幕后进行大量工作。
myComponent
bean 定义为 EJB 创建了一个代理,该代理实现了业务方法接口。 EJB 本地主目录在启动时被缓存,因此只有一个 JNDI 查找。每次调用 EJB 时,代理都会在本地 EJB 上调用classname
方法,并在 EJB 上调用相应的业务方法。
myController
bean 定义将控制器类的myComponent
属性设置为 EJB 代理。或者(最好在许多此类代理定义的情况下),考虑在 Spring 的“ jee”名称空间中使用
<jee:local-slsb>
配置元素。以下示例显示了如何执行此操作:
<jee:local-slsb id="myComponent" jndi-name="ejb/myBean" business-interface="com.mycom.MyComponent"/> <bean id="myController" class="com.mycom.myController"> <property name="myComponent" ref="myComponent"/> </bean>
这种 EJB 访问机制极大地简化了应用程序代码。 Web 层代码(或其他 EJBClient 端代码)与 EJB 的使用无关。要用 POJO 或模拟对象或其他测试存根替换该 EJB 引用,我们可以更改
myComponent
bean 定义而无需更改任何 Java 代码。此外,作为应用程序的一部分,我们不必编写任何一行 JNDI 查找或其他 EJB 管道代码。实际应用中的基准和经验表明,这种方法的性能开销(涉及目标 EJB 的反射调用)是最小的,并且在常规使用中是无法检测到的。请记住,无论如何我们都不希望对 EJB 进行细粒度的调用,因为与应用程序服务器中的 EJB 基础结构相关联的成本很高。
关于 JNDI 查找有一个警告。在 bean 容器中,此类通常最好用作单例(没有理由使其成为原型)。但是,如果该 bean 容器预先实例化了单例(与各种 XML
ApplicationContext
变体一样),则在 EJB 容器加载目标 EJB 之前加载 bean 容器时可能会出现问题。这是因为 JNDI 查找是在此类的init()
方法中执行的,然后进行了缓存,但是 EJB 尚未绑定到目标位置。解决方案是不预先实例化该工厂对象,而是让它在首次使用时创建。在 XML 容器中,您可以使用lazy-init
属性来控制它。尽管大多数 Spring 用户都不感兴趣,但是那些使用 EJB 进行编程 AOP 的用户可能希望查看
LocalSlsbInvokerInterceptor
。
2.1.3. 访问远程 SLSB
除了使用
SimpleRemoteStatelessSessionProxyFactoryBean
或<jee:remote-slsb>
配置元素外,访问远程 EJB 与访问本地 EJB 基本相同。当然,无论是否使用 Spring,远程调用语义都适用:调用另一台计算机上另一台 VM 中的对象上的方法时,有时在使用情况和故障处理方面必须区别对待。与非 Spring 方法相比,Spring 的 EJBClient 端支持增加了另一个优势。通常,在本地或远程调用 EJB 之间轻松地来回切换 EJBClient 端代码是有问题的。这是因为远程接口方法必须声明它们抛出
RemoteException
,而 Client 端代码必须对此进行处理,而本地接口方法则不需要。通常需要修改为需要移至远程 EJB 的本地 EJB 编写的 Client 端代码,以添加对远程异常的处理,为需要移至本地 EJB 的远程 EJB 编写的 Client 端代码可以保持不变,但可以执行以下操作:许多不必要的远程异常处理,或进行修改以删除该代码。使用 Spring 远程 EJB 代理,您不能在业务方法接口和实现 EJB 代码中声明任何抛出的RemoteException
,具有相同的远程接口(除了它确实抛出RemoteException
),并且依靠代理来动态地处理两个接口就像它们一样。也就是说,Client 端代码不必处理已检查的RemoteException
类。在 EJB 调用期间抛出的任何实际RemoteException
都将重新抛出为未经检查的RemoteAccessException
类,该类是RuntimeException
的子类。然后,您可以在本地 EJB 或远程 EJB(甚至纯 Java 对象)实现之间随意切换目标服务,而无需了解或关心 Client 端代码。当然,这是可选的:没有什么可以阻止您在业务界面中声明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>
查找可提供一致且更明确的 EJB 访问配置。3. JMS(Java 消息服务)
Spring 提供了一个 JMS 集成框架,该框架简化了 JMS API 的使用,就像 Spring 对 JDBC API 的集成一样。
JMS 可以大致分为两个功能区域,即消息的产生和使用。
JmsTemplate
类用于消息生成和同步消息接收。对于类似于 Java EE 的消息驱动 bean 样式的异步接收,Spring 提供了许多消息侦听器容器,可用于创建消息驱动 POJO(MDP)。 Spring 还提供了一种声明式方法来创建消息侦听器。
org.springframework.jms.core
软件包提供了使用 JMS 的核心功能。它包含 JMS 模板类,该类通过处理资源的创建和释放来简化 JMS 的使用,就像JdbcTemplate
对于 JDBC 一样。 Spring 模板类共有的设计原则是提供帮助器方法来执行常用操作,并且对于更复杂的用法,将处理任务的本质委托给用户实现的回调接口。 JMS 模板遵循相同的设计。这些类提供了各种方便的方法,用于发送消息,同步使用消息以及向用户公开 JMS 会话和消息生成器。
org.springframework.jms.support
软件包提供JMSException
翻译功能。转换将已检查的JMSException
层次结构转换为未检查的异常的镜像层次结构。如果存在选中的javax.jms.JMSException
的任何提供程序特定的子类,则将此异常包装在未选中的UncategorizedJmsException
中。
org.springframework.jms.support.converter
包提供MessageConverter
抽象以在 Java 对象和 JMS 消息之间进行转换。
org.springframework.jms.support.destination
包提供了用于 ManagementJMS 目的地的各种策略,例如为 JNDI 中存储的目的地提供服务定位器。
org.springframework.jms.annotation
软件包提供了必要的基础结构,以通过使用@JmsListener
支持 Comments 驱动的侦听器端点。
org.springframework.jms.config
软件包为jms
名称空间提供了解析器实现,并提供了 Java config 支持以配置侦听器容器和创建侦听器端点。最后,
org.springframework.jms.connection
软件包提供了适用于独立应用程序的ConnectionFactory
的实现。它还包含用于 JMS 的 SpringPlatformTransactionManager
的实现(巧妙地名为JmsTransactionManager
)。这允许将 JMS 作为事务资源无缝集成到 Spring 的事务 Management 机制中。3.1. 使用 Spring JMS
本节描述如何使用 Spring 的 JMS 组件。
3.1.1. 使用 JmsTemplate
JmsTemplate
类是 JMS 核心软件包中的中心类。由于它在发送或同步接收消息时处理资源的创建和释放,因此它简化了 JMS 的使用。使用
JmsTemplate
的代码仅需要实现回调接口,即可为其提供明确定义的高级 Contract。当给JmsTemplate
中的调用代码提供Session
时,MessageCreator
回调接口会创建一条消息。为了允许更复杂地使用 JMS API,SessionCallback
提供了 JMS 会话,而ProducerCallback
公开了Session
和MessageProducer
对。JMS API 公开了两种类型的发送方法,一种采用交付模式,优先级和生存时间作为服务质量(QOS)参数,另一种不采用 QOS 参数并使用默认值。由于
JmsTemplate
有许多发送方法,因此设置 QOS 参数已作为 bean 属性公开,以避免重复发送方法。同样,使用setReceiveTimeout
属性设置同步接收调用的超时值。某些 JMS 提供程序允许通过
ConnectionFactory
的配置来 Management 默认 QOS 值的设置。这样做的结果是,对MessageProducer
实例的send
方法(send(Destination destination, Message message)
)的调用使用与 JMS 规范中指定的 QOS 默认值不同的 QOS 默认值。为了提供对 QOS 值的一致 Management,因此,必须通过将布尔属性isExplicitQosEnabled
设置为true
来专门使JmsTemplate
使用其自己的 QOS 值。为方便起见,
JmsTemplate
还公开了一个基本的请求-答复操作,该操作允许发送消息并 await 作为该操作一部分而创建的临时队列的答复。
JmsTemplate
类的实例一旦配置便是线程安全的。这很重要,因为这意味着您可以配置JmsTemplate
的单个实例,然后将该共享引用安全地注入多个协作者中。需要明确的是,JmsTemplate
是有状态的,因为它保持对ConnectionFactory
的引用,但是此状态不是会话状态。从 Spring Framework 4.1 开始,
JmsMessagingTemplate
构建在JmsTemplate
的基础上,并提供了与消息传递抽象(即org.springframework.messaging.Message
)的集成。这使您可以创建以通用方式发送的消息。3.1.2. Connections
JmsTemplate
要求引用ConnectionFactory
。ConnectionFactory
是 JMS 规范的一部分,并且是使用 JMS 的入口点。Client 端应用程序使用它作为工厂来创建与 JMS 提供程序的连接,并封装各种配置参数,其中许多是特定于供应商的,例如 SSL 配置选项。当在 EJB 中使用 JMS 时,供应商提供 JMS 接口的实现,以便它们可以参与声明式事务 Management 并执行连接和会话的池化。为了使用此实现,Java EE 容器通常要求您在 EJB 或 Servlet 部署 Descriptors 中将 JMS 连接工厂声明为
resource-ref
。为了确保在 EJB 内的JmsTemplate
中使用这些功能,Client 端应用程序应确保引用了ConnectionFactory
的托管实现。缓存消息传递资源
标准 API 涉及创建许多中间对象。要发送消息,请执行以下“ API”遍历:
ConnectionFactory->Connection->Session->MessageProducer->send
在
ConnectionFactory
和Send
操作之间,创建并销毁了三个中间对象。为了优化资源使用并提高性能,Spring 提供了ConnectionFactory
的两种实现。
Using SingleConnectionFactory
Spring 提供了
ConnectionFactory
接口SingleConnectionFactory
的实现,该接口在所有createConnection()
调用中返回相同的Connection
,而忽略对close()
的调用。这对于测试和独立环境很有用,因此同一连接可用于可能跨越任意数量事务的多个JmsTemplate
调用。SingleConnectionFactory
引用了通常来自 JNDI 的标准ConnectionFactory
。Using CachingConnectionFactory
CachingConnectionFactory
扩展了SingleConnectionFactory
的功能,并添加了Session
,MessageProducer
和MessageConsumer
实例的缓存。初始缓存大小设置为1
。您可以使用sessionCacheSize
属性来增加缓存的会话数。请注意,由于根据会话的确认模式缓存会话,因此实际缓存的会话数大于该数量,因此,当sessionCacheSize
设置为 one 时,最多可以有四个缓存的会话实例(每个确认模式一个)。MessageProducer
和MessageConsumer
实例被缓存在它们自己的会话中,并且在缓存时还考虑了生产者和使用者的唯一属性。 MessageProducers 将根据其目的地进行缓存。基于由目标,selectors,noLocal 传递标志和持久订阅名称(如果创建持久使用者)组成的键来缓存 MessageConsumers。3.1.3. 目的地 Management
目标是
ConnectionFactory
实例,是可以在 JNDI 中存储和检索的 JMSManagement 的对象。在配置 Spring 应用程序上下文时,可以使用 JNDIJndiObjectFactoryBean
factory 类或<jee:jndi-lookup>
对对象对 JMS 目标的引用执行依赖项注入。但是,如果应用程序中有大量目标,或者 JMS 提供程序具有独特的高级目标 Management 功能,则此策略通常很麻烦。这种高级目标 Management 的示例包括动态目标的创建或对目标的分层名称空间的支持。JmsTemplate
将目标名称的解析委托给实现DestinationResolver
接口的 JMS 目标对象。DynamicDestinationResolver
是JmsTemplate
使用的默认实现,并且可以解析动态目标。还提供JndiDestinationResolver
充当 JNDI 中包含的目的地的服务定位器,并且可以选择退回到DynamicDestinationResolver
中包含的行为。通常,仅在运行时才知道 JMS 应用程序中使用的目的地,因此,在部署应用程序时无法通过 Management 方式创建。这通常是因为在交互的系统组件之间存在共享的应用程序逻辑,这些组件根据已知的命名约定在运行时创建目标。即使创建动态目标不属于 JMS 规范的一部分,但大多数供应商都提供了此功能。动态目标是使用用户定义的名称创建的,该名称将它们与临时目标区分开来,并且通常未在 JNDI 中注册。各个提供者之间用于创建动态目的地的 API 有所不同,因为与目的地关联的属性是特定于供应商的。但是,供应商有时会做出一个简单的实现选择,就是忽略 JMS 规范中的警告,并使用方法
TopicSession
createTopic(String topicName)
或QueueSession
createQueue(String queueName)
方法来创建具有默认目标属性的新目标。根据供应商的实现,DynamicDestinationResolver
然后还可以创建一个物理目标,而不是仅解决一个物理目标。布尔属性
pubSubDomain
用于在知道正在使用哪个 JMS 域的情况下配置JmsTemplate
。默认情况下,此属性的值为 false,指示将使用点对点域Queues
。此属性(由JmsTemplate
使用)通过DestinationResolver
接口的实现确定动态目标解析的行为。您还可以通过属性
defaultDestination
将JmsTemplate
配置为默认目标。默认目标是带有不引用特定目标的发送和接收操作。3.1.4. 消息侦听器容器
在 EJB 世界中,JMS 消息最常见的用途之一是驱动消息驱动的 bean(MDB)。 Spring 提供了一种解决方案,以不将用户绑定到 EJB 容器的方式创建消息驱动的 POJO(MDP)。 (有关 Spring 对 MDP 支持的详细介绍,请参见异步接收:消息驱动的 POJO。)从 Spring Framework 4.1 开始,可以用
@JmsListener
Comments 端点方法,请参见Comments 驱动的侦听器端点以获取更多详细信息。消息侦听器容器用于从 JMS 消息队列接收消息,并驱动注入到其中的
MessageListener
。侦听器容器负责消息接收的所有线程,并分派到侦听器中进行处理。消息侦听器容器是 MDP 与消息传递提供程序之间的中介,并负责注册接收消息,参与事务,资源获取和释放,异常转换等。这使您可以编写与接收消息(并可能对其进行响应)相关的(可能很复杂的)业务逻辑,并将样板 JMS 基础结构问题委托给框架。Spring 附带了两个标准的 JMS 消息侦听器容器,每个容器都有其专门的功能集。
Using SimpleMessageListenerContainer
此消息侦听器容器是两种标准样式中的简单容器。它在启动时创建固定数量的 JMS 会话和使用者,使用标准 JMS
MessageConsumer.setMessageListener()
方法注册侦听器,并将其留给 JMS 提供者执行侦听器回调。此变体不允许动态适应运行时需求或参与外部 Management 的事务。在兼容性方面,它非常接近独立 JMS 规范的精神,但通常与 Java EE 的 JMS 限制不兼容。尽管
SimpleMessageListenerContainer
不允许参与外部 Management 的事务,但它支持本机 JMS 事务。要启用此功能,可以将sessionTransacted
标志切换为true
,或者在 XML 名称空间中将acknowledge
属性设置为transacted
。然后,从您的侦听器抛出的异常会导致回滚,并重新传递消息。或者,考虑使用CLIENT_ACKNOWLEDGE
模式,该模式在出现异常的情况下也可以重新传送,但不使用事务处理的Session
实例,因此在事务协议中不包括任何其他Session
操作(例如发送响应消息)。默认的
AUTO_ACKNOWLEDGE
模式不能提供适当的可靠性保证。当侦听器执行失败时(由于提供者在侦听器调用之后会自动确认每条消息,没有异常要传播到提供者),或者在侦听器容器关闭时(您可以通过设置acceptMessagesWhileStopping
标志进行配置),消息可能会丢失。确保出于可靠性需求(例如,为了可靠的队列处理和持久的主题订阅)使用事务处理的会话。Using DefaultMessageListenerContainer
大多数情况下使用此消息侦听器容器。与
SimpleMessageListenerContainer
相比,此容器变体允许动态适应运行时需求,并且能够参与外部 Management 的事务。配置为JtaTransactionManager
时,每个接收到的消息都将注册到 XA 事务中。结果,处理可以利用 XA 事务语义。该侦听器容器在对 JMS 提供程序的低要求,高级功能(例如参与外部 Management 的事务)以及与 Java EE 环境的兼容性之间取得了良好的平衡。您可以自定义容器的缓存级别。请注意,当未启用缓存时,将为每个消息接收创建一个新的连接和一个新的会话。将此内容与具有高负载的非持久订阅结合使用可能会导致消息丢失。在这种情况下,请确保使用适当的缓存级别。
当代理关闭时,此容器还具有可恢复的功能。默认情况下,简单的
BackOff
实现每五秒钟重试一次。您可以为更细粒度的恢复选项指定自定义BackOff
实现。有关示例,请参见 api-spring-framework/util/backoff/ExponentialBackOff.html [+516+
]。与其同级(SimpleMessageListenerContainer)一样,
DefaultMessageListenerContainer
支持本机 JMS 事务,并允许自定义确认模式。如果对您的方案可行,则强烈建议在外部 Management 的事务上使用此方法,也就是说,如果 JVM 死亡,您可以偶尔接收重复消息。业务逻辑中的自定义重复消息检测步骤可以解决这种情况,例如以业务实体存在检查或协议表检查的形式。任何这样的安排都比其他安排更为有效:用 XA 事务包装整个处理过程(通过将DefaultMessageListenerContainer
配置为JtaTransactionManager
)来覆盖 JMS 消息的接收以及消息侦听器中业务逻辑的执行(包括数据库操作等)。默认的
AUTO_ACKNOWLEDGE
模式不能提供适当的可靠性保证。当侦听器执行失败时(由于提供者在侦听器调用之后会自动确认每条消息,没有异常要传播到提供者),或者在侦听器容器关闭时(您可以通过设置acceptMessagesWhileStopping
标志进行配置),消息可能会丢失。确保出于可靠性需求(例如,为了可靠的队列处理和持久的主题订阅)使用事务处理的会话。3.1.5. TransactionManagement
Spring 提供了一个
JmsTransactionManager
,用于 Management 单个 JMSConnectionFactory
的事务。如数据访问一章的事务 Management 部分所述,这使 JMS 应用程序可以利用 Spring 的托管事务功能。JmsTransactionManager
执行本地资源事务,将来自指定ConnectionFactory
的 JMS 连接/会话对绑定到线程。JmsTemplate
自动检测此类 Transaction 资源并相应地对其进行操作。在 Java EE 环境中,
ConnectionFactory
汇集了 Connection 和 Session 实例,因此可以有效地在事务之间重用这些资源。在独立环境中,使用 Spring 的SingleConnectionFactory
会导致共享 JMSConnection
,每个事务都有自己的独立Session
。或者,考虑使用提供程序专用的池适配器,例如 ActiveMQ 的PooledConnectionFactory
类。您还可以将
JmsTemplate
与JtaTransactionManager
和具有 XA 功能的 JMSConnectionFactory
结合使用来执行分布式事务。请注意,这需要使用 JTA 事务 Management 器以及正确的 XA 配置的 ConnectionFactory。 (检查您的 Java EE 服务器或 JMS 提供程序的文档.)使用 JMS API 从
Connection
创建Session
时,在托管和非托管事务环境中重用代码可能会造成混淆。这是因为 JMS API 只有一个工厂方法来创建Session
,并且它需要事务和确认模式的值。在托管环境中,设置这些值是环境的事务基础结构的责任,因此,供应商对 JMS Connection 的包装将忽略这些值。在非托管环境中使用JmsTemplate
时,可以通过使用属性sessionTransacted
和sessionAcknowledgeMode
来指定这些值。当您将PlatformTransactionManager
与JmsTemplate
一起使用时,始终为模板提供事务 JMSSession
。3.2. 发送信息
JmsTemplate
包含许多发送消息的便捷方法。发送方法使用javax.jms.Destination
对象指定目标,其他方法通过在 JNDI 查找中使用String
指定目标。不使用目标参数的send
方法使用默认目标。以下示例使用
MessageCreator
回调从提供的Session
对象创建文本消息:import javax.jms.ConnectionFactory; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.Queue; import javax.jms.Session; import org.springframework.jms.core.MessageCreator; import org.springframework.jms.core.JmsTemplate; public class JmsQueueSender { private JmsTemplate jmsTemplate; private Queue queue; public void setConnectionFactory(ConnectionFactory cf) { this.jmsTemplate = new JmsTemplate(cf); public void setQueue(Queue queue) { this.queue = queue; public void simpleSend() { this.jmsTemplate.send(this.queue, new MessageCreator() { public Message createMessage(Session session) throws JMSException { return session.createTextMessage("hello queue world");
在前面的示例中,
JmsTemplate
是通过将引用传递给ConnectionFactory
来构造的。或者,提供零参数构造函数和connectionFactory
,它们可用于以 JavaBean 样式(使用BeanFactory
或纯 Java 代码)构造实例。或者,考虑从 Spring 的JmsGatewaySupport
便捷 Base Class 派生,该 Base Class 为 JMS 配置提供了预构建的 bean 属性。
send(String destinationName, MessageCreator creator)
方法使您可以使用目标的字符串名称发送消息。如果这些名称已在 JNDI 中注册,则应将模板的destinationResolver
属性设置为JndiDestinationResolver
的实例。如果您创建了
JmsTemplate
并指定了默认目的地,则send(MessageCreator c)
会向该目的地发送一条消息。
3.2.1. 使用消息转换器
为了方便域模型对象的发送,
JmsTemplate
具有各种发送方法,这些方法将 Java 对象作为消息数据内容的参数。JmsTemplate
中的重载方法convertAndSend()
和receiveAndConvert()
方法将转换过程委托给MessageConverter
接口的实例。该接口定义了一个简单的协定,可以在 Java 对象和 JMS 消息之间进行转换。默认实现(SimpleMessageConverter
)支持String
和TextMessage
,byte[]
和BytesMesssage
以及java.util.Map
和MapMessage
之间的转换。通过使用转换器,您和您的应用程序代码可以专注于通过 JMS 发送或接收的业务对象,而不必担心如何将其表示为 JMS 消息。沙箱当前包含一个
MapMessageConverter
,它使用反射在 JavaBean 和MapMessage
之间进行转换。您可能自己实现的其他流行实现选择是使用现有 XML 编组程序包(例如 JAXB,Castor 或 XStream)创建代表对象的TextMessage
的转换器。为了适应消息属性,Headers 和正文的设置,这些属性通常不能封装在转换器类中,因此
MessagePostProcessor
接口使您可以在转换消息之后但在发送消息之前对其进行访问。以下示例显示了将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.2.2. 使用 SessionCallback 和 ProducerCallback
尽管发送操作涵盖了许多常见的使用场景,但是您有时可能希望对 JMS
Session
或MessageProducer
执行多个操作。SessionCallback
和ProducerCallback
分别暴露 JMSSession
和Session
/MessageProducer
对。JmsTemplate
上的execute()
方法执行这些回调方法。3.3. 接收讯息
这描述了如何在 Spring 中使用 JMS 接收消息。
3.3.1. 同步接收
虽然 JMS 通常与异步处理相关联,但是您可以同步使用消息。重载的
receive(..)
方法提供了此功能。在同步接收期间,调用线程将阻塞,直到消息可用为止。这可能是危险的操作,因为调用线程可能会无限期地被阻塞。receiveTimeout
属性指定接收者在放弃 await 消息之前应该 await 多长时间。3.3.2. 异步接收:消息驱动的 POJO
Spring 还通过使用
@JmsListener
Comments 支持带 Comments 的侦听器端点,并提供了开放的基础结构以编程方式注册端点。到目前为止,这是设置异步接收器的最便捷方法。有关更多详细信息,请参见启用侦听器端点 Comments。消息驱动 POJO(MDP)以类似于 EJB 世界中的消息驱动 Bean(MDB)的方式充当 JMS 消息的接收者。 MDP 的一个限制(但请参见Using MessageListenerAdapter)是它必须实现
javax.jms.MessageListener
接口。请注意,如果您的 POJO 在多个线程上接收消息,则重要的是要确保您的实现是线程安全的。以下示例显示了 MDP 的简单实现:
import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; import javax.jms.TextMessage; public class ExampleListener implements MessageListener { public void onMessage(Message message) { if (message instanceof TextMessage) { try { System.out.println(((TextMessage) message).getText()); catch (JMSException ex) { throw new RuntimeException(ex); else { throw new IllegalArgumentException("Message must be of type TextMessage");
实现
MessageListener
之后,就可以创建消息侦听器容器了。以下示例显示如何定义和配置 Spring 附带的消息侦听器容器之一(在本例中为
DefaultMessageListenerContainer
):<!-- this is the Message Driven POJO (MDP) --> <bean id="messageListener" class="jmsexample.ExampleListener"/> <!-- and this is the message listener container --> <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory"/> <property name="destination" ref="destination"/> <property name="messageListener" ref="messageListener"/> </bean>
有关每个实现所支持功能的完整说明,请参见各种消息侦听器容器(所有这些都实现MessageListenerContainer)的 Spring javadoc。
3.3.3. 使用 SessionAwareMessageListener 接口
SessionAwareMessageListener
接口是特定于 Spring 的接口,它提供与 JMSMessageListener
接口相似的协定,但还使消息处理方法可以访问从中接收Message
的 JMSSession
。以下 Lists 显示了SessionAwareMessageListener
接口的定义:package org.springframework.jms.listener; public interface SessionAwareMessageListener { void onMessage(Message message, Session session) throws JMSException;
如果希望 MDP 能够响应任何接收到的消息(使用
onMessage(Message, Session)
方法中提供的Session
),则可以选择让 MDP 实现此接口(优先于标准 JMSMessageListener
接口)。 Spring 附带的所有消息侦听器容器实现都支持实现MessageListener
或SessionAwareMessageListener
接口的 MDP。实现SessionAwareMessageListener
的类带有警告,然后通过接口将它们绑定到 Spring。是否使用它的选择完全由您作为应用程序开发人员或架构师来决定。请注意,
SessionAwareMessageListener
接口的onMessage(..)
方法抛出JMSException
。与标准 JMSMessageListener
接口相反,在使用SessionAwareMessageListener
接口时,Client 端代码负责处理所有引发的异常。
3.3.4. 使用 MessageListenerAdapter
MessageListenerAdapter
类是 Spring 异步消息传递支持中的最后一个组件。简而言之,它使您几乎可以将任何类公开为 MDP(尽管存在一些约束)。考虑以下接口定义:
public interface MessageDelegate { void handleMessage(String message); void handleMessage(Map message); void handleMessage(byte[] message); void handleMessage(Serializable message);
请注意,尽管该接口既未扩展
MessageListener
也未扩展SessionAwareMessageListener
接口,但仍可以通过使用MessageListenerAdapter
类将其用作 MDP。还要注意如何根据各种Message
类型的内容来强类型化各种消息处理方法,它们可以接收和处理。现在考虑
MessageDelegate
接口的以下实现:
public class DefaultMessageDelegate implements MessageDelegate { // implementation elided for clarity...
特别要注意的是,
MessageDelegate
接口(DefaultMessageDelegate
类)的先前实现完全没有 JMS 依赖性。这确实是一个 POJO,我们可以通过以下配置将其变成 MDP:
<!-- this is the Message Driven POJO (MDP) --> <bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter"> <constructor-arg> <bean class="jmsexample.DefaultMessageDelegate"/> </constructor-arg> </bean> <!-- and this is the message listener container... --> <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory"/> <property name="destination" ref="destination"/> <property name="messageListener" ref="messageListener"/> </bean>
下一个示例显示另一个 MDP,它只能处理接收 JMS
TextMessage
消息。请注意,实际上是如何将消息处理方法称为receive
(MessageListenerAdapter
中的消息处理方法的名称默认为handleMessage
),但是它是可配置的(如本节后面所述)。还请注意receive(..)
方法是如何强类型 Importing 的,以便仅接收和响应 JMSTextMessage
消息。以下 Lists 显示了TextMessageDelegate
接口的定义:
public interface TextMessageDelegate { void receive(TextMessage message);
下面的 Lists 显示了实现
TextMessageDelegate
接口的类:
public class DefaultTextMessageDelegate implements TextMessageDelegate { // implementation elided for clarity...
话务员
MessageListenerAdapter
的配置如下:
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter"> <constructor-arg> <bean class="jmsexample.DefaultTextMessageDelegate"/> </constructor-arg> <property name="defaultListenerMethod" value="receive"/> <!-- we don't want automatic message context extraction --> <property name="messageConverter"> <null/> </property> </bean>
请注意,如果
messageListener
收到的类型不是TextMessage
的 JMSMessage
,则会抛出IllegalStateException
(随后将其吞咽)。MessageListenerAdapter
类的另一个功能是,如果处理程序方法返回非无效值,则自动发送回响应Message
的功能。考虑以下接口和类:
public interface ResponsiveTextMessageDelegate { // notice the return type... String receive(TextMessage message);
public class DefaultResponsiveTextMessageDelegate implements ResponsiveTextMessageDelegate { // implementation elided for clarity...
如果将
DefaultResponsiveTextMessageDelegate
与MessageListenerAdapter
结合使用,则从'receive(..)'
方法的执行返回的任何非 null 值都将(在默认配置中)转换为TextMessage
。然后将结果TextMessage
发送到原始Message
的 JMSReply-To
属性或MessageListenerAdapter
上设置的默认Destination
(如果已配置)的 JMSReply-To
属性中定义的Destination
(如果存在)。如果未找到Destination
,则会引发InvalidDestinationException
(请注意,该异常不会被吞没,并且会在调用堆栈中传播)。3.3.5. 处理事务中的消息
在事务中调用消息侦听器仅需要重新配置侦听器容器。
您可以通过侦听器容器定义上的
sessionTransacted
标志激活本地资源事务。然后,每个消息侦听器调用都在活动的 JMS 事务中运行,并且在侦听器执行失败的情况下回退消息接收。 (通过SessionAwareMessageListener
)发送响应消息是同一本地事务的一部分,但是任何其他资源操作(例如数据库访问)都是独立运行的。这通常需要在侦听器实现中进行重复消息检测,以解决数据库处理已提交但消息处理未能提交的情况。考虑以下 bean 定义:
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory"/> <property name="destination" ref="destination"/> <property name="messageListener" ref="messageListener"/> <property name="sessionTransacted" value="true"/> </bean>
要参与外部 Management 的事务,您需要配置一个事务 Management 器并使用支持外部 Management 的事务(通常为
DefaultMessageListenerContainer
)的侦听器容器。要为 XA 事务参与配置消息侦听器容器,您需要配置
JtaTransactionManager
(默认情况下,它委派给 Java EE 服务器的事务子系统)。请注意,底层的 JMSConnectionFactory
必须具有 XA 功能,并已向您的 JTA 事务协调器正确注册。 (检查 Java EE 服务器的 JNDI 资源配置.)这使消息接收和(例如)数据库访问成为同一事务的一部分(具有统一的提交语义,但以 XA 事务日志开销为代价)。以下 bean 定义创建一个事务 Management 器:
<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) 我们的 TransactionManager。
3.4. 支持 JCA 消息端点
从 2.5 版开始,Spring 还提供了对基于 JCA 的
MessageListener
容器的支持。JmsMessageEndpointManager
尝试根据提供者的ResourceAdapter
类名自动确定ActivationSpec
类名。因此,通常可以提供 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>
或者,您可以使用给定的
ActivationSpec
对象设置JmsMessageEndpointManager
。ActivationSpec
对象也可以来自 JNDI 查找(使用<jee:jndi-lookup>
)。以下示例显示了如何执行此操作:
<bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager"> <property name="resourceAdapter" ref="resourceAdapter"/> <property name="activationSpec"> <bean class="org.apache.activemq.ra.ActiveMQActivationSpec"> <property name="destination" value="myQueue"/> <property name="destinationType" value="javax.jms.Queue"/> </bean> </property> <property name="messageListener" ref="myMessageListener"/> </bean>
使用 Spring 的
ResourceAdapterFactoryBean
,您可以在本地配置目标ResourceAdapter
,如以下示例所示:
<bean id="resourceAdapter" class="org.springframework.jca.support.ResourceAdapterFactoryBean"> <property name="resourceAdapter"> <bean class="org.apache.activemq.ra.ActiveMQResourceAdapter"> <property name="serverUrl" value="tcp://localhost:61616"/> </bean> </property> <property name="workManager"> <bean class="org.springframework.jca.work.SimpleTaskWorkManager"/> </property> </bean>
指定的
WorkManager
也可以指向特定于环境的线程池-通常通过SimpleTaskWorkManager
实例的asyncTaskExecutor
属性来指向。如果您碰巧使用多个适配器,请考虑为所有ResourceAdapter
实例定义一个共享线程池。在某些环境(例如 WebLogic 9 或更高版本)中,您可以(通过使用
<jee:jndi-lookup>
)从 JNDI 获取整个ResourceAdapter
对象。然后,基于 Spring 的消息侦听器可以与服务器托管的ResourceAdapter
进行交互,该服务器也使用服务器的内置WorkManager
。
有关更多详细信息,请参见JmsMessageEndpointManager,JmsActivationSpecConfig和ResourceAdapterFactoryBean的 javadoc。
Spring 还提供了与 JMS 无关的通用 JCA 消息端点 Management 器:
org.springframework.jca.endpoint.GenericMessageEndpointManager
。该组件允许使用任何消息侦听器类型(例如 CCIMessageListener
)和任何特定于提供程序的ActivationSpec
对象。请参阅 JCA 提供程序的文档以了解连接器的实际功能,并请参阅GenericMessageEndpointManager javadoc 以获取特定于 Spring 的配置详细信息。基于 JCA 的消息端点 Management 与 EJB 2.1 消息驱动 Bean 非常相似。它使用相同的基础资源提供者 Contract。与 EJB 2.1 MDB 一样,您也可以在 Spring 上下文中使用 JCA 提供程序支持的任何消息侦听器接口。尽管如此,Spring 仍为 JMS 提供了明确的“便利”支持,因为 JMS 是 JCA 端点 Management 协定中最常用的端点 API。
3.5. Comments 驱动的侦听器端点
异步接收消息的最简单方法是使用带 Comments 的侦听器端点基础结构。简而言之,它使您可以将托管 Bean 的方法公开为 JMS 侦听器端点。以下示例显示了如何使用它:
@Component public class MyService { @JmsListener(destination = "myDestination") public void processOrder(String data) { ... }
前面示例的想法是,只要
javax.jms.Destination
myDestination
上有消息可用,就相应地调用processOrder
方法(在这种情况下,使用 JMS 消息的内容,类似于MessageListenerAdapter提供的内容)。带 Comments 的终结点基础结构通过使用
JmsListenerContainerFactory
在幕后为每种带 Comments 的方法创建一个消息侦听器容器。此类容器未针对应用程序上下文进行注册,但可以通过使用JmsListenerEndpointRegistry
bean 进行轻松定位以进行 Management。
@JmsListener
是 Java 8 上的可重复 Comments,因此您可以通过向其添加其他@JmsListener
声明来将多个 JMS 目标与同一方法相关联。3.5.1. 启用侦听器端点 Comments
要启用对
@JmsListener
Comments 的支持,可以将@EnableJms
添加到@Configuration
类之一,如以下示例所示:@Configuration @EnableJms public class AppConfig { @Bean public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); factory.setConnectionFactory(connectionFactory()); factory.setDestinationResolver(destinationResolver()); factory.setSessionTransacted(true); factory.setConcurrency("3-10"); return factory;
默认情况下,基础结构将查找名为
jmsListenerContainerFactory
的 bean 作为工厂用来创建消息侦听器容器的源。在这种情况下(并忽略了 JMS 基础结构设置),您可以使用三个线程的核心轮询大小和十个线程的最大池大小来调用processOrder
方法。
您可以自定义用于每个 Comments 的侦听器容器工厂,也可以通过实现
JmsListenerConfigurer
接口来配置显式默认值。仅当至少一个端点在没有特定容器工厂的情况下注册时,才需要使用默认值。有关详细信息和示例,请参见实现JmsListenerConfigurer的类的 javadoc。如果您更喜欢XML configuration,则可以使用
<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
Comments 检测到的端点外,该基础结构还允许您以编程方式配置端点。以下示例显示了如何执行此操作:@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. 带 Comments 的端点方法签名
到目前为止,我们已经在端点中注入了一个简单的
String
,但实际上它可以具有非常灵活的方法签名。在以下示例中,我们将其重写为使用自定义 Headers 注入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 消息。请注意,此消息同时包含自定义 Headers 和标准 Headers(由JmsHeaders
定义)。
@Header
-带 Comments 的方法参数,用于提取特定的 Headers 值,包括标准的 JMSHeaders。一个带有
@Headers
Comments 的参数,还必须可以将其分配给java.util.Map
才能访问所有 Headers。不是受支持的类型(
Message
或Session
)之一的非 Comments 元素被视为有效负载。您可以通过用@Payload
Comments 参数来使其明确。您还可以通过添加额外的@Valid
来启用验证。注入 Spring 的
Message
抽象的能力特别有用,它可以受益于存储在特定于传输的消息中的所有信息,而无需依赖于特定于传输的 API。以下示例显示了如何执行此操作:
@JmsListener(destination = "myDestination") public void processOrder(Message<Order> order) { ... }
DefaultMessageHandlerMethodFactory
提供了方法参数的处理,您可以进一步对其进行自定义以支持其他方法参数。您也可以在那里自定义转换和验证支持。例如,如果我们想在处理
Order
之前确保其有效,则可以使用@Valid
Comments 有效负载并配置必要的验证器,如以下示例所示:
@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. 反应 Management
MessageListenerAdapter中的现有支持已使您的方法具有非
void
返回类型。在这种情况下,调用的结果将封装在javax.jms.Message
中,该javax.jms.Message
要么在原始消息的JMSReplyTo
头中指定的目标中发送,要么在侦听器上配置的默认目标中发送。现在,您可以使用消息传递抽象的@SendTo
Comments 设置该默认目标。假设我们的
processOrder
方法现在应该返回OrderStatus
,我们可以将其编写为自动发送响应,如以下示例所示:@JmsListener(destination = "myDestination") @SendTo("status") public OrderStatus processOrder(Order order) { // order processing return status;
如果您有几种带有
@JmsListener
Comments 的方法,则还可以将@SendTo
Comments 放置在类级别以共享默认的答复目标。如果需要以与传输无关的方式设置其他 Headers,则可以使用类似于以下方法来返回
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 命名空间元素,您需要引用 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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms.xsd"> <!-- bean definitions here --> </beans>
(1) 引用 JMS 模式。
命名空间由三个顶级元素组成:
<annotation-driven/>
,<listener-container/>
和<jca-listener-container/>
。<annotation-driven/>
启用Comments 驱动的侦听器端点的使用。<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 定义,如Using MessageListenerAdapter所示。除了前面示例中显示的属性之外,listener
元素还可以包含多个可选属性。下表描述了所有可用属性:表 3. JMS<listener>元素的属性
method
要调用的处理程序方法的名称。如果ref
属性指向MessageListener
或 SpringSessionAwareMessageListener
,则可以省略此属性。response-destination
向其发送响应消息的默认响应目标的名称。如果请求消息中不包含JMSReplyTo
字段,则应用此方法。此目标的类型由侦听器容器的response-destination-type
属性确定。请注意,这仅适用于具有返回值的侦听器方法,为此,每个结果对象都将转换为响应消息。subscription
持久订阅的名称(如果有)。selector
此侦听器的可选消息 selectors。concurrency
要启动此侦听器的并发会话或使用者的数量。该值可以是表示最大数的简单数字(例如5
),也可以是指示下限和上限的范围(例如3-5
)。请注意,指定的最小值仅是一个提示,在运行时可能会被忽略。默认值为容器提供的值。
<listener-container/>
元素还接受几个可选属性。这允许自定义各种策略(例如taskExecutor
和destinationResolver
)以及基本的 JMS 设置和资源引用。通过使用这些属性,您可以定义高度自定义的侦听器容器,同时仍然受益于命名空间的便利性。您可以通过指定要通过
factory-id
属性公开的 Bean 的id
来自动将此类设置公开为JmsListenerContainerFactory
,如以下示例所示:<jms:listener-container connection-factory="myConnectionFactory" task-executor="myTaskExecutor" destination-resolver="myDestinationResolver" transaction-manager="myTransactionManager" concurrency="10"> <jms:listener destination="queue.orders" ref="orderService" method="placeOrder"/> <jms:listener destination="queue.confirmations" ref="confirmationLogger" method="log"/> </jms:listener-container>
下表描述了所有可用属性。有关各个属性的更多详细信息,请参见AbstractMessageListenerContainer的类级 javadoc 及其具体子类。 Javadoc 还讨论了事务选择和消息重新交付方案。
表 4. JMS<listener-container>元素的属性
container-type
此侦听器容器的类型。可用选项为default
,simple
,default102
或simple102
(默认选项为default
)。container-class
自定义侦听器容器实现类,作为完全限定的类名。根据container-type
属性,默认值为 Spring 的标准DefaultMessageListenerContainer
或SimpleMessageListenerContainer
。factory-id
用指定的id
公开此元素定义为JmsListenerContainerFactory
的设置,以便可以将其与其他端点重用。connection-factory
对 JMSConnectionFactory
bean 的引用(默认 bean 名称为connectionFactory
)。task-executor
对 JMS 侦听器调用程序的 SpringTaskExecutor
的引用。destination-resolver
对解决 JMSDestination
实例的DestinationResolver
策略的引用。message-converter
对将 JMS 消息转换为侦听器方法参数的MessageConverter
策略的引用。默认值为SimpleMessageConverter
。error-handler
对ErrorHandler
策略的引用,该策略用于处理MessageListener
执行期间可能发生的任何未捕获的异常。destination-type
此侦听器的 JMS 目标类型:queue
,topic
,durableTopic
,sharedTopic
或sharedDurableTopic
。这可能会启用容器的pubSubDomain
,subscriptionDurable
和subscriptionShared
属性。默认值为queue
(将禁用这三个属性)。response-destination-type
响应的 JMS 目标类型:queue
或topic
。默认值为destination-type
属性的值。client-id
此侦听器容器的 JMSClient 端 ID。使用持久订阅时必须指定它。cache
JMS 资源的缓存级别:none
,connection
,session
,consumer
或auto
。默认情况下(auto
),缓存级别实际上是consumer
,除非已指定外部事务 Management 器。在这种情况下,有效的默认值为none
(假设 Java EE 风格的事务 Management,其中给定的 ConnectionFactory 是 XA 感知的池)。acknowledge
本机 JMS 确认模式:auto
,client
,dups-ok
或transacted
。值transacted
激活本地处理的Session
。或者,您可以指定transaction-manager
属性,如表中稍后所述。默认值为auto
。transaction-manager
对外部PlatformTransactionManager
的引用(通常是基于 XA 的事务协调器,例如 Spring 的JtaTransactionManager
)。如果未指定,则使用本机确认(请参见acknowledge
属性)。concurrency
每个侦听器启动的并发会话或使用者的数量。它可以是表示最大数的简单数字(例如5
),也可以是指示下限和上限的范围(例如3-5
)。请注意,指定的最小值只是一个提示,在运行时可能会被忽略。默认值为1
。如果是主题侦听器或队列 Sequences 很重要,则应将并发限制为1
。考虑将其提高到一般队列。prefetch
加载到单个会话中的最大消息数。请注意,增加此数字可能会导致并发 Consumer 饥饿。receive-timeout
用于接听电话的超时时间(以毫秒为单位)。默认值为1000
(一秒)。-1
表示没有超时。back-off
指定用于计算两次恢复尝试间隔的BackOff
实例。如果BackOffExecution
实现返回BackOffExecution#STOP
,则侦听器容器不会进一步尝试恢复。设置此属性时,将忽略recovery-interval
值。默认值为FixedBackOff
,间隔为 5000 毫秒(即五秒)。recovery-interval
指定两次恢复尝试之间的时间间隔(以毫秒为单位)。它提供了一种方便的方法来创建具有指定间隔的FixedBackOff
。有关更多恢复选项,请考虑改为指定BackOff
实例。缺省值为 5000 毫秒(即 5 秒)。phase
此容器应在其中启动和停止的生命周期阶段。值越低,此容器启动的越早,而容器停止的越晚。默认值为Integer.MAX_VALUE
,这意味着容器将尽可能晚地启动,并尽快停止。配置具有
jms
模式支持的基于 JCA 的侦听器容器非常相似,如以下示例所示:<jms:jca-listener-container resource-adapter="myResourceAdapter" destination-resolver="myDestinationResolver" transaction-manager="myTransactionManager" concurrency="10"> <jms:listener destination="queue.orders" ref="myMessageListener"/> </jms:jca-listener-container>
下表描述了 JCA 变体的可用配置选项:
表 5. JMS<jca-listener-container/>元素的属性
activation-spec-factory
对JmsActivationSpecFactory
的引用。默认设置是自动检测 JMS 提供程序及其ActivationSpec
类(请参见DefaultJmsActivationSpecFactory)。destination-resolver
对解决 JMSDestinations
的DestinationResolver
策略的引用。message-converter
对将 JMS 消息转换为侦听器方法参数的MessageConverter
策略的引用。默认值为SimpleMessageConverter
。destination-type
此侦听器的 JMS 目标类型:queue
,topic
,durableTopic
,sharedTopic
。或sharedDurableTopic
。这可能会启用容器的pubSubDomain
,subscriptionDurable
和subscriptionShared
属性。默认值为queue
(将禁用这三个属性)。response-destination-type
响应的 JMS 目标类型:queue
或topic
。默认值为destination-type
属性的值。client-id
此侦听器容器的 JMSClient 端 ID。使用持久订阅时需要指定它。acknowledge
本机 JMS 确认模式:auto
,client
,dups-ok
或transacted
。值transacted
激活本地处理的Session
。或者,您可以指定后面描述的transaction-manager
属性。默认值为auto
。transaction-manager
对 SpringJtaTransactionManager
或javax.transaction.TransactionManager
的引用,用于为每个传入消息启动 XA 事务。如果未指定,则使用本机确认(请参见acknowledge
属性)。concurrency
每个侦听器启动的并发会话或使用者的数量。它可以是表示最大数的简单数字(例如5
),也可以是指示下限和上限的范围(例如3-5
)。请注意,指定的最小值只是一个提示,通常在运行时使用 JCA 侦听器容器时将被忽略。预设值为 1.prefetch
加载到单个会话中的最大消息数。请注意,增加此数字可能会导致并发 Consumer 饥饿。Spring 中的 JMX(JavaManagement 扩展)支持提供的功能使您可以轻松,透明地将 Spring 应用程序集成到 JMX 基础结构中。
本章不是 JMX 的介绍。它没有试图解释为什么您可能要使用 JMX。如果您不熟悉 JMX,请参阅本章末尾的Further Resources。
具体来说,Spring 的 JMX 支持提供了四个核心功能:
将任何 Spring bean 自动注册为 JMX MBean。
一种用于控制 beanManagement 界面的灵活机制。
通过远程 JSR-160 连接器以声明方式公开 MBean。
本地和远程 MBean 资源的简单代理。
这些功能旨在在不将应用程序组件耦合到 Spring 或 JMX 接口和类的情况下起作用。实际上,在大多数情况下,您的应用程序类无需了解 Spring 或 JMX 即可利用 Spring JMX 功能。
4.1. 将您的 Bean 导出到 JMX
Spring 的 JMX 框架的核心类是
MBeanExporter
。此类负责获取您的 Spring bean 并向 JMXMBeanServer
注册它们。例如,考虑以下类: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"> <entry key="bean:name=testBean1" value-ref="testBean"/> </property> </bean> <bean id="testBean" class="org.springframework.jmx.JmxTestBean"> <property name="name" value="TEST"/> <property name="age" value="100"/> </bean> </beans>
前面的配置片段中相关的 bean 定义是
exporter
bean。beans
属性准确地告诉MBeanExporter
您必须将哪个 bean 导出到 JMXMBeanServer
。在默认配置中,beans
Map
中每个条目的键用作相应条目值所引用的 Bean 的ObjectName
。您可以按照控制您的 Bean 的 ObjectName 实例中所述更改此行为。使用此配置,
testBean
bean 在ObjectName
bean:name=testBean1
下作为 MBean 公开。默认情况下,bean 的所有public
属性都作为属性公开,所有public
方法(从Object
类继承的方法除外)都作为操作公开。
MBeanExporter
是Lifecycle
bean(请参阅启动和关机回调)。默认情况下,MBean 在应用程序生命周期中尽可能晚地导出。您可以通过设置autoStartup
标志来配置导出发生的phase
或禁用自动注册。4.1.1. 创建一个 MBeanServer
preceding section中显示的配置假定该应用程序正在一个(并且只有一个)
MBeanServer
已运行的环境中运行。在这种情况下,Spring 尝试找到正在运行的MBeanServer
并将您的 bean 注册到该服务器(如果有)。当您的应用程序在具有自己的MBeanServer
的容器(例如 Tomcat 或 IBM WebSphere)中运行时,此行为很有用。但是,此方法在独立环境中或在不提供
MBeanServer
的容器中运行时无用。为了解决这个问题,您可以通过在配置中添加org.springframework.jmx.support.MBeanServerFactoryBean
类的实例来声明性地创建MBeanServer
实例。您还可以通过将MBeanExporter
实例的server
属性的值设置为MBeanServerFactoryBean
返回的MBeanServer
值来确保使用特定的MBeanServer
,如以下示例所示:<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"> <entry key="bean:name=testBean1" value-ref="testBean"/> </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
创建,并通过server
属性提供给MBeanExporter
。当您提供自己的MBeanServer
实例时,MBeanExporter
不会尝试查找正在运行的MBeanServer
并使用提供的MBeanServer
实例。为了使其正常工作,您必须在 Classpath 上具有 JMX 实现。4.1.2. 重用现有的 MBeanServer
如果未指定服务器,则
MBeanExporter
尝试自动检测正在运行的MBeanServer
。在大多数仅使用一个MBeanServer
实例的环境中,这是可行的。但是,当存在多个实例时,导出器可能选择了错误的服务器。在这种情况下,应使用MBeanServer
agentId
指示要使用的实例,如以下示例所示:<beans> <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean"> <!-- indicate to first look for a server --> <property name="locateExistingServerIfPossible" value="true"/> <!-- search for the MBeanServer instance with the given agentId --> <property name="agentId" value="MBeanServer_instance_agentId>"/> </bean> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="server" ref="mbeanServer"/> </bean> </beans>
对于平台或现有
MBeanServer
具有通过查找方法检索到的动态(或未知)agentId
的情况,应使用factory-method,如以下示例所示:<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="server"> <!-- Custom MBeanServerLocator --> <bean class="platform.package.MBeanServerLocator" factory-method="locateMBeanServer"/> </property> </bean> <!-- other beans here --> </beans>
4.1.3. 延迟初始化的 MBean
如果为 Bean 配置了
MBeanExporter
,并且也为延迟初始化配置了该MBeanExporter
,则MBeanExporter
不会破坏该协定,并且避免实例化该 Bean。相反,它向MBeanServer
注册了一个代理,并推迟从容器获取 Bean,直到对该代理进行第一次调用为止。4.1.4. 自动注册 MBean
通过
MBeanExporter
导出并且已经是有效 MBean 的所有 bean 都将直接在MBeanServer
上注册,而无需 Spring 的进一步干预。通过将autodetect
属性设置为true
,可以使MBeanExporter
自动检测 MBean,如以下示例所示:<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
的 bean 已经是有效的 JMX MBean,并由 Spring 自动注册。缺省情况下,自动检测到 JMX 注册的 bean 的 Bean 名称用作ObjectName
。您可以覆盖此行为,如控制您的 Bean 的 ObjectName 实例中所述。4.1.5. 控制注册行为
考虑以下情形:Spring
MBeanExporter
尝试通过使用ObjectName
bean:name=testBean1
向MBeanServer
注册MBean
。如果MBean
实例已经在同一ObjectName
下注册,则默认行为是失败(并抛出InstanceAlreadyExistsException
)。您可以精确控制将
MBean
注册到MBeanServer
时发生的情况。当注册过程发现MBean
已经在同一ObjectName
下注册时,Spring 的 JMX 支持允许三种不同的注册行为来控制注册行为。下表总结了这些注册行为:表 6.注册行为
FAIL_ON_EXISTING
这是默认的注册行为。如果MBean
实例已经在同一ObjectName
下注册,则不注册正在注册的MBean
,并抛出InstanceAlreadyExistsException
。现有的MBean
不受影响。IGNORE_EXISTING
如果MBean
实例已经在同一ObjectName
下注册,则正在注册的MBean
不会被注册。现有的MBean
不受影响,并且不会抛出Exception
。这在多个应用程序要在共享MBeanServer
中共享一个公共MBean
的设置中很有用。REPLACE_EXISTING
如果MBean
实例已经在同一ObjectName
下注册,那么先前已注册的现有MBean
将被取消注册,而新的MBean
会在其位置注册(新的MBean
有效地替换先前的实例)。上表中的值定义为
RegistrationPolicy
类上的枚举。如果要更改默认注册行为,则需要将MBeanExporter
定义上的registrationPolicy
属性的值设置为这些值之一。下面的示例显示如何从默认注册行为更改为
REPLACE_EXISTING
行为:<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <entry key="bean:name=testBean1" value-ref="testBean"/> </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 的 Management 接口
在preceding section的示例中,您几乎无法控制 bean 的 Management 界面。每个导出 bean 的所有
public
属性和方法分别作为 JMX 属性和操作公开。为了对已导出的 bean 的哪些属性和方法实际上作为 JMX 属性和操作公开而进行更细粒度的控制,Spring JMX 提供了一种全面且可扩展的机制来控制 bean 的 Management 接口。4.2.1. 使用 MBeanInfoAssembler 接口
在幕后,
MBeanExporter
代表org.springframework.jmx.export.assembler.MBeanInfoAssembler
接口的实现,该接口负责定义每个公开的 bean 的 Management 接口。默认实现org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler
定义了一个 Management 接口,该接口公开了所有公共属性和方法(如您在前面几节的示例中所看到的)。 Spring 提供了MBeanInfoAssembler
接口的两个附加实现,使您可以使用源级元数据或任何任意接口来控制生成的 Management 接口。4.2.2. 使用源级元数据:JavaComments
通过使用
MetadataMBeanInfoAssembler
,您可以通过使用源级元数据来定义 bean 的 Management 接口。元数据的读取由org.springframework.jmx.export.metadata.JmxAttributeSource
接口封装。 Spring JMX 提供了一个使用 JavaComments 的默认实现,即org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource
。您必须为MetadataMBeanInfoAssembler
配置JmxAttributeSource
接口的实现实例,才能使其正常运行(没有默认值)。要标记要导出到 JMX 的 bean,应使用
ManagedResource
Comments 对 bean 类进行 Comments。您必须使用_注解标记要公开的每个方法,并使用ManagedAttribute
注解标记希望公开的每个属性。标记属性时,可以省略 getter 或 setter 的 Comments,以分别创建只写或只读属性。带有
ManagedResource
Comments 的 Bean 必须是公共的,公开操作或属性的方法也必须是公共的。以下示例显示了我们在创建一个 MBeanServer中使用的
JmxTestBean
类的带 Comments 版本: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
Comments,并且此ManagedResource
Comments 配置了一组属性。这些属性可用于配置MBeanExporter
生成的 MBean 的各个方面,稍后将在源级元数据类型中进行详细说明。
age
和name
属性都带有ManagedAttribute
Comments,但是,在age
属性的情况下,仅标记了吸气剂。这导致这两个属性都作为属性包含在 Management 界面中,但是age
属性是只读的。最后,
add(int, int)
方法带有ManagedOperation
属性标记,而dontExposeMe()
方法则没有。当您使用MetadataMBeanInfoAssembler
时,这将导致 Management 界面仅包含一个操作(add(int, int)
)。以下配置显示了如何配置
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
。这是为 Spring 公开的 MBean 利用元数据驱动的 Management 接口所需要的全部。4.2.3. 源级元数据类型
下表描述了可在 Spring JMX 中使用的源级别元数据类型:
表 7.源级元数据类型
description
设置资源,属性或操作的友好描述。ManagedResource
,ManagedAttribute
,ManagedOperation
或ManagedOperationParameter
currencyTimeLimit
设置currencyTimeLimit
Descriptors 字段的值。ManagedResource
或ManagedAttribute
defaultValue
设置defaultValue
Descriptors 字段的值。ManagedAttribute
设置log
Descriptors 字段的值。ManagedResource
logFile
设置logFile
Descriptors 字段的值。ManagedResource
persistPolicy
设置persistPolicy
Descriptors 字段的值。ManagedResource
persistPeriod
设置persistPeriod
Descriptors 字段的值。ManagedResource
persistLocation
设置persistLocation
Descriptors 字段的值。ManagedResource
persistName
设置persistName
Descriptors 字段的值。ManagedResource
设置操作参数的显示名称。ManagedOperationParameter
index
设置操作参数的索引。ManagedOperationParameter
4.2.4. 使用 AutodetectCapableMBeanInfoAssembler 接口
为了进一步简化配置,Spring 包含了
AutodetectCapableMBeanInfoAssembler
接口,该接口扩展了MBeanInfoAssembler
接口以添加对自动检测 MBean 资源的支持。如果用AutodetectCapableMBeanInfoAssembler
的实例配置MBeanExporter
,则可以在包含 Bean 的情况下“投票”,以暴露给 JMX。
AutodetectCapableMBeanInfo
接口的唯一实现是MetadataMBeanInfoAssembler
,该投票表决以包括所有带有ManagedResource
属性标记的 bean。在这种情况下,默认方法是将 Bean 名称用作ObjectName
,这将导致与以下内容类似的配置:<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <!-- notice how no 'beans' are explicitly configured here --> <property name="autodetect" value="true"/> <property name="assembler" ref="assembler"/> </bean> <bean id="testBean" class="org.springframework.jmx.JmxTestBean"> <property name="name" value="TEST"/> <property name="age" value="100"/> </bean> <bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler"> <property name="attributeSource"> <bean class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/> </property> </bean> </beans>
请注意,在上述配置中,没有将任何 bean 传递给
MBeanExporter
。但是,JmxTestBean
仍被注册,因为它已被标记为ManagedResource
属性,并且MetadataMBeanInfoAssembler
检测到该问题并对其进行投票以将其包括在内。这种方法的唯一问题是JmxTestBean
的名称现在具有商业意义。您可以通过更改控制您的 Bean 的 ObjectName 实例中定义的ObjectName
创建的默认行为来解决此问题。4.2.5. 使用 Java 接口定义 Management 接口
除了
MetadataMBeanInfoAssembler
之外,Spring 还包括InterfaceBasedMBeanInfoAssembler
,它使您可以基于一组接口中定义的一组方法来约束公开的方法和属性。尽管公开 MBean 的标准机制是使用接口和简单的命名方案,但是
InterfaceBasedMBeanInfoAssembler
扩展了此功能,它消除了对命名约定的需要,使您可以使用多个接口,并且不再需要 bean 来实现 MBean 接口。考虑以下接口,该接口用于为我们先前显示的
JmxTestBean
类定义 Management 接口:public interface IJmxTestBean { public int add(int x, int y); public long myOperation(); public int getAge(); public void setAge(int age); public void setName(String name); public String getName();
该接口定义在 JMX MBean 上作为操作和属性公开的方法和属性。以下代码显示了如何配置 Spring JMX 以使用此接口作为 Management 接口的定义:
<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <entry key="bean:name=testBean5" value-ref="testBean"/> </property> <property name="assembler"> <bean class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler"> <property name="managedInterfaces"> <value>org.springframework.jmx.IJmxTestBean</value> </property> </bean> </property> </bean> <bean id="testBean" class="org.springframework.jmx.JmxTestBean"> <property name="name" value="TEST"/> <property name="age" value="100"/> </bean> </beans>
在前面的示例中,在为任何 bean 构造 Management 接口时,将
InterfaceBasedMBeanInfoAssembler
配置为使用IJmxTestBean
接口。理解由InterfaceBasedMBeanInfoAssembler
处理的 bean 不需要实现用于生成 JMXManagement 接口的接口,这一点很重要。在上述情况下,
IJmxTestBean
接口用于为所有 bean 构造所有 Management 接口。在许多情况下,这不是理想的行为,并且您可能希望对不同的 bean 使用不同的接口。在这种情况下,您可以通过interfaceMappings
属性传递InterfaceBasedMBeanInfoAssembler
Properties
实例,其中每个条目的键是 Bean 名称,每个条目的值是一个用逗号分隔的接口名称列表,用于该 Bean。如果没有通过
managedInterfaces
或interfaceMappings
属性指定 Management 接口,则InterfaceBasedMBeanInfoAssembler
会在 Bean 上进行反映,并使用该 Bean 所实现的所有接口来创建 Management 接口。
4.2.6. 使用 MethodNameBasedMBeanInfoAssembler
MethodNameBasedMBeanInfoAssembler
使您可以指定作为属性和操作公开给 JMX 的方法名称的列表。以下代码显示了示例配置:<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <entry key="bean:name=testBean5" value-ref="testBean"/> </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 属性的适当一半。在前面的代码中,方法 Map 适用于 JMX 公开的 bean。要控制每个 bean 的方法公开,可以使用MethodNameMBeanInfoAssembler
的methodMappings
属性将 bean 名称 Map 到方法名称列表。
4.3. 控制您的 Bean 的 ObjectName 实例
在幕后,
MBeanExporter
委派ObjectNamingStrategy
的实现,以为其注册的每个 bean 获得ObjectName
实例。默认情况下,默认实现KeyNamingStrategy
使用beans
Map
的键作为ObjectName
。此外,KeyNamingStrategy
可以将beans
Map
的键 Map 到Properties
文件(或多个文件)中的条目以解析ObjectName
。除了KeyNamingStrategy
之外,Spring 还提供了两个附加的ObjectNamingStrategy
实现:IdentityNamingStrategy
(基于 Bean 的 JVM 身份构建ObjectName
)和MetadataNamingStrategy
(使用源级元数据获取ObjectName
)。4.3.1. 从属性读取 ObjectName 实例
您可以配置自己的
KeyNamingStrategy
实例,并将其配置为从Properties
实例读取ObjectName
实例,而不必使用 Bean 键。KeyNamingStrategy
尝试使用与 bean 密钥相对应的密钥在Properties
中定位条目。如果未找到任何条目,或者Properties
实例是null
,则使用 bean 密钥本身。以下代码显示了
KeyNamingStrategy
的示例配置:<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <entry key="testBean" value-ref="testBean"/> </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
实例配置在一起,该实例是从 mapping 属性定义的Properties
实例和位于 mappings 属性定义的路径中的属性文件合并而成的。在此配置中,testBean
bean 被赋予bean:name=testBean1
的ObjectName
,因为这是Properties
实例中的条目,该条目具有与 bean 密钥相对应的密钥。如果在
Properties
实例中找不到任何条目,则将 bean 键名用作ObjectName
。
4.3.2. 使用元数据命名策略
MetadataNamingStrategy
使用每个 bean 上ManagedResource
属性的objectName
属性来创建ObjectName
。以下代码显示了MetadataNamingStrategy
的配置:<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <entry key="testBean" value-ref="testBean"/> </property> <property name="namingStrategy" ref="namingStrategy"/> </bean> <bean id="testBean" class="org.springframework.jmx.JmxTestBean"> <property name="name" value="TEST"/> <property name="age" value="100"/> </bean> <bean id="namingStrategy" class="org.springframework.jmx.export.naming.MetadataNamingStrategy"> <property name="attributeSource" ref="attributeSource"/> </bean> <bean id="attributeSource" class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/> </beans>
如果没有为
ManagedResource
属性提供objectName
,那么将使用以下格式创建ObjectName
:* [完全合格的软件包名称]:type = [short-classname],name = [bean-name] *。例如,为以下 bean 生成的ObjectName
将是com.example:type=MyClass,name=myBean
:
<bean id="myBean" class="com.example.MyClass"/>
4.3.3. 配置基于 Comments 的 MBean 导出
如果您更喜欢使用基于 Comments 的方法定义 Management 界面,则可以使用
MBeanExporter
的便利子类:AnnotationMBeanExporter
。在定义该子类的实例时,您不再需要namingStrategy
,assembler
和attributeSource
配置,因为它始终使用基于 JavaComments 的标准元数据(也始终启用自动检测)。实际上,@EnableMBeanExport
@Configuration
Comments 支持更简单的语法,而不是定义MBeanExporter
bean,如以下示例所示:@Configuration @EnableMBeanExport public class AppConfig {
如果您更喜欢基于 XML 的配置,则
<context:mbean-export/>
元素具有相同的目的,并在以下列表中显示:<context:mbean-export/>
如有必要,可以提供对特定 MBean
server
的引用,并且defaultDomain
属性(AnnotationMBeanExporter
的属性)接受生成的 MBeanObjectName
域的备用值。如上一示例所示,它用于代替上一章节MetadataNamingStrategy中描述的标准软件包名称:@EnableMBeanExport(server="myMBeanServer", defaultDomain="myDomain") @Configuration ContextConfiguration {
以下示例显示了与前面的基于 Comments 的示例等效的 XML:
<context:mbean-export server="myMBeanServer" default-domain="myDomain"/>
Warning
请勿将基于接口的 AOP 代理与 bean 类中的 JMXComments 的自动检测结合使用。基于接口的代理“隐藏”目标类,它也隐藏了 JMXManagement 的资源 Comments。因此,在这种情况下,您应该使用目标类代理(通过在
<aop:config/>
,<tx:annotation-driven/>
等上设置'proxy-target-class'标志)。否则,启动时可能会静默忽略您的 JMX bean。4.4. 使用 JSR-160 连接器
对于远程访问,Spring JMX 模块在
org.springframework.jmx.support
包中提供了两个FactoryBean
实现,用于创建服务器端和 Client 端连接器。4.4.1. 服务器端连接器
要使 Spring JMX 创建,启动和公开 JSR-160
JMXConnectorServer
,可以使用以下配置:<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean"/>
默认情况下,
ConnectorServerFactoryBean
创建绑定到service:jmx:jmxmp://localhost:9875
的JMXConnectorServer
。因此,serverConnector
bean 通过 localhost 上的 JMXMP 协议(端口 9875)向 Client 端公开本地MBeanServer
。请注意,JSR 160 规范将 JMXMP 协议标记为可选。当前,主要的开源 JMX 实现 MX4J 和 JDK 随附的实现不支持 JMXMP。要指定另一个 URL 并向
MBeanServer
注册JMXConnectorServer
本身,可以分别使用serviceUrl
和ObjectName
属性,如以下示例所示:
<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean"> <property name="objectName" value="connector:name=rmi"/> <property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector"/> </bean>
如果设置了
ObjectName
属性,Spring 会在ObjectName
下自动将连接器注册到MBeanServer
。以下示例显示了创建JMXConnector
时可以传递给ConnectorServerFactoryBean
的完整参数集:
<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"> <entry key="someKey" value="someValue"/> </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. Client 端连接器
要为启用了远程 JSR-160 的
MBeanServer
创建MBeanServerConnection
,可以使用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 允许扩展 Client 端与服务器之间进行通信的方式。前面各节中显示的示例使用 JSR-160 规范(IIOP 和 JRMP)和(可选)JMXMP 所需的基于 RMI 的强制实现。通过使用其他提供程序或 JMX 实现(例如MX4J),可以通过简单的 HTTP 或 SSL 以及其他协议利用 SOAP 或 Hessian 等协议,如以下示例所示:
<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
中注册的 MBean。这些代理为您提供了一个标准的 Java 接口,您可以通过它与 MBean 进行交互。以下代码显示了如何为在本地MBeanServer
中运行的 MBean 配置代理:<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean"> <property name="objectName" value="bean:name=testBean"/> <property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/> </bean>
在前面的示例中,您可以看到为在
bean:name=testBean
的ObjectName
下注册的 MBean 创建了代理。代理实现的接口集由proxyInterfaces
属性控制,将这些接口上的方法和属性 Map 到 MBean 上的操作和属性的规则与InterfaceBasedMBeanInfoAssembler
使用的规则相同。
MBeanProxyFactoryBean
可以创建可通过MBeanServerConnection
访问的任何 MBean 的代理。默认情况下,已找到并使用了本地MBeanServer
,但是您可以覆盖它并提供一个MBeanServerConnection
指向远程MBeanServer
,以适应指向远程 MBean 的代理:
<bean id="clientConnector" class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean"> <property name="serviceUrl" value="service:jmx:rmi://remotehost:9875"/> </bean> <bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean"> <property name="objectName" value="bean:name=testBean"/> <property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/> <property name="server" ref="clientConnector"/> </bean>
在前面的示例中,我们创建一个
MBeanServerConnection
,该MBeanServerConnection
指向使用MBeanServerConnectionFactoryBean
的远程计算机。然后,此MBeanServerConnection
通过server
属性传递到MBeanProxyFactoryBean
。创建的代理通过此MBeanServerConnection
将所有调用转发到MBeanServer
。
4.6. Notifications
Spring 的 JMX 产品包括对 JMX 通知的全面支持。
4.6.1. 注册侦听器以接收通知
Spring 的 JMX 支持使您可以轻松地将任意数量的
NotificationListeners
注册到任意数量的 MBean(这包括 Spring 的MBeanExporter
导出的 MBean 和通过其他机制注册的 MBean)。例如,考虑一种情况,即每次目标 MBean 的属性发生更改时,都希望(通过Notification
)被告知。以下示例将通知写入控制台:package com.example; import javax.management.AttributeChangeNotification; import javax.management.Notification; import javax.management.NotificationFilter; import javax.management.NotificationListener; public class ConsoleLoggingNotificationListener implements NotificationListener, NotificationFilter { public void handleNotification(Notification notification, Object handback) { System.out.println(notification); System.out.println(handback); public boolean isNotificationEnabled(Notification notification) { return AttributeChangeNotification.class.isAssignableFrom(notification.getClass());
以下示例将
ConsoleLoggingNotificationListener
(在前面的示例中定义)添加到notificationListenerMappings
:
<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <entry key="bean:name=testBean1" value-ref="testBean"/> </property> <property name="notificationListenerMappings"> <entry key="bean:name=testBean1"> <bean class="com.example.ConsoleLoggingNotificationListener"/> </entry> </property> </bean> <bean id="testBean" class="org.springframework.jmx.JmxTestBean"> <property name="name" value="TEST"/> <property name="age" value="100"/> </bean> </beans>
使用先前的配置,每次从目标 MBean(
bean:name=testBean1
)BroadcastJMXNotification
时,都会通知通过notificationListenerMappings
属性注册为侦听器的ConsoleLoggingNotificationListener
bean。然后ConsoleLoggingNotificationListener
bean 可以响应Notification
采取其认为适当的任何操作。您还可以使用纯 bean 名称作为导出的 bean 与侦听器之间的链接,如以下示例所示:
<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <entry key="bean:name=testBean1" value-ref="testBean"/> </property> <property name="notificationListenerMappings"> <entry key="testBean"> <bean class="com.example.ConsoleLoggingNotificationListener"/> </entry> </property> </bean> <bean id="testBean" class="org.springframework.jmx.JmxTestBean"> <property name="name" value="TEST"/> <property name="age" value="100"/> </bean> </beans>
如果要为封闭的
MBeanExporter
导出的所有 bean 注册一个NotificationListener
实例,则可以使用特殊的通配符(*
)作为notificationListenerMappings
属性 Map 中条目的键,如以下示例所示:
<property name="notificationListenerMappings"> <entry key="*"> <bean class="com.example.ConsoleLoggingNotificationListener"/> </entry> </property>
如果需要进行相反操作(即,针对 MBean 注册多个不同的侦听器),则必须使用
notificationListeners
list 属性(而不是notificationListenerMappings
属性)。这次,我们配置NotificationListenerBean
实例,而不是为单个 MBean 配置NotificationListener
。NotificationListenerBean
将NotificationListener
和要注册的ObjectName
(或ObjectNames
)封装在MBeanServer
中。NotificationListenerBean
还封装了许多其他属性,例如NotificationFilter
和可以在高级 JMX 通知场景中使用的任意 handback 对象。使用
NotificationListenerBean
实例时的配置与先前介绍的配置没有很大不同,如以下示例所示:
<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <entry key="bean:name=testBean1" value-ref="testBean"/> </property> <property name="notificationListeners"> <bean class="org.springframework.jmx.export.NotificationListenerBean"> <constructor-arg> <bean class="com.example.ConsoleLoggingNotificationListener"/> </constructor-arg> <property name="mappedObjectNames"> <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
时得到一个递归对象,并且还希望通过提供NotificationFilter
来过滤掉无关的Notifications
。以下示例实现了这些目标:
<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <entry key="bean:name=testBean1" value-ref="testBean1"/> <entry key="bean:name=testBean2" value-ref="testBean2"/> </property> <property name="notificationListeners"> <bean class="org.springframework.jmx.export.NotificationListenerBean"> <constructor-arg ref="customerNotificationListener"/> <property name="mappedObjectNames"> <!-- 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>
(有关移交对象是什么,实际上
NotificationFilter
是什么的完整讨论,请参阅 JMX 规范(1.2)中名为“ JMX 通知模型”的部分。)
4.6.2. 发布通知
Spring 不仅提供注册支持以接收
Notifications
,而且还提供发布Notifications
的支持。这部分实际上仅与已通过
MBeanExporter
公开为 MBean 的 Spring 托管 Bean 有关。任何现有的用户定义的 MBean 都应使用标准的 JMX API 进行通知发布。Spring 的 JMX 通知发布支持中的关键接口是
NotificationPublisher
接口(在org.springframework.jmx.export.notification
包中定义)。任何要通过MBeanExporter
实例作为 MBean 导出的 bean 都可以实现相关的NotificationPublisherAware
接口来访问NotificationPublisher
实例。NotificationPublisherAware
接口通过一个简单的 setter 方法将NotificationPublisher
的实例提供给实现 Bean,然后该 bean 可以使用它来发布Notifications
。如NotificationPublisher接口的 javadoc 中所述,通过
NotificationPublisher
机制发布事件的托管 bean 不负责通知侦听器的状态 Management。 Spring 的 JMX 支持负责处理所有 JMX 基础结构问题。作为应用程序开发人员,您需要做的就是实现NotificationPublisherAware
接口并使用提供的NotificationPublisher
实例开始发布事件。注意,NotificationPublisher
是在已将托管 bean 注册到MBeanServer
之后设置的。使用
NotificationPublisher
实例非常简单。您创建一个 JMXNotification
实例(或适当的Notification
子类的实例),使用与要发布的事件相关的数据填充通知,并在NotificationPublisher
实例上调用sendNotification(Notification)
并传入Notification
。在以下示例中,每次调用
add(int, int)
操作时,JmxTestBean
的导出实例都会发布NotificationEvent
: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 specification(JSR-000003)。
JMX 远程 API 规范(JSR-000160)。
MX4J homepage。 (MX4J 是各种 JMX 规范的开源实现.)
5. JCA CCI
Java EE 提供了一个规范来标准化对企业信息系统(EIS)的访问:JCA(Java EE 连接器体系结构)。该规范分为两个不同的部分:
连接器提供程序必须实现的 SPI(服务提供程序接口)。这些接口构成了可以部署在 Java EE 应用程序服务器上的资源适配器。在这种情况下,服务器将 Management 连接池,事务和安全性(托管模式)。应用服务器还负责 Management 配置,该配置保存在 Client 端应用程序外部。连接器也可以在没有应用程序服务器的情况下使用。在这种情况下,应用程序必须直接对其进行配置(非托管模式)。
应用程序可用于与连接器进行交互并由此与 EIS 通信的 CCI(通用 Client 端接口)。还提供了用于本地事务划分的 API。
Spring CCI 支持的目的是使用 Spring Framework 的常规资源和事务 Management 工具,提供类以典型的 Spring 样式访问 CCI 连接器。
连接器的 Client 端始终不使用 CCI。某些连接器公开了自己的 API,提供了 JCA 资源适配器以使用 Java EE 容器的系统协定(连接池,全局事务和安全性)。 Spring 没有为此类特定于连接器的 API 提供特殊支持。
5.1. 配置 CCI
本节介绍如何配置公共 Client 端接口(CCI)。它包括以下主题:
5.1.1. 连接器配置
使用 JCA CCI 的基本资源是
ConnectionFactory
接口。您使用的连接器必须提供此接口的实现。要使用连接器,可以将其部署在应用程序服务器上,并从服务器的 JNDI 环境(托管模式)中获取
ConnectionFactory
。连接器必须打包为 RAR 文件(资源适配器 Files),并包含ra.xml
文件来描述其部署特性。资源的实际名称是在部署时指定的。要在 Spring 中访问它,可以使用 Spring 的JndiObjectFactoryBean
或<jee:jndi-lookup>
通过其 JNDI 名称获取工厂。使用连接器的另一种方法是将其嵌入到您的应用程序中(非托管模式),而不使用应用程序服务器来部署和配置它。 Spring 提供了通过称为(
LocalConnectionFactoryBean
)的FactoryBean
实现将连接器配置为 Bean 的可能性。通过这种方式,您只需要在 Classpath 中使用连接器库(不需要 RAR 文件和ra.xml
Descriptors)。如有必要,必须从连接器的 RAR 文件中提取该库。一旦可以访问
ConnectionFactory
实例,就可以将其注入到组件中。这些组件可以根据普通的 CCI API 进行编码,也可以使用 Spring 的支持类进行 CCI 访问(例如CciTemplate
)。在非托管模式下使用连接器时,您将无法使用全局事务,因为资源永远不会在当前线程的当前全局事务中被征用或除名。该资源不知道任何可能正在运行的全局 Java EE 事务。
5.1.2. Spring 中的 ConnectionFactory 配置
要连接到 EIS,需要从应用程序服务器(如果处于托管模式)或直接从 Spring(如果处于非托管模式)获取
ConnectionFactory
。在托管模式下,您可以从 JNDI 访问
ConnectionFactory
。其属性在应用程序服务器中配置。以下示例显示了如何执行此操作:<jee:jndi-lookup id="eciConnectionFactory" jndi-name="eis/cicseci"/>
在非托管模式下,必须将要在 Spring 的配置中使用的
ConnectionFactory
配置为 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
实现来配置到 EIS 的连接。要配置其属性,您需要使用专用适配器ConnectionSpecConnectionFactoryAdapter
包装目标连接工厂。您可以使用connectionSpec
属性(作为内部 bean)配置专用的ConnectionSpec
。此属性不是必需的,因为 CCI
ConnectionFactory
接口定义了两种不同的方法来获取 CCI 连接。您通常可以在应用程序服务器(处于托管模式下)或相应的本地ManagedConnectionFactory
实现上配置某些ConnectionSpec
属性。以下 Lists 显示了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
适配器来 Management 此连接。SingleConnectionFactory
适配器类延迟打开单个连接,并在应用程序关闭时销毁该 bean 时将其关闭。此类公开了具有相应行为的特殊Connection
代理,它们均共享相同的基础物理连接。以下示例显示如何使用SingleConnectionFactory
适配器类:<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>
无法使用
ConnectionSpec
直接配置此ConnectionFactory
适配器。如果您需要特定ConnectionSpec
的单个连接,则可以使用SingleConnectionFactory
与之联系的中介ConnectionSpecConnectionFactoryAdapter
。
5.2. 使用 Spring 的 CCI 访问支持
本节描述如何使用 Spring 对 CCI 的支持来实现各种目的。它包括以下主题:
5.2.1. 记录转换
Spring 的 JCA CCI 支持的目的之一是为处理 CCI 记录提供便利的设施。您可以指定策略来创建记录并从 Logging 提取数据,以用于 Spring 的
CciTemplate
。如果您不想直接在应用程序中使用记录,本节中描述的接口将策略配置为使用 Importing 和输出记录。要创建 Importing
Record
,可以使用RecordCreator
接口的专用实现。以下 Lists 显示了RecordCreator
接口定义:public interface RecordCreator { Record createRecord(RecordFactory recordFactory) throws ResourceException, DataAccessException;
createRecord(..)
方法接收RecordFactory
实例作为参数,该实例与所使用的ConnectionFactory
的RecordFactory
相对应。您可以使用此引用来创建IndexedRecord
或MappedRecord
实例。下面的示例演示如何使用RecordCreator
接口以及索引或 Map 记录:
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
提取数据。以下 Lists 显示了RecordExtractor
接口定义:
public interface RecordExtractor { Object extractData(Record record) throws ResourceException, SQLException, DataAccessException;
以下示例显示了如何使用