基於Spring實現的Rmi, HttpInvoker, Hessian, Web services
基於Spring實現的Rmi, HttpInvoker, Hessian, Web services
因為要寫Ronda的關係,所以就想了解下應該怎麼實現Rmi協議。普通的Rmi必須要繼承Remote
方法,但是在框架裡肯定不會為每個類都繼承Remote。
那麼就來看看官網上怎麼說的,然後再分析一下為什麼這麼實現就可以。
Spring使用不同的技術支援多種遠端呼叫。目前支援四種:
Rmoete Method Invocation. 通過使用
RmiProxyFactoryBean
和RmiServiceExporter
支援了傳統的RMI和透明的RMI遠端呼叫。這裡說傳統的是繼承了Remote
java.rmi.RemoteException
,後一種說的是隻要有介面就可以。Spring Http invoker. 只要類實現了介面,那麼Spring就支援通過Http進行序列化傳輸。使用到的類是
Http
和
InvokerProxyFactoryBeanHttpInvokerServiceExporter
Hessian. 使用
HessianProxyFactoryBean
和HessianServiceExporter
你可以使用Hessian協議進行傳輸。Burlap. Burlap是對於Hessian的另外一個格式選擇,即
XML
。使用BurlapProxyFactoryBean
BurlapServiceExporter
JAX RPC. Spring通過JAX-RPC提供對web service的支援
JMS. 使用
JmsInvokerServiceExporter
和JmsInvokerProxyFactoryBean
進行實現
從上面的簡介我們可以看到,Spring主要使用ServiceExporter進行服務的匯出,使用ProxyFactoryBean進行生成代理的服務單物件,然後實現遠端呼叫。
RMI
核心就是講Service匯出來,然後在客戶端生成一個Service的代理物件
Server端:
<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>
Client端:
<bean id="accountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl" value="rmi://HOST:1199/AccountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
Hessian
Hessian協議使用通過HTTP進行通訊。所以使用DispatcherServlet進行暴露服務.
<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>
暴露服務
<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>
客戶端
<bean class="example.SimpleObject">
<property name="accountService" ref="accountService"/>
</bean>
<bean id="accountService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
<property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
HTTP
HTTP其實是使用自己的協議進行HTTP互動,而Hessian、Burlap使用自己的序列化協議。
<bean name="/AccountService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
<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>
Web services
使用JAX-RPC進行暴露
Spring提供一個ServletEndPointSupport類來方便進行暴露web service服務,業務層的類需要繼承ServletEndpointSupport.
public class AccountServiceEndpoint extends ServletEndpointSupport implements RemoteAccountService {
private AccountService biz;
protected void onInit() {
this.biz = (AccountService) getWebApplicationContext().getBean("accountService");
}
public void insertAccount(Account acc) throws RemoteException {
biz.insertAccount(acc);
}
public Account[] getAccounts(String name) throws RemoteException {
return biz.getAccounts(name);
}
}
訪問web service
spring有兩個工廠bean用來建立web service的代理: LocalJaxRpcServiceFactoryBean
和JaxRpcPortProxyFactoryBean
。前面的一個類返回的JAX-RPC服務,後一個返回全面的實現業務介面的類。
<bean id="accountWebService" class="org.springframework.remoting.jaxrpc.JaxRpcPortProxyFactoryBean">
<property name="serviceInterface" value="example.RemoteAccountService"/>
<property name="wsdlDocumentUrl" value="http://localhost:8080/account/services/accountService?WSDL"/>
<property name="namespaceUri" value="http://localhost:8080/account/services/accountService"/>
<property name="serviceName" value="AccountService"/>
<property name="portName" value="AccountPort"/>
</bean>
現在可以正常使用service,不過要處理RemoteException. 可以通過配置一個non-RMI的介面來去除。
客戶端註冊Bean Mappings
public class AxisPortProxyFactoryBean extends JaxRpcPortProxyFactoryBean {
protected void postProcessJaxRpcService(Service service) {
TypeMappingRegistry registry = service.getTypeMappingRegistry();
TypeMapping mapping = registry.createTypeMapping();
registerBeanMapping(mapping, Account.class, "Account");
registry.register("http://schemas.xmlsoap.org/soap/encoding/", mapping);
}
protected void registerBeanMapping(TypeMapping mapping, Class type, String name) {
QName qName = new QName("http://localhost:8080/account/services/accountService", name);
mapping.register(type, qName,
new BeanSerializerFactory(type, qName),
new BeanDeserializerFactory(type, qName));
}
}
註冊自定義的Handler
public class AccountHandler extends GenericHandler {
public QName[] getHeaders() {
return null;
}
public boolean handleRequest(MessageContext context) {
SOAPMessageContext smc = (SOAPMessageContext) context;
SOAPMessage msg = smc.getMessage();
try {
SOAPEnvelope envelope = msg.getSOAPPart().getEnvelope();
SOAPHeader header = envelope.getHeader();
// ...
} catch (SOAPException e) {
throw new JAXRPCException(e);
}
return true;
}
}
public class AccountHandlerJaxRpcPortProxyFactoryBean extends JaxRpcPortProxyFactoryBean {
protected void postProcessJaxRpcService(Service service) {
QName port = new QName(this.getNamespaceUri(), this.getPortName());
List list = service.getHandlerRegistry().getHandlerChain(port);
list.add(new HandlerInfo(AccountHandler.class, null, null));
logger.info("Registered JAX-RPC Handler [" + AccountHandler.class.getName() + "] on port " + port);
}
}
<bean id="accountWebService" class="example.AccountHandlerJaxRpcPortProxyFactoryBean">
<!-- ... -->
</bean>
Exposing web services using XFire
新增servlet
<servlet>
<servlet-name>xfire</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
</servlet>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:org/codehaus/xfire/spring/xfire.xml
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
新增servlet mapping
<beans>
<bean name="/Echo" class="org.codehaus.xfire.spring.remoting.XFireExporter">
<property name="serviceInterface" value="org.codehaus.xfire.spring.Echo"/>
<property name="serviceBean">
<bean class="org.codehaus.xfire.spring.EchoImpl"/>
</property>
<!-- the XFire bean is defined in the xfire.xml file -->
<property name="xfire" ref="xfire"/>
</bean>
</beans>
JMS
通過JMS進行互動。 Spring支援JMS是很基本的,在同一個執行緒中進行通訊,不支援事務。
Server和Client都需要的介面
package com.foo;
public interface CheckingAccountService {
void cancelAccount(Long accountId);
}
Server端實現:
package com.foo;
public class SimpleCheckingAccountService implements CheckingAccountService {
public void cancelAccount(Long accountId) {
System.out.println("Cancelling account [" + accountId + "]");
}
}
Server和Client都有的配置檔案:
<?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-2.0.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>
Server端的配置:
<?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-2.0.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>
Client端的配置:
<?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-2.0.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>
簡單說下RMI其實還是走Java的那一套,就是判斷你的service有沒有繼承Remote介面,如果沒有就給你生成一個繼承的介面暴露出去。