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(); } }