Hessian,rcp,有状态,模拟会话,ThreadLocal,AOP

http://www.iteye.com/topic/82492




本帖是工作成果之总结发贴,又由于太长,故有关心hessian/burlap/RCP如何在客户端和服务端之间默认传送参数,使服务器端的对象能够使用ThreadLocal获得当前远程请求者信息的同学 可以看看这个帖子,其它同学可以忽略之  否则看这这么长的贴可能会很受罪 


背景  
在远程调用框架方面,Spring现在能够良好集成Hessian/Burlap之类基于Http的RCP。 构建这样的程序基本步骤是这样的: 
1,创建并配置POJO形式的服务对象,比如UserBo; 
2、远程调用暴露器,比如Spring提供的Hessian暴露器,使成为web服务; 
3、客户端(e.g.Swing-based)通过proxy,按照UserBo的接口调用远程服务 

问题  
区别于浏览器通过http访问Web服务器,浏览器能够支持它和服务器端cookie传送, 
使本没有状态的http协议在web上能够提供有状态的服务,web容器因此可以通过标准的HttpSession保存登录信息等。 

而这是一般使用Hessian之类的RCP难以做到的,如何模拟浏览器传送"cookie"成为头痛的问题。我一直为这件事烦恼着,做了这几个试验: 
1、Hessian客户端能够保持服务器送过来的Cookie吗?   不能。试验过程如下: 
1)做一个Filter过滤器,过滤Hessian远程调用 
2)获取request.getSession(),打印sessionId 
通过打印,看出每一次调用打印的sessionId都不一样,所以试图直接使用HttpSession保持会话是不行的。 

现在已经没办法通过HttpSession机制处理这个问题,那 
1、如何确定每次从swing到server的hessian调用是登录后的调用,而不会被人模拟调用? 
2、我如何知道每次调用的调用者是谁? 

头脑风暴  
头脑中一下子蹦出来的是:每个调用方法都加一个userId参数。 

“搞定!”but极其ugly,想想,一般情况下,我不希望Bo提供的每一个方法都要求加一个UserId,我不会。 
如果想要知道当前是由谁调用这个Bo类,在普通的web应用上,我采用的是Filter+ThreadLocal来做的, 
这样只需在Bo方法里面调用提供ThreadLocal服务的全局共享对象来获取当前的请求者。 

我不会在Bo中加入userId参数的,除非万不得已。。。继续苦思冥想.... 

是否能够改变Hessian客户端代理,使其能够读取http头信息,并在每次发送新请求时再把读取的cookie送到服务器?我想这是个不错的方案,但汗了一下,同上,也只要在“需求极其有必要+没有其它办法”条件下才去改变别人的底层结构。暂行搁置此方案。

有了,借助了“网络协议栈”提供的编程思想+AOP,我想到了一个方法,并在进行了试验性实践,证明可行了 
因为可能也有人需要: 《如何在hessian的环境下模拟session(会话)》 , 或者可以用来参考拓展一下编程思路,所以我写这个总结。。。花费了我近2个小时,快晕死了 


终极解决-思路  
1、协议栈思想: 
协议栈告诉我们下层协议总是“偷偷”的在上层协议内容之外再加上一些额外的,和本层协议有关的内容,发送到网络另外一端,同时另外那一端的对等协议,接收传输内容时,会剥离刚才加入的额外信息进行控制,并把剥离后的协议内容往上一层推。。。。 

2、AOP理论: 
拦截器,就像土匪,在你行走的路上,硬要对你进行盘问,满意则放人通过,不满意可以把你潜回;它可以偷袭你一些数据,也可能悄悄地加入他想要的行为、数据 

3、协议栈思想 + AOP 
客户端调用某个Bo的方法时,配置代理bean给他,这个代理bean同时也是拦截器,客户端调用Bo的每个方法都会被拦截,去调用一个底层接口(Delegate)。这个底层接口,能把你要调用的Bo名称,方法名,参数记起来,并加上每次你都需要传给服务器的参数传送到服务器。同时在服务器端,有一个这个底层接口个对应的服务对象(由DelegateImpl实现),该实现会把拦截器刚才记住的这些信息找出来: 
取回拦截器刚才悄悄加入的信息(Attachments)记录在一个和ThreadLocal相关的类中(由Current实现); 
取回你要调的Bo名称,从服务器applicationContext中找到这个对象 

通过方法名和参数找到最终要调用的Method对象,最终调用method.invoke,实现对服务端Bo的调用。 

终极解决 - 编程代码展示 
1、UserBo(仅为示例用,没有考虑接口设计是否完全合理): 

Java代码   收藏代码
  1. public interface UserBo {  
  2.     public User login(String name, String password);  
  3.     public void updateProfile(String signature);  
  4. }  


2、客户端登录代码 
Java代码   收藏代码
  1. User user = userBo.login("wang""xxxxxx");  
  2. SimpleAttachments attachments = (SimpleAttachments )getBean("attachments");//SimpleAttachments 是这个方案中的一个接口,只有一对get/set方法;getBean指的是从client端的applicationContext.xml读  
  3. attachments.set(new Object[]{user.getId(), user.getToken(), user.getLastLoginTime()});  


