web应用的项目:分表现层portal端和业务服务层service端,使用dubbo框架,rpc使用hessian。dubbo里的dubbo协议由于是socket长链接能够attachment隐式带参数,hessian协议则不能attachment参数。现有需求须要在每一个服务调用都多传一个参数,因而考虑不在改动接口的状况下,也在hessian协议下进行隐式传参。web
同事说hessian也是走http,能够在发起请求的时候把参数添加到http头信息,服务端接收的时候再解析出来。还有就是hack一下hessian的二进制协议在序列化和反序列化的时候作点手脚。因而源码看起、google百度相关的hessian源码搭配更快地看懂源码。途中就找在远在2007年的一个帖子:Hessian源码分析和Hack --让Hessian携带远程调用端的信息,里面就说到隐式传参的解决方案。因此本文不算原创。app
在Hessian客户端HessianProxy调用方法发送请求中添加http头信息:
public Object invoke(Object proxy, Method method, Object []args)
-->
protected HessianConnection sendRequest(String methodName, Object []args)
-->
protected void addRequestHeaders(HessianConnection conn)
里添加头信息框架
protected void addRequestHeaders(HessianConnection conn) { conn.addHeader("Content-Type", "x-application/hessian"); conn.addHeader("Accept-Encoding", "deflate"); //从ThreadLocal里获取参数 Map<String,String> context=HessianContext.getContext(); if(context!=null) { for(Map.Entry<String, String> entry:context.entrySet()) conn.addHeader(entry.getKey(), entry.getValue()); } String basicAuth = _factory.getBasicAuth(); if (basicAuth != null) conn.addHeader("Authorization", basicAuth); }
2.在dubbo的HessianProtocol中,skeleton.invoke(request.getInputStream(), response.getOutputStream());
前提取http头信息socket
private class HessianHandler implements HttpHandler { public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String uri = request.getRequestURI(); HessianSkeleton skeleton = skeletonMap.get(uri); if (! request.getMethod().equalsIgnoreCase("POST")) { response.setStatus(500); } else { RpcContext.getContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort()); try { Enumeration paramName=request.getHeaderNames(); String name; Map<String,String> data=new HashMap<String, String>(); while(paramName.hasMoreElements()) { name=(String)paramName.nextElement(); data.put(name, request.getHeader(name)); } HessianContext.setContext(data); skeleton.invoke(request.getInputStream(), response.getOutputStream()); HessianContext.removeContext(); } catch (Throwable e) { throw new ServletException(e); } } } }
3.这种用法须要同时升级hessian和dubbo包,可是不破坏hessian协议能够和原有的hessian兼容。在portal端调用service端方法先后须要HessianContext.setContext(...)
和HessianContext.removeContext()
,service端用HessianContext.getContext()
获取参数。源码分析
hessian的HessianOutput和Hessian2Output在HessianProxy的sendRequest方法里用call方法拼接请求body。在call方法的拼接参数后再write一个参数google
public void call(String method, Object []args) throws IOException { int length = args != null ? args.length : 0; startCall(method, length); for (int i = 0; i < length; i++) writeObject(args[i]); //再write一个参数 writeObject(HessianContext.getContext()); completeCall(); }
在HessianSkeleton里invoke(Object service,AbstractHessianInput in,AbstractHessianOutput out)
里读取参数后再读取一个参数code
for (int i = 0; i < args.length; i++) { // XXX: needs Marshal object values[i] = in.readObject(args[i]); } HessianContext.setContext((Map<String, Object>) in.readObject()); Object result = null; try { result = method.invoke(service, values); } catch (Exception e) { Throwable e1 = e;
这个方案只需升级hessian,可是破坏hessian的二进制规则,不能兼容原有的hessian,须要全部项目都升级,且不能外部使用。接口
HessianContext用ThreadLocal存放参数,代码简单直接上代码:rem
public class HessianContext { private static final ThreadLocal<HessianContext> _THREADLOCAL = new ThreadLocal<HessianContext>(); private Map<String,Object> data; private HessianContext(){} public static void setContext(Map<String,Object> data) { if(data==null) return; HessianContext context=_THREADLOCAL.get(); if(context==null) { context=new HessianContext(); _THREADLOCAL.set(context); } context.data=data; } public static Map<String,Object> getAndRemoveContext() { Map<String,Object> data=getContext(); removeContext(); return data; } public static Map<String,Object> getContext() { HessianContext context=_THREADLOCAL.get(); return context==null?null:context.data; } public static void removeContext() { _THREADLOCAL.remove(); } }