我們到底能走多遠系列(40)
扯淡:
判斷是否加可以效力於這家公司,一個很好的判斷是,接觸下這公司工作幾年的員工,了解下生活工作狀態,這就是你幾年后的狀態,如果滿意就可以考慮加入了。
主題:
場景:項目A作為主項目,業務實現完整,項目B需要調用項目A中的部分服務,那么項目A就需要提供出服務出來。實現分布式調用的方法有很多,這里介紹一下利用Spring Http Invoker 來實現的服務提供和調用。
demo地址:摸我
如果你對快速用springmvc搭建web應用感興趣:摸我
閱讀Spring的源碼基本的流程實現是這樣的:
下面就詳細分析一下這個流程的實現:
<bean id="remoteDemoService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"> <property name="serviceUrl"> <value>http://localhost:7777/remote/demoService</value> </property> <property name="serviceInterface"> <value>com.witown.open.demo.remote.service.DemoService</value> </property> </bean>
首先看的是HttpInvokerProxyFactoryBean類,它繼承FactoryBean,所以先來看一看這個FactoryBean。
public class HttpInvokerProxyFactoryBean extends HttpInvokerClientInterceptor implements FactoryBean<Object> { // 代理對象 private Object serviceProxy; // 初始化后執行方法 @Override public void afterPropertiesSet() { super.afterPropertiesSet(); if (getServiceInterface() == null) { throw new IllegalArgumentException("Property 'serviceInterface' is required"); } // 使用AOP代理工廠創建代理對象,對這個代理對象的所有方法調用最后都會被攔截 // 調用接口的任何一個方法時都會被攔截去變成http請求 this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader()); } // 獲得bean方法 public Object getObject() { return this.serviceProxy; } public Class<?> getObjectType() { return getServiceInterface(); } public boolean isSingleton() { return true; } }
這樣DemoService 的調用都會被HttpInvokerClientInterceptor攔截,攔截的方法調用會去執行HttpInvokerClientInterceptor中的invoke方法,這個方法明了的體現了客戶端的執行流程的三個步驟:
public Object invoke(MethodInvocation methodInvocation) throws Throwable { if (AopUtils.isToStringMethod(methodInvocation.getMethod())) { return "HTTP invoker proxy for service URL [" + getServiceUrl() + "]"; } // 1,把方法執行信息封裝成RemoteInvocation RemoteInvocation invocation = createRemoteInvocation(methodInvocation); RemoteInvocationResult result = null; try { // 2,發起http請求,把方法執行信息傳過去,正常的話,服務器會返回結果 result = executeRequest(invocation, methodInvocation); } catch (Throwable ex) { throw convertHttpInvokerAccessException(ex); } try { // 3,解析返回的結果,轉化成java對象 return recreateRemoteInvocationResult(result); } catch (Throwable ex) { if (result.hasInvocationTargetException()) { throw ex; } else { throw new RemoteInvocationFailureException("Invocation of method [" + methodInvocation.getMethod() + "] failed in HTTP invoker remote service at [" + getServiceUrl() + "]", ex); } } }
public class RemoteInvocation implements Serializable { //方法名 private String methodName; // 類 private Class[] parameterTypes; // 參數 private Object[] arguments; // 步驟1中實際就是調用了這個構造函數而已 public RemoteInvocation(MethodInvocation methodInvocation) { this.methodName = methodInvocation.getMethod().getName(); this.parameterTypes = methodInvocation.getMethod().getParameterTypes(); this.arguments = methodInvocation.getArguments(); } // ... }
public final RemoteInvocationResult executeRequest(HttpInvokerClientConfiguration config, RemoteInvocation invocation) throws Exception { // RemoteInvocation(遠程調用對象),轉成可以傳輸的OutputStream,以便寫入http請求中 ByteArrayOutputStream baos = getByteArrayOutputStream(invocation); if (logger.isDebugEnabled()) { logger.debug("Sending HTTP invoker request for service at [" + config.getServiceUrl() + "], with size " + baos.size()); } // 將流寫入http請求,發送請求,並接收響應 return doExecuteRequest(config, baos); }
繼續追蹤getByteArrayOutputStream方法,發現使用了ObjectOutputStream最終調用了writeObject方法來將對象轉化成流,下面是一段ObjectOutputStream的描述:
protected RemoteInvocationResult doExecuteRequest( HttpInvokerClientConfiguration config, ByteArrayOutputStream baos) throws IOException, ClassNotFoundException { // 打開遠程的HTTP連接 HttpURLConnection con = openConnection(config); // 設置HTTP連接信息 prepareConnection(con, baos.size()); // 把准備好的序列化的遠程方法調用對象的字節流寫入到HTTP請求體中 writeRequestBody(config, con, baos); // 校驗HTTP響應 validateResponse(config, con); // 獲得HTTP相應體的流對象 InputStream responseBody = readResponseBody(config, con); // 讀取遠程調用結果對象並返回 return readRemoteInvocationResult(responseBody, config.getCodebaseUrl()); }
最后的讀取遠程調用結果對象並返回:
protected RemoteInvocationResult doReadRemoteInvocationResult(ObjectInputStream ois) throws IOException, ClassNotFoundException { // 獲得Object Object obj = ois.readObject(); if (!(obj instanceof RemoteInvocationResult)) { throw new RemoteException("Deserialized object needs to be assignable to type [" + RemoteInvocationResult.class.getName() + "]: " + obj); } // 服務端對返回的對象封裝成RemoteInvocationResult! return (RemoteInvocationResult) obj; }
到這里基本完成了,客戶端請求遠程對象方法的流程。
<servlet> <servlet-name>remote</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:/config/remote-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>remote</servlet-name> <url-pattern>/remote/*</url-pattern> </servlet-mapping>
remote-servlet.xml文件中配置了處理請求的service:
< bean name= "/demoService" class ="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter" > < property name= "service" ref = "demoServiceImpl"/> < property name= "serviceInterface" value ="com.witown.open.demo.service.DemoService" /> </bean >
HttpInvokerServiceExporter繼承了HttpRequestHandler,所以請求從handleRequest方法開始。
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { // 解析請求,獲得RemoteInvocation RemoteInvocation invocation = readRemoteInvocation(request); // 根據RemoteInvocation里的信息:方法,參數,執行,把返回的結果封裝成RemoteInvocationResult RemoteInvocationResult result = invokeAndCreateResult(invocation, getProxy()); // 把結果寫入響應 writeRemoteInvocationResult(request, response, result); } catch (ClassNotFoundException ex) { throw new NestedServletException("Class not found during deserialization", ex); } }
內部調用的代碼經過前面請求調用發起的流程學習,就會很好理解了。
至此,整個流程就完整了。
讓我們繼續前行
----------------------------------------------------------------------
努力不一定成功,但不努力肯定不會成功。