3、客户端修改资料代码 
Java代码   收藏代码
  1. userBo.updateProfile("签名是用来做什么的,有什么意义?");  


4、客户端applicationContext.xml片段 
Java代码   收藏代码
  1. <bean id="remoteProxyBean" abstract="true" class="xxx.RemoteProxyBean">  
  2.     <property name="delegate">  
  3.         <bean  
  4.             class="org.springframework.remoting.caucho.HessianProxyFactoryBean">  
  5.             <property name="serviceUrl" value="${remoting}delegate" />  
  6.             <property name="serviceInterface"  
  7.                 value="xxx.Delegate" />  
  8.         </bean>  
  9.     </property>  
  10.     <property name="attachments" ref="attachments"></property>  
  11. </bean>  
  12.   
  13. <bean id="attachments" class = "xxx.SimpleAttachements">  
  14. </bean>  
  15.   
  16. <bean id="userBo" parent="remoteProxyBean">  
  17.     <property name="serviceInterface" value="xxx.UserBoImpl" />  
  18. </bean>  


5、服务端UserBo实现 
Java代码   收藏代码
  1. public User login(String name, String password){  
  2.     User user = userDao.getByName(name);  
  3.     if (user != null && user.getPassword().equals(password))  {  
  4.          user.setLastLoginTime(new Date());  
  5.          user.setToken("随机生成的类似session id的一个字符串,和userId,当前时间有关");  
  6.          return user;  
  7.     }  
  8.     return null;  
  9. }  
  10.   
  11. public void updateProfile(String signature) {  
  12.     Object[] attachements = Current.getAttachements();//注意这个Current类  
  13.      //attachements==null的情况,我们可以通过配置AOP拦截掉,使这里的attachements不会为null  
  14.     Long userId = attachements[0];  
  15.     //attachements不合法,比如Token被恶意改写的情况,同样可以配置AOP或Filter在调用本方法之前就验证了,这里我们就认为是合法的便可以  
  16.     User user = userDao.getById(userId);  
  17.     user.setSignature(signature);  
  18. }  


6、服务端remoting-servlet.xml配置片段 
Java代码   收藏代码
  1. <bean name="/delegate"  
  2.     class="org.springframework.remoting.caucho.HessianServiceExporter">  
  3.     <property name="service" ref="delegate" />  
  4.     <property name="serviceInterface" value="xxx.Delegate" />  
  5. </bean>  
  6.   
  7. <bean name="delegate" class="xxx.DelegateImpl" />  

7、服务端applicationContext-bo.xml配置片段 
<bean id="userBo" class="xxx.UserBoImpl" autowire="byName"/> 

终极解决-底层架构代码展示 
1、Current代码 

Java代码   收藏代码
  1. public abstract class Current {  
  2.     private static final ThreadLocal<Object[]> data = new ThreadLocal<Object[]>();  
  3.   
  4.     public static void setAttachemnts(Object[] attachments) {  
  5.         data.set(attachments);  
  6.     }  
  7.   
  8.     public static Object[] getAttachments() {  
  9.         return data.get();  
  10.     }  
  11. }  

2、Deletegate接口 
Java代码   收藏代码
  1. public interface Delegate {  
  2.   
  3.     public Object call(String targetBeanName, String methodName, Object[] args,  
  4.             Object[] attachments);  
  5. }  


3、DelegateImpl代码 
Java代码   收藏代码
  1. public class DelegateImpl implements Delegate, ApplicationContextAware {  
  2.     // Spring上下文  
  3.     private ApplicationContext applicationContext;  
  4.   
  5.     // 缓存从applicationContext得到的bean  
  6.     private Map<String, Object> beans = new HashMap<String, Object>();  
  7.   
  8.     private static final Log log = LogFactory.getLog(Delegate.class);  
  9.   
  10.     public void setApplicationContext(ApplicationContext applicationContext)  
  11.             throws BeansException {  
  12.         this.applicationContext = applicationContext;  
  13.     }  
  14.   
  15.     public ApplicationContext getApplicationContext() {  
  16.         return applicationContext;  
  17.     }  
  18.   
  19.     public Object call(String serviceBeanName, String methodName,  
  20.             Object[] args, Object[] attachments) {  
  21.         if (log.isDebugEnabled()) {  
  22.             StringBuilder sb = new StringBuilder();  
  23.             sb.append("-------------------------------call delegate for ")  
  24.                     .append(serviceBeanName).append('.').append(methodName)  
  25.                     .append("\n attchments: ");  
  26.             for (Object a : attachments) {  
  27.                 sb.append(a).append(", ");  
  28.             }  
  29.             sb.setLength(sb.length() - 2);  
  30.             log.debug(sb.toString());  
  31.         }  
  32.         if (attachments != null) {  
  33.             Current.setAttachemnts(attachments);//!!这里把attachments设置到Current  
  34.         }  
  35.         Object bo = getBean(serviceBeanName);  
  36.         Class<?>[] argClasses = new Class[args.length];  
  37.         for (int i = 0; i < argClasses.length; i++) {  
  38.             argClasses[i] = args[i].getClass();  
  39.         }  
  40.         Method method = null;  
  41.         try {  
  42.             method = bo.getClass().getMethod(methodName, argClasses);  
  43.         } catch (Exception e) {  
  44.             throw new InvocationFailureException(e.getMessage(), e);  
  45.         }  
  46.         if (log.isDebugEnabled()) {  
  47.             log.debug("found method named '" + methodName + "' of bean "  
  48.                     + serviceBeanName);  
  49.         }  
  50.         Object ret = null;  
  51.         try {  
  52.             ret = method.invoke(bo, args);  
  53.         } catch (IllegalArgumentException e) {  
  54.             throw new InvocationFailureException(e.getMessage(), e);  
  55.         } catch (IllegalAccessException e) {  
  56.             throw new InvocationFailureException(e.getMessage(), e);  
  57.         } catch (InvocationTargetException e) {  
  58.             throw new InvocationFailureException(e.getMessage(), e);  
  59.         }  
  60.         if (log.isDebugEnabled()) {  
  61.             log.debug("successfully completed invocation for "  
  62.                     + serviceBeanName + '.' + methodName);  
  63.         }  
  64.         return ret;  
  65.     }  
  66.   
  67.     protected Object getBean(String serviceBeanName) {  
  68.         Object bean = beans.get(serviceBeanName);  
  69.         if (bean == null) {  
  70.             bean = applicationContext.getBean(serviceBeanName);  
  71.             beans.put(serviceBeanName, bean);  
  72.         }  
  73.         return bean;  
  74.     }  
  75.   
  76. }  


4、RemoteProxyBean代码 
Java代码   收藏代码
  1. public class RemoteProxyBean implements MethodInterceptor, InitializingBean,  
  2.         FactoryBean, ApplicationContextAware {  
  3.   
  4.     // 业务对象接口,比如com.xxx.bo.UserBo  
  5.     private Class serviceInterface;  
  6.   
  7.     // 业务对象在服务器中的名称,比如userBo,由本类从applicationContext自动读入(配置本类的bean标识)  
  8.     private String serviceBeanName;  
  9.   
  10.     // 暴露给客户端使用的代理对象,以serviceInterface的名义  
  11.     private Object serviceProxy;  
  12.   
  13.     // 服务端暴露的远程代理接口  
  14.     private Delegate delegate;  
  15.   
  16.     // Spring上下文  
  17.     private ApplicationContext applicationContext;  
  18.   
  19.     // 附加信息来源  
  20.     private Attachments attachments;  
  21.   
  22.     //attachments,delegate,applicationContext,serviceInterface getter/setters here  
  23.   
  24.     public Object getObject() throws Exception {  
  25.         return this.serviceProxy;  
  26.     }  
  27.   
  28.     public Class getObjectType() {  
  29.         return getServiceInterface();  
  30.     }  
  31.   
  32.     public boolean isSingleton() {  
  33.         return true;  
  34.     }  
  35.   
  36.     public void afterPropertiesSet() {  
  37.         readServiceBeanName();  
  38.         this.serviceProxy = ProxyFactory.getProxy(getServiceInterface(), this);  
  39.     }  
  40.   
  41.     protected void readServiceBeanName() {  
  42.         String[] names = applicationContext  
  43.                 .getBeanNamesForType(RemoteProxyBean.class);  
  44.         for (String name : names) {  
  45.             Object maybe = applicationContext.getBean(name);  
  46.             if (maybe == this) {  
  47.                 this.serviceBeanName = name;  
  48.                 break;  
  49.             }  
  50.         }  
  51.         if (this.serviceBeanName == null) {  
  52.             throw new IllegalArgumentException();  
  53.         }  
  54.         //Spring会对FacotryBean的名称前面将&,我们要把它去掉  
  55.         int index = this.serviceBeanName.indexOf('&');  
  56.         this.serviceBeanName = this.serviceBeanName.substring(index + 1);  
  57.     }  
  58.   
  59.     public Object invoke(MethodInvocation invocation) throws Throwable {  
  60.         Object [] attachments = null;  
  61.         if (this.attachments != null) {  
  62.             attachments = this.attachments.get();  
  63.         }  
  64.         return delegate.call(serviceBeanName, invocation.getMethod().getName(),  
  65.                 invocation.getArguments(), attachments);  
  66.     }  
  67.   
  68. }  


5、Attachments接口 
Java代码   收藏代码
  1. public interface Attachments {  
  2.   
  3.     public Object[] get();  
  4. }  


6、SimpleAttachments 
Java代码   收藏代码
  1. /** 
  2.  * 最简单的实现,用于单用户的客户端。 
  3.  * 多用户的客户端,应该另外实现。 
  4.  *  
  5.  * @author zhiliang.wang 
  6.  * 
  7.  */  
  8. public class SimpleAttachments implements Attachments {  
  9.       
  10.     private Object[] values;  
  11.   
  12.     public Object[] get() {  
  13.         return values;  
  14.     }  
  15.   
  16.     public void set(Object[] values) {  
  17.         this.values = values;  
  18.     }  
  19.   
  20. }  


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值