spring rmi 简单使用
1.使用RmiServiceExporter暴露服务
使用RmiServiceExporter,我们可以把对象的接口暴露成RMI对象。可以使用 RmiProxyFactoryBean 或者在传统RMI服务中使用普通RMI来访问该接口。RmiServiceExporter 显式地支持使用RMI调用器暴露任何非RMI的服务。当然,我们首先需要在Spring容器中设置我们的服务:
新建一个userDao的接口 及userDaoImpl实现类
userDao
package com.soyen.dao;
public interface UserDao {
public String login(String username,String password);
}
UserDaoImpl
package com.soyen.dao.impl;
import com.soyen.dao.UserDao;
public class UserDaoImpl implements UserDao {
public String login(String username, String password) {
return "你好" + username + "你的密码为:" + password;
}
}
现将服务端配置代码贴上
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="userDao" class="com.soyen.dao.impl.UserDaoImpl"></bean>
<!-- 将类暴露成为一个RMI服务 -->
<bean id="rmiService" class="org.springframework.remoting.rmi.RmiServiceExporter">
<!-- 服务类 -->
<property name="service" ref="userDao" />
<!-- 服务名 -->
<property name="serviceName" value="UserService" />
<!-- 服务接口 -->
<property name="serviceInterface" value="com.soyen.dao.UserDao" />
<!-- 服务端口默认为1199-->
<property name="registryPort" value="1091" />
</bean>
</beans>
import org.springframework.remoting.rmi.RmiProxyFactoryBean;
import com.soyen.dao.UserDao;
public class TestRmi {
public static void main(String[] args) {
RmiProxyFactoryBean factory = new RmiProxyFactoryBean();
factory.setServiceInterface(UserDao.class);
factory.setServiceUrl("rmi://localhost:1091/UserService");
factory.afterPropertiesSet();
UserDao userService = (UserDao)factory.getObject();
String msg=userService.login("zhangsan", "12344");
System.out.println(msg);
}
}
<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.5.xsd">
<bean id="rmiService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceInterface" value="com.soyen.dao.UserDao" ></property>
<property name="serviceUrl" value="rmi://localhost:1091/UserService"></property>
</bean>
</beans>
Spring Rmi远程方法调用
一: 服务端 暴露服务
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;
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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/contexthttp://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("张三"));
}
}
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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/contexthttp://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服务的发布,测试代码如下:
java 代码
//MyService.java: remote interface for RMI
package test.spring.remote.rmi;
public interface MyService extends java.rmi.Remot {
public void doSomething() throws java.rmi.RemoteException;
}
//MyBusinessInterface.java: My own business interface, must has the same methods as MyService
package test.spring.remote.rmi;
public interface MyBusinessInterface {
public void doSomething();
}
//MyServiceImpl.java: the service implement class
package test.spring.remote.rmi;
public class MyServiceImpl implements MyService, MyBusinessInterface {
public void doSomething() {
System.out.println("MyServiceImpl.doSomething()");
}
}
Spring的context配置文件如下:
xml 代码
//spring-remote.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="myService" class="test.spring.remote.rmi.MyServiceImpl" />
<bean id="serviceExporter" class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="serviceName">
<value>myService</value>
</property>
<property name="service">
<ref bean="myService" />
</property>
<property name="serviceInterface">
<value>test.spring.remote.rmi.MyService</value>
</property>
<property name="registryPort">
<value>1199</value>
</property>
</bean>
</beans>
再写一个测试程序,如下:
java 代码
//TestSpringRmi.java
package test.spring.remote.rmi;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestSpringRmi {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-remote.xml");
}
}
运行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配置文件:
xml 代码
//spring-remote.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="myService" class="test.spring.remote.rmi.MyServiceImpl" />
<bean id="serviceExporter" class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="serviceName">
<value>myService</value>
</property>
<property name="service">
<ref bean="myService" />
</property>
<property name="serviceInterface">
<!-- <value>test.spring.remote.rmi.MyService</value> -->
<value>test.spring.remote.rmi.MyBusinessInterface</value>
</property>
<property name="registryPort">
<value>1199</value>
</property>
</bean>
</beans>
再运行TestSpringRmi,成功了。console打印:
03-02 14:51:56 INFO [RmiServiceExporter.java:236] Binding RMI service 'myService' to registry at port '1199'
2.再继续测试客户端调用,先修改context配置如下:
xml 代码
//spring-remote.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="rmiService"
class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceInterface">
<value>test.spring.remote.rmi.MyBusinessInterface</value>
</property>
<property name="serviceUrl">
<value>rmi://localhost:1199/myService</value>
</property>
</bean>
<bean id="myService" class="test.spring.remote.rmi.MyServiceImpl" />
<bean id="serviceExporter" class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="serviceName">
<value>myService</value>
</property>
<property name="service">
<ref bean="myService" />
</property>
<property name="serviceInterface">
<!-- <value>test.spring.remote.rmi.MyService</value> -->
<value>test.spring.remote.rmi.MyBusinessInterface</value>
</property>
<property name="registryPort">
<value>1199</value>
</property>
</bean>
</beans>
再修改测试代码,添加客户端调用:
java 代码
//TestSpringRmi.java
package test.spring.remote.rmi;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestSpringRmi {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-remote.xml");
MyBusinessInterface service = (MyBusinessInterface) context.getBean("rmiService");
service.doSomething();
}
}
运行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 代码
//spring-remote.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="myService" class="test.spring.remote.rmi.MyServiceImpl" />
<bean id="serviceExporter" class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="serviceName">
<value>myService</value>
</property>
<property name="service">
<ref bean="myService" />
</property>
<property name="serviceInterface">
<!-- <value>test.spring.remote.rmi.MyService</value> -->
<value>test.spring.remote.rmi.MyBusinessInterface</value>
</property>
<property name="registryPort">
<value>1199</value>
</property>
</bean>
<bean id="rmiService"
class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceInterface">
<value>test.spring.remote.rmi.MyBusinessInterface</value>
</property>
<property name="serviceUrl">
<value>rmi://localhost:1199/myService</value>
</property>
</bean>
</beans>
运行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的使用方式
服务端注册
[java] view plaincopyprint? <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>
<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配置
[java] view plaincopyprint?<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>
<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 导出服务
分类: 源码学习 2012-03-29 17:25 317人阅读 评论(0) 收藏 举报
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定义
[java] view plaincopyprint?public class RmiServiceExporter extends RmiBasedExporter implements InitializingBean, DisposableBean {
}
[java] view plaincopyprint?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 方法实现
[java] view plaincopyprint?public void afterPropertiesSet() throws RemoteException {
prepare();
}
[java] view plaincopyprint?public void afterPropertiesSet() throws RemoteException {
prepare();
}
public void afterPropertiesSet() throws RemoteException {
prepare();
}prepare方法
[java] view plaincopyprint?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;
}
}
[java] view plaincopyprint?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方法
[java] view plaincopyprint?protected void checkService() throws IllegalArgumentException {
if (getService() == null) {
throw new IllegalArgumentException("Property 'service' is required");
}
}
[java] view plaincopyprint?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");
}
}[java] view plaincopyprint?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);
}
}
[java] view plaincopyprint?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 接口
[java] view plaincopyprint?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);
}
}
[java] view plaincopyprint?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
[java] view plaincopyprint?public interface RmiInvocationHandler extends Remote {
}
[java] view plaincopyprint?public interface RmiInvocationHandler extends Remote {
}
public interface RmiInvocationHandler extends Remote {
}[java] view plaincopyprint?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());
}
[java] view plaincopyprint?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接口
RMI@SPRING的常见问题解决
RMI 问题 1 背景:
用 ./shutdown.sh 关闭 rmi 服务器的 tomcat ,然后 ./startup.sh 启动,客户端连接总是会导致如下错误:
org.springframework.remoting.RemoteLookupFailureException: Lookup of RMI stub failed; nested exception is java.rmi.UnmarshalException: error unmarshalling return; nested exception is:
java.io.EOFException
com.ffcs.ieie.communicate.ieiemp.IeiempException: org.springframework.remoting.RemoteLookupFailureException: Lookup of RMI st
ub failed; nested exception is java.rmi.UnmarshalException: error unmarshalling return; nested exception is:
java.io.EOFException
RMI 问题 1 分析:
The cause of the problem is the fact that Spring creates an RMIRegistry with the classloader of the server webapp. Then, when restarting the server, the RMIRegistry is not shut down. After the restart the Registry keeps its references to the old Stubs which do not exist anymore.
There are two solutions:
1) Start the rmiregistry in a seperate process without the classpath of the server app.
2) (better approach) Let spring start the RMIRegistry throu RmiRegistryFactoryBean, which shuts it down correctly.
参考: http://forum.springframework.org/showthread.php?t=33073
RMI 问题 1 解决:
将服务器中的 spring 配置代码,如下:
< bean id = "incomingService" class = "com.ffcs.ieiemp.ieie.rmi.IncomingService" />
< bean id = "rmiService" class = "org.springframework.remoting.rmi.RmiServiceExporter" >
< property name = "serviceName" value = "IncomingService" />
< property name = "service" ref = "incomingService" />
< property name = "serviceInterface" value = "com.ffcs.ieiemp.ieie.rmi.Incoming" />
< property name = "registryPort" value = "1099" />
</ bean >
替换为:
<bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
<property name="port" value="1099"/>
</bean>
< bean id = "incomingService" class = "com.ffcs.ieiemp.ieie.rmi.IncomingService" />
< bean id = "rmiService" class = "org.springframework.remoting.rmi.RmiServiceExporter" >
< property name = "serviceName" value = "IncomingService" />
< property name = "service" ref = "incomingService" />
< property name = "serviceInterface" value = "com.ffcs.ieiemp.ieie.rmi.Incoming" />
<!-- <property name="registryPort" value="1099"/> -->
<property name="registry" ref="registry"/>
</ bean >
RMI 问题 2 背景:
RMI服务器重启,总是会出现客户端连接拒绝的问题。
RMI 问题2 分析:
服务器重启会影响到客户端?说明客户端有保存着重启之前的服务器连接相关记录。经研究发现,客户端Stub 有缓存,所以只要刷新缓存即可解决问题。参考: http://forum.springsource.org/showthread.php?t=61575
RMI 问题2 解决:
在客户端连接代码中增加红色标识的代码:
RmiProxyFactoryBean factory=newRmiProxyFactoryBean();
factory.setServiceInterface(Incoming.class);
factory.setServiceUrl(url);
//XXXvincan: 解决重启 rmi 的服务器后会出现拒绝连接或找不到服务对象的错误
factory.setLookupStubOnStartup(false );
factory.setRefreshStubOnConnectFailure(true );
factory.afterPropertiesSet();
Incoming service=(Incoming)factory.getObject();
因为RMI stub被连接到特定的端点,不仅仅是为每个调用打开一个给定的目标地址的连接,所以如果重新启动RMI端点主机的服务器,那么就需要重新注册这些stub,并且客户端需要再次查询它们。
虽然目标服务的重新注册在重新启动时通常会自动发生,不过此时客户端保持的stub将会变的陈旧,且客户端不会注意这些,除非他们再次尝试调用stub上的方法,而这也将throw一个连接失败的异常。
为了避免这种情形,Spring的RmiProxyFactoryBean提供了一个refreshStubOnConnectFailure的bean属性,如果调用失败,并且连接异常的话,将它设定为true来强制重新自动查询stub。
<bean id="reportService"
class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl">
<value>serviceurl</value>
</property>
<property name="serviceInterface">
<value>ServiceIntfc</value>
</property>
<property name="refreshStubOnConnectFailure">
<value>true</value>
</property>
</bean>
stub查询的另一个问题是,目标RMI服务器和RMI注册项在查询时要为可用的。如果客户端在服务器启动之前,尝试查询和缓存该服务stub,那么客户端的启动将会失败(即使还不需要该服务)。
为了能够惰性查询服务stub,设定RmiProxyFactoryBean的lookupStubOnStarup标志为false。然后在第一次访问时查询该stub,也就是说,当代理上的第一个方法被调用的时候去主动查询stub,同时被缓存。这也有一个缺点,就是直到第一次调用,否则无法确认目标服务是否实际存在。
<bean id="reportService"
class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl">
<value>serviceurl</value>
</property>
<property name="serviceInterface">
<value>Service</value>
</property>
<property name="lookupStubOnStartup">
<value>false</value>
</property>
<property name="refreshStubOnConnectFailure">
<value>true</value>
</property>
</bean>
还有一个属性就是cacheStub,当它设置为false的时候,就完全避免了stub的缓存,但影响了性能。需要的时候还是可以试试
RMI 问题3 背景:
两台服务器, JDK 都是 1.6 ,在一个局域网内,内网 IP 分别为 192.168.39.11 , 192.168.39.164 ,对应的的还有外网 IP 。写了一个 RMI 服务器和客户端,在本地调试没有问题。把服务器端布署到 11 这台服务器上后,在 164 客户端连接却总是抛错:
java.rmi.ConnectException: Connection refused to host:127.0.0.1; nested exception is:
java.net.ConnectException: Connection refused: connect
at sun.rmi.transport.tcp.TCPEndpoint.newSocket(Unknown Source)
at sun.rmi.transport.tcp.TCPChannel.createConnection(Unknown Source)
at sun.rmi.transport.tcp.TCPChannel.newConnection(Unknown Source)
at sun.rmi.server.UnicastRef.invoke(Unknown Source)
at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod
(Unknown Source)
at java.rmi.server.RemoteObjectInvocationHandler.invoke(Unknown Source)
at $Proxy0.call(Unknown Source)
at RMI.Client.callRMI(Client.java:39)
at RMI.Client.main(Client.java:53)
Caused by: java.net.ConnectException: Connection refused: connect
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(Unknown Source)
at java.net.PlainSocketImpl.connectToAddress(Unknown Source)
at java.net.PlainSocketImpl.connect(Unknown Source)
at java.net.SocksSocketImpl.connect(Unknown Source)
at java.net.Socket.connect(Unknown Source)
at java.net.Socket.connect(Unknown Source)
at java.net.Socket.<init>(Unknown Source)
at java.net.Socket.<init>(Unknown Source)
at sun.rmi.transport.proxy.RMIDirectSocketFactory.createSocket
(Unknown Source)
at sun.rmi.transport.proxy.RMIMasterSocketFactory.createSocket
(Unknown Source)
... 9 more
也就是 IP 被指向 127.0.0.1 了,而客户端发起连接的时候 IP 绝对是写的 IP 是 192.168.39.11 。
RMI 问题3 分析:
这就是典型的服务器有多个ip 引起的 rmi 连接问题。
RMI 问题3 解决:
解决方法有三:
1. 服务器端添加代码:System.setProperty("java.rmi.server.hostname" , "192.168.39.11" );
可写在全局监听器里成为全局变量。
2. 在RMI 服务器上 root 身份登录,输入 Vi /etc/hosts ,在第一行添加 192.168.39.11 ieie
3. 若是用spring, 则在 RmiServiceExporter 中添加属性 <property name="registryHost" value="192.168.39.11" />
通过spring设置java系统属性
在做RMI的时候需要如遇到rmi所在服务是多网卡时,需要对系统属性java.rmi.server.hostname进行设置 ,以前我们会扩展spring的listener
- public class SpringContextLoaderListener extends ContextLoaderListener {
- @Override
- public void contextInitialized(ServletContextEvent event) {
- //初始化之前在设置java.rmi.server.hostname
- System.setProperty("java.rmi.server.hostname", "ip");
- super.contextInitialized(event);
- }
- }
public class SpringContextLoaderListener extends ContextLoaderListener {
@Override
public void contextInitialized(ServletContextEvent event) {
//初始化之前在设置java.rmi.server.hostname
System.setProperty("java.rmi.server.hostname", "ip");
super.contextInitialized(event);
}
}
虽然这样没问题, 但是spring为我们提供了更简单快捷的方式,只需要配置就行了
- <bean
- class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
- <property name="targetObject" value="#{@systemProperties}" />
- <property name="targetMethod" value="putAll" />
- <property name="arguments">
- <props>
- <prop key="java.rmi.server.hostname">${java.rmi.server.hostname}</prop>
- </props>
- </property>
- </bean>
<bean
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" value="#{@systemProperties}" />
<property name="targetMethod" value="putAll" />
<property name="arguments">
<props>
<prop key="java.rmi.server.hostname">${java.rmi.server.hostname}</prop>
</props>
</property>
</bean>
注意:java.rmi.server.hostname这个是属性文件里的KEY,比如:在设置数据库连接的文件里就可以.
jdbc.properties
在jdbc.properties属性文件中定义属性值:
jdbc.driverClassName= com.mysql.jdbc.Driver
jdbc.url= jdbc:mysql://localhost:3306/manger
jdbc.username=root
jdbc.password=1234
java.rmi.server.hostname=127.0.0.1
提示 经常有开发者在${xxx}的前后不小心键入一些空格,这些空格字符将和变量合并后作为属性的值。如: <property name="username" value=" ${jdbc.username} "></property> 的属性配置项,在前后都有空格,被解析后,username的值为“ 1234 ”,这将造成最终的错误,因此需要特别小心。
Spring配置数据源及加载配置文件
<!-- jdbc properties -->
<bean id="jdbcConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="/WEB-INF/jdbc.properties"/>
</bean>
<!-- jdbc data source -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
scope="singleton" name="c3po_datasource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
Failed to start jmx connector: Cannot bind to URL(jmxrmi)
Failed to start jmx connector: Cannot bind to URL[rmi://localhost:1099/jmxrmi]
INFO ActiveMQ JMS Message Broker (localhost, ID:cnshawlt1129-1495-1316140863881-0:0) started
INFO Connector vm://localhost Started
WARN Failed to start jmx connector: Cannot bind to URL [rmi://localhost:1099/jmxrmi]: javax.naming.ServiceUnavailableException [Root exception is java.rmi.ConnectException: Connection refused to host: localhost; nested exception is:
java.net.ConnectException: Connection refused: connect]
Xml代码
1. <bean id="broker" class="org.apache.activemq.xbean.BrokerFactoryBean">
2. <property name="config" value="classpath:org/apache/activemq/xbean/activemq.xml" />
3. <property name="start" value="true" />
4. </bean>
5.
6. <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory" depends-on="broker">
7. <property name="brokerURL" value="vm://localhost"/>
8. </bean>
如果persistent是true,那么ActiveMQ会在当前目录下创建一个缺省值是activemq-data的目录用于持久化保存数据。需要注意的是,如果程序中启动了多个不同名字的VM broker,那么可能会有如下警告:Failed to start jmx connector: Cannot bind to URL [rmi://localhost:1099/jmxrmi]: javax.naming.NameAlreadyBoundException…可以通过在transportOptions中追加 broker.useJmx=false来禁用JMX来避免这个警告
2.2.2 TCP Transport
TCP transport 允许客户端通过TCP socket连接到远程的broker。以下是配置语法:
tcp://hostname:port?transportOptions
Transport Options的可选值如下:
Option Name | Default Value | Description |
minmumWireFormatVersion | 0 | The minimum version wireformat that is allowed |
trace | false | Causes all commands that are sent over the transport to be logged |
useLocalHost | true | When true, it causes the local machines name to resolve to “localhost”. |
socketBufferSize | 64 * 1024 | Sets the socket buffer size in bytes |
soTimeout | 0 | sets the socket timeout in milliseconds |
connectionTimeout | 30000 | A non-zero value specifies the connection timeout in milliseconds. A zero value means wait forever for the connection to be established. Negative values are ignored. |
wireFormat | default | The name of the WireFormat to use |
wireFormat.* | All the properties with this prefix are used to configure the wireFormat. See Configuring Wire Formats for more information |
例如:tcp://localhost:61616?trace=false