http://blog.csdn.net/java2000_wl/article/details/7407073
http://zcjl.iteye.com/blog/36336
一: 服務端 暴露服務
- package com.xx.service;
- /**
- * 定義遠程服務接口
- * 1.可以不繼承java.rmi.Remote接口
- * 2.方法可以不拋出java.rmi.RemoteException異常
- *
- */
- public interface ISayHelloService {
- public String doSayHello(String name);
- }
package com.xx.service;
/**
* 定義遠程服務接口
* 1.可以不繼承java.rmi.Remote接口
* 2.方法可以不拋出java.rmi.RemoteException異常
*
*/
public interface ISayHelloService {
public String doSayHello(String name);
}
- package com.xx.service.impl;
- import com.xx.service.ISayHelloService;
- /**
- * 遠程接口實現
- */
- public class ChinaSayHelloServiceImpl implements ISayHelloService {
- public String doSayHello(String name) {
- return "hello " + name;
- }
- }
package com.xx.service.impl;
import com.xx.service.ISayHelloService;
/**
* 遠程接口實現
*/
public class ChinaSayHelloServiceImpl implements ISayHelloService {
public String doSayHello(String name) {
return "hello " + name;
}
}
- package com.xx.service;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- /**
- * 服務端
- * 暴露遠程服務
- */
- public class ServerMain {
- public static void main(String[] args) {
- ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"classpath:applicationContext-server.xml"}, true);
- System.out.println("==============服務啓動成功 ==============");
- }
- }
package com.xx.service;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 服務端
* 暴露遠程服務
*/
public class ServerMain {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"classpath:applicationContext-server.xml"}, true);
System.out.println("==============服務啓動成功 ==============");
}
}
- spring配置文件 applicationContext-server.xml
- <?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:context="http://www.springframework.org/schema/context"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
- <bean id="chinaSayHelloService" class="com.xx.service.impl.ChinaSayHelloServiceImpl" />
- <bean id="chinaSayHelloServiceRmi" class="org.springframework.remoting.rmi.RmiServiceExporter" >
- <property name="serviceName" value="chinaSayHelloService" />
- <property name="service" ref="chinaSayHelloService"/>
- <property name="serviceInterface" value="com.xx.service.ISayHelloService"/>
- <property name="registryPort" value="9999"/>
- </bean>
- </beans>
spring配置文件 applicationContext-server.xml
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="chinaSayHelloService" class="com.xx.service.impl.ChinaSayHelloServiceImpl" />
<bean id="chinaSayHelloServiceRmi" class="org.springframework.remoting.rmi.RmiServiceExporter" >
<property name="serviceName" value="chinaSayHelloService" />
<property name="service" ref="chinaSayHelloService"/>
<property name="serviceInterface" value="com.xx.service.ISayHelloService"/>
<property name="registryPort" value="9999"/>
</bean>
</beans>
二:客戶端 遠程方法調用
- package com.xx.service;
- import java.net.UnknownHostException;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- /**
- * 客戶端
- */
- public class ClientMain {
- public static void main(String[] args) throws UnknownHostException {
- ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext-client.xml");
- ISayHelloService object = applicationContext.getBean("chinaSayHelloServiceRmi", ISayHelloService.class);
- System.out.println(object.doSayHello("張三"));
- }
- }
package com.xx.service;
import java.net.UnknownHostException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 客戶端
*/
public class ClientMain {
public static void main(String[] args) throws UnknownHostException {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext-client.xml");
ISayHelloService object = applicationContext.getBean("chinaSayHelloServiceRmi", ISayHelloService.class);
System.out.println(object.doSayHello("張三"));
}
}
- spring配置文件 applicationContext-client.xml
- <?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:context="http://www.springframework.org/schema/context"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
- <bean id="chinaSayHelloServiceRmi" class="org.springframework.remoting.rmi.RmiProxyFactoryBean" >
- <property name="serviceUrl" value="rmi://192.168.3.104:9999/chinaSayHelloService" />
- <property name="serviceInterface" value="com.xx.service.ISayHelloService"/>
- </bean>
- </beans>
看了《J2EE without EJB》的remote章節,忍不住寫點代碼試試,看看Spring的實現到底多巧妙。
1.先測試RMI服務的發佈,測試代碼如下:
Spring的context配置文件如下:
再寫一個測試程序,如下:
運行TestSpringRmi,報錯如下:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'serviceExporter' defined in class path resource [spring-remote.xml]: Initialization of bean failed; nested exception is java.rmi.StubNotFoundException: Stub class not
found: test.spring.remote.rmi.MyServiceImpl_Stub; nested exception is:
java.lang.ClassNotFoundException: test.spring.remote.rmi.MyServiceImpl_Stub
java.rmi.StubNotFoundException: Stub class not found: test.spring.remote.rmi.MyServiceImpl_Stub; nested exception is:
java.lang.ClassNotFoundException: test.spring.remote.rmi.MyServiceImpl_Stub
咦?Spring不是號稱不需要自己生成stub麼?怎麼會出現“Stub class not found”呢?
祭出google,從spring官方論壇搜到一個帖子:http://forum.springframework.org/showthread.php?t=19185,裏面有條回覆是:
I found the answer:
The class org.springframework.remoting.rmi.RmiInvocationWrap per_Stub is present in spring.jar, but not in the source tree as a Java file. Since I was running against the compiled Spring Java files, rather than the jar, it did not find it.
暈倒,Spring不會這麼弱智吧,難道我以後使用的時候還得把jar包解壓到class目錄下?
不甘心,再搜,找到這個帖子:http://forum.springframework.org/showthread.php?t=12685
在Juergen Hoeller的回覆提示下,我再去看了jpetstore的配置文件,原來用以發佈rmi的接口應該是pojo形式的MyBusinessInterface,而不是那個繼承自Remote的MyService,修改自己的context配置文件:
再運行TestSpringRmi,成功了。console打印:
03-02 14:51:56 INFO [RmiServiceExporter.java:236] Binding RMI service 'myService' to registry at port '1199'
2.再繼續測試客戶端調用,先修改context配置如下:
再修改測試代碼,添加客戶端調用:
運行TestSpringRmi,報錯如下:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'rmiService' defined in class path resource [spring-remote.xml]: Initialization of bean failed; nested exception is java.rmi.ConnectException: Connection refused to host:
localhost; nested exception is:
java.net.ConnectException: Connection refused: connect
java.rmi.ConnectException: Connection refused to host: localhost; nested exception is:
java.net.ConnectException: Connection refused: connect
仔細檢查,原來自己把生成rmi客戶端的bean映射放到了發佈rmi服務的serviceExporter之前了,調換一下順序:
xml 代碼
運行TestSpringRmi,結果如下:
03-02 15:01:24 INFO [RmiServiceExporter.java:236] Binding RMI service 'myService' to registry at port '1199'
03-02 15:01:24 INFO [RmiClientInterceptor.java:128] RMI stub [rmi://localhost:1199/myService] is an RMI invoker
MyServiceImpl.doSomething()
經過一番淺嘗輒止,初步得出幾個結論:
1.Spring對RMI的支持果然很不錯,在Cglib等工具的支持下,使用RMI終於可以同Naming、rmic和stub告別了。
2.用以發佈RMI的接口不能從java.rmi.Remote繼承而來,否則就會出現“Stub class not found”的錯誤,原因有待深究。
3.Spring的BeanFactory創建bean實例是有序的,向RMI、JNDI、WebService等註冊服務性質的應用,同一應用中的客戶端要根據其依賴性調整配置順序。
JNDI的使用方式
服務端註冊
<bean class="org.springframework.remoting.rmi.JndiRmiServiceExporter">
<property name="service" ref="creditService" />
<property name="serviceInterface" value="com.common.CreditRemoteService" />
<property name="jndiName" value="CreditService" />
</bean>
客戶端調用xml配置
<bean id="creditService" class="org.springframework.remoting.rmi.JndiRmiProxyFactoryBean"
scope="prototype" lazy-init="true">
<property name="serviceInterface" value="com.common.CreditRemoteService" />
<property name="lookupStubOnStartup" value="false"/>
<property name="refreshStubOnConnectFailure" value="true"/>
<property name="jndiName" value="CreditService" />
<property name="jndiEnvironment">
<props>
<prop key="java.naming.provider.url">${com.jndi.creditServiceUrl}</prop>
<prop key ="java.naming.factory.initial">weblogic.jndi.WLInitialContextFactory</prop>
</props>
</property>
</bean>
Spring RMI 源碼淺析-RmiServiceExporter 導出服務
Java Rmi
1.接口必須繼承java.rmi.Remote接口
2.方法必須拋出java.rmi.RemoteException異常
Spring Rmi
1.可以不實現java.rmi.Remote接口
2.方法可以不拋出異常
問題:在Spring 內部是怎麼實現的?
在Spring中 是通過org.springframework.remoting.rmi.RmiServiceExporte 在服務端導出一個服務
RmiServiceExporter定義
- public class RmiServiceExporter extends RmiBasedExporter implements InitializingBean, DisposableBean {
- }
public class RmiServiceExporter extends RmiBasedExporter implements InitializingBean, DisposableBean {
}
實現了 InitializingBean接口 Spring會在bean的實例化階段 調用 InitializingBean 的afterPropertiesSet 方法
bean的實例化 會在什麼時候觸發 取決於配置 例如lazy-init
RmiServiceExporter afterPropertiesSet 方法實現
- public void afterPropertiesSet() throws RemoteException {
- prepare();
- }
public void afterPropertiesSet() throws RemoteException {
prepare();
}
prepare方法
- public void prepare() throws RemoteException {
- //檢查配置中的 service對象 如果爲null 拋出異常
- checkService();
- //檢查服務名稱
- if (this.serviceName == null) {
- throw new IllegalArgumentException("Property 'serviceName' is required");
- }
- // Check socket factories for exported object.
- // 略....
- // Determine RMI registry to use.
- if (this.registry == null) {
- //獲得註冊器
- this.registry = getRegistry(this.registryHost, this.registryPort,
- this.registryClientSocketFactory, this.registryServerSocketFactory);
- }
- // 獲得要導出的服務對象
- // getObjectToExport方法 在父類RmiBasedExporter中定義
- // 1.如果實現了jdk Remote接口 那就是一個標準的RMI 類型轉換後 直接返回
- // 2.沒有實現jdk Remote接口 返回spring包裝對象RmiInvocationWrapper調用器 RmiInvocationWrapper實現了jdk Remote接口
- // RmiInvocationWrapper 中有兩個屬性 1.wrappedObject 自己定義的遠程對象[service屬性]
- // 2.RmiBasedExporter 也就是當前導出對象 this 在客戶端調用的時候 會觸發invoke方法
- this.exportedObject = getObjectToExport();
- // 導出服務對象 jdk UnicastRemoteObject實現
- if (this.clientSocketFactory != null) {
- UnicastRemoteObject.exportObject(
- this.exportedObject, this.servicePort, this.clientSocketFactory, this.serverSocketFactory);
- }
- else {
- UnicastRemoteObject.exportObject(this.exportedObject, this.servicePort);
- }
- // Bind RMI object to registry.
- // 把RMI遠程服務對象和註冊器綁定 jdk實現
- try {
- if (this.replaceExistingBinding) {
- //替換指定serviceName的遠程對象
- this.registry.rebind(this.serviceName, this.exportedObject);
- }
- else {
- //綁定對象
- this.registry.bind(this.serviceName, this.exportedObject);
- }
- }
- catch (AlreadyBoundException ex) {
- // Already an RMI object bound for the specified service name...
- unexportObjectSilently();
- throw new IllegalStateException(
- "Already an RMI object bound for name '" + this.serviceName + "': " + ex.toString());
- }
- catch (RemoteException ex) {
- // Registry binding failed: let's unexport the RMI object as well.
- unexportObjectSilently();
- throw ex;
- }
- }
public void prepare() throws RemoteException {
//檢查配置中的 service對象 如果爲null 拋出異常
checkService();
//檢查服務名稱
if (this.serviceName == null) {
throw new IllegalArgumentException("Property 'serviceName' is required");
}
// Check socket factories for exported object.
// 略....
// Determine RMI registry to use.
if (this.registry == null) {
//獲得註冊器
this.registry = getRegistry(this.registryHost, this.registryPort,
this.registryClientSocketFactory, this.registryServerSocketFactory);
}
// 獲得要導出的服務對象
// getObjectToExport方法 在父類RmiBasedExporter中定義
// 1.如果實現了jdk Remote接口 那就是一個標準的RMI 類型轉換後 直接返回
// 2.沒有實現jdk Remote接口 返回spring包裝對象RmiInvocationWrapper調用器 RmiInvocationWrapper實現了jdk Remote接口
// RmiInvocationWrapper 中有兩個屬性 1.wrappedObject 自己定義的遠程對象[service屬性]
// 2.RmiBasedExporter 也就是當前導出對象 this 在客戶端調用的時候 會觸發invoke方法
this.exportedObject = getObjectToExport();
// 導出服務對象 jdk UnicastRemoteObject實現
if (this.clientSocketFactory != null) {
UnicastRemoteObject.exportObject(
this.exportedObject, this.servicePort, this.clientSocketFactory, this.serverSocketFactory);
}
else {
UnicastRemoteObject.exportObject(this.exportedObject, this.servicePort);
}
// Bind RMI object to registry.
// 把RMI遠程服務對象和註冊器綁定 jdk實現
try {
if (this.replaceExistingBinding) {
//替換指定serviceName的遠程對象
this.registry.rebind(this.serviceName, this.exportedObject);
}
else {
//綁定對象
this.registry.bind(this.serviceName, this.exportedObject);
}
}
catch (AlreadyBoundException ex) {
// Already an RMI object bound for the specified service name...
unexportObjectSilently();
throw new IllegalStateException(
"Already an RMI object bound for name '" + this.serviceName + "': " + ex.toString());
}
catch (RemoteException ex) {
// Registry binding failed: let's unexport the RMI object as well.
unexportObjectSilently();
throw ex;
}
}
checkService方法
- protected void checkService() throws IllegalArgumentException {
- if (getService() == null) {
- throw new IllegalArgumentException("Property 'service' is required");
- }
- }
protected void checkService() throws IllegalArgumentException {
if (getService() == null) {
throw new IllegalArgumentException("Property 'service' is required");
}
}
- protected Remote getObjectToExport() {
- //自定義的遠程對象 實現了 jdk Remote
- if (getService() instanceof Remote &&
- (getServiceInterface() == null || Remote.class.isAssignableFrom(getServiceInterface()))) { return (Remote) getService();
- }
- else {
- // 沒有實現 Remote接口 spring在此處包裝了我們自定義的遠程服務對象
- // getProxyForService方法 返回一個代理對象
- return new RmiInvocationWrapper(getProxyForService(), this);
- }
- }
protected Remote getObjectToExport() {
//自定義的遠程對象 實現了 jdk Remote
if (getService() instanceof Remote &&
(getServiceInterface() == null || Remote.class.isAssignableFrom(getServiceInterface()))) { return (Remote) getService();
}
else {
// 沒有實現 Remote接口 spring在此處包裝了我們自定義的遠程服務對象
// getProxyForService方法 返回一個代理對象
return new RmiInvocationWrapper(getProxyForService(), this);
}
}
RmiInvocationWrapper定義 實現了RmiInvocationHandler接口 而RmiInvocationHandler接口繼承了Remote 接口
- class RmiInvocationWrapper implements RmiInvocationHandler {
- private final Object wrappedObject;
- private final RmiBasedExporter rmiExporter;
- public RmiInvocationWrapper(Object wrappedObject, RmiBasedExporter rmiExporter) {
- Assert.notNull(wrappedObject, "Object to wrap is required");
- Assert.notNull(rmiExporter, "RMI exporter is required");
- this.wrappedObject = wrappedObject;
- this.rmiExporter = rmiExporter;
- }
- public String getTargetInterfaceName() {
- Class ifc = this.rmiExporter.getServiceInterface();
- return (ifc != null ? ifc.getName() : null);
- }
- /***
- * 非標準的RMI調用遠程方法的中轉站
- * invocation封裝了方法名 參數名
- */
- public Object invoke(RemoteInvocation invocation)
- throws RemoteException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
- //會在客戶端調用遠程方法時觸發,chuwrappedObject是 我們定義的遠程對象
- return this.rmiExporter.invoke(invocation, this.wrappedObject);
- }
- }
class RmiInvocationWrapper implements RmiInvocationHandler {
private final Object wrappedObject;
private final RmiBasedExporter rmiExporter;
public RmiInvocationWrapper(Object wrappedObject, RmiBasedExporter rmiExporter) {
Assert.notNull(wrappedObject, "Object to wrap is required");
Assert.notNull(rmiExporter, "RMI exporter is required");
this.wrappedObject = wrappedObject;
this.rmiExporter = rmiExporter;
}
public String getTargetInterfaceName() {
Class ifc = this.rmiExporter.getServiceInterface();
return (ifc != null ? ifc.getName() : null);
}
/***
* 非標準的RMI調用遠程方法的中轉站
* invocation封裝了方法名 參數名
*/
public Object invoke(RemoteInvocation invocation)
throws RemoteException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
//會在客戶端調用遠程方法時觸發,chuwrappedObject是 我們定義的遠程對象
return this.rmiExporter.invoke(invocation, this.wrappedObject);
}
}
RmiInvocationHandler接口繼承了 jdk Remote
- public interface RmiInvocationHandler extends Remote {
- }
public interface RmiInvocationHandler extends Remote {
}
- protected Object getProxyForService() {
- //檢查配置中的 service對象 如果爲null 拋出異常
- checkService();
- //檢查serviceInterface屬性
- checkServiceInterface();
- ProxyFactory proxyFactory = new ProxyFactory();
- proxyFactory.addInterface(getServiceInterface());
- if (this.registerTraceInterceptor != null ?
- this.registerTraceInterceptor.booleanValue() : this.interceptors == null) {
- proxyFactory.addAdvice(new RemoteInvocationTraceInterceptor(getExporterName()));
- }
- if (this.interceptors != null) {
- AdvisorAdapterRegistry adapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();
- for (int i = 0; i < this.interceptors.length; i++) {
- proxyFactory.addAdvisor(adapterRegistry.wrap(this.interceptors[i]));
- }
- }
- proxyFactory.setTarget(getService());
- // 生成代理對象 到底是jdk實現 還是cglib實現 取決於 到底有沒有實現接口
- return proxyFactory.getProxy(getBeanClassLoader());
- }
protected Object getProxyForService() {
//檢查配置中的 service對象 如果爲null 拋出異常
checkService();
//檢查serviceInterface屬性
checkServiceInterface();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addInterface(getServiceInterface());
if (this.registerTraceInterceptor != null ?
this.registerTraceInterceptor.booleanValue() : this.interceptors == null) {
proxyFactory.addAdvice(new RemoteInvocationTraceInterceptor(getExporterName()));
}
if (this.interceptors != null) {
AdvisorAdapterRegistry adapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();
for (int i = 0; i < this.interceptors.length; i++) {
proxyFactory.addAdvisor(adapterRegistry.wrap(this.interceptors[i]));
}
}
proxyFactory.setTarget(getService());
// 生成代理對象 到底是jdk實現 還是cglib實現 取決於 到底有沒有實現接口
return proxyFactory.getProxy(getBeanClassLoader());
}
總結:1.spring 容器發佈一個遠程服務 是通過InitializingBean接口驅動起來的
2.spring 包裝了JDK Rmi 也就是說 服務端是spring暴露 客戶端也可以用Jdk rmi調用 沒有任何問題
3,spring對沒有實現Remote接口的遠程服務 用RmiInvocationWrapper做了包裝 RmiInvocationWrapper實現了Remote接口