1.远程调用rmi协议
Java代码
1. Exception in thread "main" java.rmi.UnmarshalException: error unmarshalling return; nested exception is:
2. class loader disabled)
3. at sun.rmi.registry.RegistryImpl_Stub.lookup(Unknown Source)
4. at java.rmi.Naming.lookup(Unknown Source)
5. 11)
6. Caused by: java.lang.ClassNotFoundException: org.springframework.remoting.rmi.RmiInvocationWrapper_Stub (no security manager: RMI class loader disabled)
7. at sun.rmi.server.LoaderHandler.loadClass(Unknown Source)
8. at sun.rmi.server.LoaderHandler.loadClass(Unknown Source)
9. 2.loadClass(Unknown Source)
10. at java.rmi.server.RMIClassLoader.loadClass(Unknown Source)
11. at sun.rmi.server.MarshalInputStream.resolveClass(Unknown Source)
12. at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)
13. at java.io.ObjectInputStream.readClassDesc(Unknown Source)
14. at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
15. at java.io.ObjectInputStream.readObject0(Unknown Source)
16. at java.io.ObjectInputStream.readObject(Unknown Source)
17. 3 more
1.1 spring2.5和spring3.0的区别
org.springframework.remoting.rmi.RmiInvocationWrapper_Stub没有,从网上也发现了,这个是由于spring2和spring3的rmi方式调用方式不同引起的,通过查阅相关文档后发现,spring3不在需要生成skeleton和stub了,所以把这个类从spring-context中删除了,解决办法就是想办法将它再加进来,知道了病根就知道了怎么治病了,下面给出药方:
RmiInvocationWrapper_Stub类从spring2里面解压出来,然后再生成一个包。
这边给出来解决的办法:
1. 下载spring-context的2.5.6版本的程序,将其解压,解压命令如下:
Java代码
1. jar -xvf modules/spring-context.jar org/springframework/remoting/rmi/RmiInvocationWrapper_Stub.class
RmiInvocationWrapper_Stub.class生成到一个新的的jar包里面,比如spring-2.5.6-rmi-compatibility.jar
Java代码
- jar -cvf spring-2.5.6-rmi-compatibility.jar org/springframework/remoting/rmi
3. 由于我们系统使用了maven,这个包可以加入到maven的依赖里面,具体的使用scope为System就可以:
Xml代码
1. <dependency>
2. <groupId>org.springframework</groupId>
3. <artifactId>spring-rmi-compatibility</artifactId>
4. <version>2.5.6</version>
5. <scope>system</scope>
6. <systemPath>${basedir}/src/main/webapp/WEB-INF/lib/spring-2.5.6-rmi-compatibility.jar</systemPath>
7. </dependency>
RmiProxyFactoryBean 的实现。
pring Rmi 客户端是通过 RmiProxyFactoryBean 和它的父类来完成 查找远程对象 生成代理对象 方法调用
RmiProxyFactoryBean 定义
Java代码
1. public class RmiProxyFactoryBean extends RmiClientInterceptor implements FactoryBean, BeanClassLoaderAware {
2. }
父类RmiClientInterceptor 定义
Java代码
1. public class RmiClientInterceptor extends RemoteInvocationBasedAccessor implements MethodInterceptor {
2.
3. //spring容器 bean实例化阶段 是否要 查找远程对象 预查找
4. private boolean lookupStubOnStartup = true;
5.
6. //查找过的 远程对象是否进行缓存
7. private boolean cacheStub = true;
8.
9. //如果连接失败 是否刷新远程调用stub
10. private boolean refreshStubOnConnectFailure = false;
11.
12. //rmi客户端 套接字工厂
13. private RMIClientSocketFactory registryClientSocketFactory;
14.
15. //缓存远程调用对象
16. private Remote cachedStub;
17.
18. //查找远程对象时用到的监控器
19. private final Object stubMonitor = new Object();
20.
21. //.....略
22. }
一:查找远程服务对象
RmiProxyFactoryBean 是InitializingBean接口的实现 Spring容器在bean的实例化(getBean)阶段 回调afterPropertiesSet 来查找远程对象 然后 生成远程代理对象
Java代码
1. public void afterPropertiesSet() {
2. //父类RmiClientInterceptor检查serviceUrl是否配置
3. //父类RmiClientInterceptor 查找远程对象
4. super.afterPropertiesSet();
5. //远程调用接口检查
6. if (getServiceInterface() == null) {
7. throw new IllegalArgumentException("Property 'serviceInterface' is required");
8. }
9. //创建代理对象
10. //因为父类RmiClientInterceptor实现了 MethodInterceptor 接口 所以this
11. this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
12. }
父类RmiClientInterceptor的 afterPropertiesSet 方法 干了两件事
1.验证是否配置了serviceUrl 如果没有 抛出异常
2.查找远程对象
Java代码
1. public void afterPropertiesSet() {
2. //检查serviceUrl 属性是否为空 如果为空直接抛出异常
3. super.afterPropertiesSet();
4. //查找远程对象
5. prepare();
6. }
Java代码
1. public void prepare() throws RemoteLookupFailureException {
2. //预查找远程对象 默认为true
3. if (this.lookupStubOnStartup) {
4. //通过标准Api 查找远程对象
5. Remote remoteObj = lookupStub();
6. //是否对stub进行缓存
7. if (this.cacheStub) {
8. this.cachedStub = remoteObj;
9. }
10. }
11. }
通过java API查找远程对象
Java代码
1. protected Remote lookupStub() throws RemoteLookupFailureException {
2. try {
3. null;
4. if (this.registryClientSocketFactory != null) {
5. ...略 }
6. else {
7. //TODO 通过客户端配置 serviceUrl查找对象
8. stub = Naming.lookup(getServiceUrl());
9. }
10. return stub;
11. }
12. catch (MalformedURLException ex) {
13. throw new RemoteLookupFailureException("Service URL [" + getServiceUrl() + "] is invalid", ex);
14. }
15. catch (NotBoundException ex) {
16. throw new RemoteLookupFailureException(
17. "Could not find RMI service [" + getServiceUrl() + "] in RMI registry", ex);
18. }
19. catch (RemoteException ex) {
20. throw new RemoteLookupFailureException("Lookup of RMI stub failed", ex);
21. }
22. }
二:返回代理对象
RmiProxyFactoryBean是FactoryBean接口的实现 其返回的是getObject方法 返回的对象
Java代码
1. /**
2. * 返回远程代理对象
3. * 创建代理对象 是在afterPropertiesSet 方法完成
4. */
5. public Object getObject() {
6. return this.serviceProxy;
7. }
三:调用方法
父类实现了MethodInterceptor接口 在客户端调用方法时会被拦截
Java代码
1. public Object invoke(MethodInvocation invocation) throws Throwable {
2. //获取远程对象 如果配置了缓存cacheStub=true 从缓存中获取 缓存中没有 现在立刻查找
3. Remote stub = getStub();
4. try {
5. //TODO 客户端调用远程方法时 拦截处理
6. return doInvoke(invocation, stub);
7. }
8. catch (RemoteConnectFailureException ex) {
9. return handleRemoteConnectFailure(invocation, ex);
10. }
11. catch (RemoteException ex) {
12. //如果是连接失败异常
13. if (isConnectFailure(ex)) {
14. //处理连接失败. 是否需要刷新
15. return handleRemoteConnectFailure(invocation, ex);
16. }
17. else {
18. throw ex;
19. }
20. }
21. }
方法调用,如果是标准的Rmi 通过反射调用,非标准的交给doInvoke方法处理
Java代码
1. protected Object doInvoke(MethodInvocation invocation, Remote stub) throws Throwable {
2. //spring RmiInvocationHandler包装的远程对象 非实现Remote接口的
3. if (stub instanceof RmiInvocationHandler) {
4. try {
5. //不是标准的Rmi
6. return doInvoke(invocation, (RmiInvocationHandler) stub);
7. }
8. //....略
9. }
10. else {
11. //标准的java Rmi
12. try {
13. //直接通过反射调用
14. return RmiClientInterceptorUtils.invokeRemoteMethod(invocation, stub);
15. }
16. //....略
17. }
18. }
标准Rmi方法调用处理 RmiClientInterceptorUtils的 invokeRemoteMethod方法
Java代码
1. public static Object invokeRemoteMethod(MethodInvocation invocation, Object stub)
2. throws InvocationTargetException {
3.
4. Method method = invocation.getMethod();
5. try {
6. if (method.getDeclaringClass().isInstance(stub)) {
7. // directly implemented
8. return method.invoke(stub, invocation.getArguments());
9. }
10. else {
11. // not directly implemented
12. Method stubMethod = stub.getClass().getMethod(method.getName(), method.getParameterTypes());
13. return stubMethod.invoke(stub, invocation.getArguments());
14. }
15. }
16. catch (InvocationTargetException ex) {
17. throw ex;
18. }
19. catch (NoSuchMethodException ex) {
20. throw new RemoteProxyFailureException("No matching RMI stub method found for: " + method, ex);
21. }
22. catch (Throwable ex) {
23. throw new RemoteProxyFailureException("Invocation of RMI stub method failed: " + method, ex);
24. }
25. }
非标准Rmi处理 方法名 参数封装成InvocationHandler 通过中转站方法调用目标方法
Java代码
1. protected Object doInvoke(MethodInvocation methodInvocation, RmiInvocationHandler invocationHandler)
2. throws RemoteException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
3. // 如果是toString方法
4. if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
5. return "RMI invoker proxy for service URL [" + getServiceUrl() + "]";
6. }
7. //invocationHandler spring包装过的Rmi远程对象 服务端在暴露服务时包装
8. //createRemoteInvocation方法 返回RemoteInvocation实例 封装了方法调用相关信息 例如:参数, 方法名
9. //Rmi服务端接受到信息后 会通过RemoteInvocation封装的信息 进行调用
10. return invocationHandler.invoke(createRemoteInvocation(methodInvocation));
11. }
RemoteInvocation定义
Java代码
1. public class RemoteInvocation implements Serializable {
2.
3. private String methodName;
4.
5. private Class[] parameterTypes;
6.
7. private Object[] arguments;
8.
9. private Map attributes;
10.
11. public RemoteInvocation(MethodInvocation methodInvocation) {
12. this.methodName = methodInvocation.getMethod().getName();
13. this.parameterTypes = methodInvocation.getMethod().getParameterTypes();
14. this.arguments = methodInvocation.getArguments();
15. }
16. }
总结一下:
- 标准的Rmi 即实现了jdk Remote接口的 直接使用反射机制调用
- 非标准的Rmi spring暴露服务时包装成自己的对象[RmiInvocationHandler] 当客户段调用的时候 被拦截器拦截 封装方法名 参数等信息 最后调用RmiInvocationHandler的invoke方法 invoke方法类似中转站(泛化调用) 只要非标准Rmi 方法调用都会经过它调用目标方法。
- Spring对RMI的支持果然很不错,在Cglib等工具的支持下,使用RMI终于可以同Naming、rmic和stub告别了。
- 用以发布RMI的接口不能从java.rmi.Remote继承而来,否则就会出现“Stub class not found”的错误,原因有待深究。
- Spring的BeanFactory创建bean实例是有序的,向RMI、JNDI、WebService等注册服务性质的应用,同一应用中的客户端要根据其依赖性调整配置顺序。