服務調用方案(Spring Http Invoker) - 我們到底能走多遠系列(40)


我們到底能走多遠系列(40)

扯淡:

   判斷是否加可以效力於這家公司,一個很好的判斷是,接觸下這公司工作幾年的員工,了解下生活工作狀態,這就是你幾年后的狀態,如果滿意就可以考慮加入了。

 

主題:

  場景:項目A作為主項目,業務實現完整,項目B需要調用項目A中的部分服務,那么項目A就需要提供出服務出來。實現分布式調用的方法有很多,這里介紹一下利用Spring Http Invoker 來實現的服務提供和調用。

  demo地址:摸我

  如果你對快速用springmvc搭建web應用感興趣:摸我

  

  閱讀Spring的源碼基本的流程實現是這樣的:  

下面就詳細分析一下這個流程的實現:

 

1,服務請求流程分析
對遠程服務的配置例子如下:
 <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。

在spring中可以利用FactoryBean來產生一些自定義配置的bean。
 
如果一個類繼承 FactoryBean 用戶可以自己定義產生實例對象的方法只要實現他的 getObject 方法。然而在 Spring 內部這個 Bean 的實例對象是 FactoryBean,通過調用這個對象的 getObject 方法就能獲取用戶自定義產生的對象,正是這樣獲得bean的機制,我們可以在getObject 方法中自定義一些操作,從而為 Spring 提供了很好的擴展性。
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);
               }
          }
     }

 

step 1,MethodInvocation是客戶端調用的方法,spring的AOP實現了這個接口,第一步就是吧這個對象轉變成RemoteInvocation,它是Serializable 的一邊后面對這個對象的序列化,RemoteInvocation封裝了什么:
 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();
          }
          // ...
     }

 

step 2,調用鏈下去會調用到AbstractHttpInvokerRequestExecutor的executeRequest方法,這個方法又分成兩步:1,將遠程調用信息對象轉成流,2,將流寫入http請求
  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的描述:

     Java中ObjectInputStream 與 ObjectOutputStream這兩個包裝類可用於輸入流中讀取對象類數據和將對象類型的數據寫入到底層輸入流 。ObjectInputStream 與 ObjectOutputStream 類所讀寫的對象必須實現了 Serializable 接口。需要注意的是: 對象中的 transient 和 static 類型的成員變量不會被讀取和寫入  。
 
轉換好之后得到的是ByteArrayOutputStream ,此類實現了一個輸出流,其中的數據被寫入一個 byte 數組。緩沖區會隨着數據的不斷寫入而自動增長。可使用 toByteArray()和 toString()獲取數據。
 
將流寫入http請求,發送請求,並接收響應:
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;
     }

 

到這里基本完成了,客戶端請求遠程對象方法的流程。

 

2,服務提供者流程分析
 
既然對方用http請求,那作為spring,自然是會給予Spring mvc來實現的啦。
從例子的配置中就可以看到:攔截所有/remote/*請求,來調用服務。
<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);
          }
     }

內部調用的代碼經過前面請求調用發起的流程學習,就會很好理解了。
至此,整個流程就完整了。

 

牽涉知識點,有興趣可以擴展學習:
1,spring FactoryBean
2,spring aop攔截機制
3,序列化
4,java發起http請求
 
 
總結:spring的這個封裝很不錯,在實際項目中使用方便,研讀源碼時可以遇到一些常用的知識領域,會有收獲!
 
 
 
 
 

 

讓我們繼續前行

----------------------------------------------------------------------

努力不一定成功,但不努力肯定不會成功。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM