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(仅为示例用,没有考虑接口设计是否完全合理):
- public interface UserBo {
- public User login(String name, String password);
- public void updateProfile(String signature);
- }
2、客户端登录代码
- User user = userBo.login("wang", "xxxxxx");
- SimpleAttachments attachments = (SimpleAttachments )getBean("attachments");//SimpleAttachments 是这个方案中的一个接口,只有一对get/set方法;getBean指的是从client端的applicationContext.xml读
- attachments.set(new Object[]{user.getId(), user.getToken(), user.getLastLoginTime()});
3、客户端修改资料代码
- userBo.updateProfile("签名是用来做什么的,有什么意义?");
4、客户端applicationContext.xml片段
- <bean id="remoteProxyBean" abstract="true" class="xxx.RemoteProxyBean">
- <property name="delegate">
- <bean
- class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
- <property name="serviceUrl" value="${remoting}delegate" />
- <property name="serviceInterface"
- value="xxx.Delegate" />
- </bean>
- </property>
- <property name="attachments" ref="attachments"></property>
- </bean>
- <bean id="attachments" class = "xxx.SimpleAttachements">
- </bean>
- <bean id="userBo" parent="remoteProxyBean">
- <property name="serviceInterface" value="xxx.UserBoImpl" />
- </bean>
5、服务端UserBo实现
- public User login(String name, String password){
- User user = userDao.getByName(name);
- if (user != null && user.getPassword().equals(password)) {
- user.setLastLoginTime(new Date());
- user.setToken("随机生成的类似session id的一个字符串,和userId,当前时间有关");
- return user;
- }
- return null;
- }
- public void updateProfile(String signature) {
- Object[] attachements = Current.getAttachements();//注意这个Current类
- //attachements==null的情况,我们可以通过配置AOP拦截掉,使这里的attachements不会为null
- Long userId = attachements[0];
- //attachements不合法,比如Token被恶意改写的情况,同样可以配置AOP或Filter在调用本方法之前就验证了,这里我们就认为是合法的便可以
- User user = userDao.getById(userId);
- user.setSignature(signature);
- }
6、服务端remoting-servlet.xml配置片段
- <bean name="/delegate"
- class="org.springframework.remoting.caucho.HessianServiceExporter">
- <property name="service" ref="delegate" />
- <property name="serviceInterface" value="xxx.Delegate" />
- </bean>
- <bean name="delegate" class="xxx.DelegateImpl" />
7、服务端applicationContext-bo.xml配置片段
<bean id="userBo" class="xxx.UserBoImpl" autowire="byName"/>
终极解决-底层架构代码展示
1、Current代码
- public abstract class Current {
- private static final ThreadLocal<Object[]> data = new ThreadLocal<Object[]>();
- public static void setAttachemnts(Object[] attachments) {
- data.set(attachments);
- }
- public static Object[] getAttachments() {
- return data.get();
- }
- }
2、Deletegate接口
- public interface Delegate {
- public Object call(String targetBeanName, String methodName, Object[] args,
- Object[] attachments);
- }
3、DelegateImpl代码
- public class DelegateImpl implements Delegate, ApplicationContextAware {
- // Spring上下文
- private ApplicationContext applicationContext;
- // 缓存从applicationContext得到的bean
- private Map<String, Object> beans = new HashMap<String, Object>();
- private static final Log log = LogFactory.getLog(Delegate.class);
- public void setApplicationContext(ApplicationContext applicationContext)
- throws BeansException {
- this.applicationContext = applicationContext;
- }
- public ApplicationContext getApplicationContext() {
- return applicationContext;
- }
- public Object call(String serviceBeanName, String methodName,
- Object[] args, Object[] attachments) {
- if (log.isDebugEnabled()) {
- StringBuilder sb = new StringBuilder();
- sb.append("-------------------------------call delegate for ")
- .append(serviceBeanName).append('.').append(methodName)
- .append("\n attchments: ");
- for (Object a : attachments) {
- sb.append(a).append(", ");
- }
- sb.setLength(sb.length() - 2);
- log.debug(sb.toString());
- }
- if (attachments != null) {
- Current.setAttachemnts(attachments);//!!这里把attachments设置到Current
- }
- Object bo = getBean(serviceBeanName);
- Class<?>[] argClasses = new Class[args.length];
- for (int i = 0; i < argClasses.length; i++) {
- argClasses[i] = args[i].getClass();
- }
- Method method = null;
- try {
- method = bo.getClass().getMethod(methodName, argClasses);
- } catch (Exception e) {
- throw new InvocationFailureException(e.getMessage(), e);
- }
- if (log.isDebugEnabled()) {
- log.debug("found method named '" + methodName + "' of bean "
- + serviceBeanName);
- }
- Object ret = null;
- try {
- ret = method.invoke(bo, args);
- } catch (IllegalArgumentException e) {
- throw new InvocationFailureException(e.getMessage(), e);
- } catch (IllegalAccessException e) {
- throw new InvocationFailureException(e.getMessage(), e);
- } catch (InvocationTargetException e) {
- throw new InvocationFailureException(e.getMessage(), e);
- }
- if (log.isDebugEnabled()) {
- log.debug("successfully completed invocation for "
- + serviceBeanName + '.' + methodName);
- }
- return ret;
- }
- protected Object getBean(String serviceBeanName) {
- Object bean = beans.get(serviceBeanName);
- if (bean == null) {
- bean = applicationContext.getBean(serviceBeanName);
- beans.put(serviceBeanName, bean);
- }
- return bean;
- }
- }
4、RemoteProxyBean代码
- public class RemoteProxyBean implements MethodInterceptor, InitializingBean,
- FactoryBean, ApplicationContextAware {
- // 业务对象接口,比如com.xxx.bo.UserBo
- private Class serviceInterface;
- // 业务对象在服务器中的名称,比如userBo,由本类从applicationContext自动读入(配置本类的bean标识)
- private String serviceBeanName;
- // 暴露给客户端使用的代理对象,以serviceInterface的名义
- private Object serviceProxy;
- // 服务端暴露的远程代理接口
- private Delegate delegate;
- // Spring上下文
- private ApplicationContext applicationContext;
- // 附加信息来源
- private Attachments attachments;
- //attachments,delegate,applicationContext,serviceInterface getter/setters here
- public Object getObject() throws Exception {
- return this.serviceProxy;
- }
- public Class getObjectType() {
- return getServiceInterface();
- }
- public boolean isSingleton() {
- return true;
- }
- public void afterPropertiesSet() {
- readServiceBeanName();
- this.serviceProxy = ProxyFactory.getProxy(getServiceInterface(), this);
- }
- protected void readServiceBeanName() {
- String[] names = applicationContext
- .getBeanNamesForType(RemoteProxyBean.class);
- for (String name : names) {
- Object maybe = applicationContext.getBean(name);
- if (maybe == this) {
- this.serviceBeanName = name;
- break;
- }
- }
- if (this.serviceBeanName == null) {
- throw new IllegalArgumentException();
- }
- //Spring会对FacotryBean的名称前面将&,我们要把它去掉
- int index = this.serviceBeanName.indexOf('&');
- this.serviceBeanName = this.serviceBeanName.substring(index + 1);
- }
- public Object invoke(MethodInvocation invocation) throws Throwable {
- Object [] attachments = null;
- if (this.attachments != null) {
- attachments = this.attachments.get();
- }
- return delegate.call(serviceBeanName, invocation.getMethod().getName(),
- invocation.getArguments(), attachments);
- }
- }
5、Attachments接口
- public interface Attachments {
- public Object[] get();
- }
6、SimpleAttachments
- /**
- * 最简单的实现,用于单用户的客户端。
- * 多用户的客户端,应该另外实现。
- *
- * @author zhiliang.wang
- *
- */
- public class SimpleAttachments implements Attachments {
- private Object[] values;
- public Object[] get() {
- return values;
- }
- public void set(Object[] values) {
- this.values = values;
- }
- }