遠程服務HttpInvoker源碼解析(四)服務端實現

Spring開發小組意識到在RMI服務和基於HTTP的服務(如Hessian如Burlap)之間的空白。一方面,RMI使用Java標準的對象序列化,很難穿越防火牆;另一方面,Hessian/Burlap能很好地穿過防火牆工作,但使用自己私有的一套對象序列化機制。

就這樣,Spring的HttpInvoker應運而生。HttpInvoker是一個新的遠程調用模型,作爲Spring框架的一部分,來執行基於HTTP的遠程調用(讓防火牆高興的事),並使用Java的序列化機制(這是讓程序員高興的事)。

我們首先看看HttpInvoker的使用示例。HttpInvoker是基於HTTP的遠程調用,同時也是使用Spirng中提供的web服務作爲基礎,所以我們的測試需要首先搭建web工程。

關於HttpInvoker的使用示例有興趣的朋友可以去網上搜索,很簡單,下面我們從源碼解析開始分析


服務端實現


對於Spring中HttpInvoker服務的實現,我們還是首先從服務端進行分析。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
	<bean name="httpinvokertest" class="org.tarena.note.service.impl.HttpInvokertestImpl"/>
	
	<bean name="/hit" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
		<property name="service" ref="httpinvokertest"/>
		<property name="serviceInterface" value="org.tarena.note.service.HttpInvokerTest1"/>
	</bean>
</beans>


根據xml中的配置,我們分析入口類應該爲

org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter
,那麼同樣,根據這個函數進行分析:

public class HttpInvokerServiceExporter extends RemoteInvocationSerializingExporter
    implements HttpRequestHandler
RemoteInvocationSerializingExporter類實現了接口InitializingBean接口,本類實現了HttpRequestHandler接口。分析RMI服務時我們已經瞭解到了,當某個bean繼承自InitializingBean接口的時候,Spring會確保這個bean在初始化時調用其afterPropertiesSet方法,而對於HttpRequestHandler接口,因爲我們在配置中已經將此接口配置成Web服務,那麼當有相應請求的時候,Spring的Web服務就會將程序引導至HttpRequestHandler的handlerRequest方法中。首先我們從afterPropertiesSet方法開始分析,看看在bean的初始化過程中做了那些邏輯。

1.創建代理

    public void afterPropertiesSet()
    {
        prepare();
    }

    public void prepare()
    {
        proxy = getProxyForService();
    }

    protected Object getProxyForService()
    {
        //驗證service
       checkService();
        //驗證serviceInterface
       checkServiceInterface();
        //使用JDK的方式創建代理
       ProxyFactory proxyFactory = new ProxyFactory();
        //添加代理接口
       proxyFactory.addInterface(getServiceInterface());
        if(registerTraceInterceptor == null ? interceptors == null : registerTraceInterceptor.booleanValue())
            proxyFactory.addAdvice(new RemoteInvocationTraceInterceptor(getExporterName()));
        if(interceptors != null)
        {
            AdvisorAdapterRegistry adapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();
            for(int i = 0; i < interceptors.length; i++)
                 //加入代理的橫切面RemoteInvocationTraceInterceptor並記錄Exporter名稱
                proxyFactory.addAdvisor(adapterRegistry.wrap(interceptors[i]));

        }
        //設置要代理的目標類
       proxyFactory.setTarget(getService());
        proxyFactory.setOpaque(true);
        //創建代理
       return proxyFactory.getProxy(getBeanClassLoader());
    }

通過將上面3個方法串聯,可以看到,初始化過程中實現的邏輯主要是創建了一個代理,代理中封裝了對於特定請求的處理方法以及接口等信息,而這個代理的最關鍵目的是加入了RemoteInvocationTraceInterceptor增強器,當然創建代理還有些其他好處,比如代碼優雅,方便擴展等。RemoteInvocationTraceInterceptor中的增強主要是對增強的目標方法進行一些相關信息的日誌打印,並沒有在此基礎上進行任何功能性的增強。那麼這個代理究竟是在什麼時候使用的呢?暫時留下懸念,我們接下來分析當有Web請求時HttpRequestHandler的handleRequest方法的處理

2.處理來自客戶端的request

當有web請求時,根據配置中的規則會把路徑匹配的訪問直接引入對應的HttpRequestHandler中。本例中的Web請求時有區別的,因爲此處的請求包含着HttpInvoker的處理過程。

    public void handleRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        try
        {
            //從request中讀取序列化對象
           RemoteInvocation invocation = readRemoteInvocation(request);
            //執行調用
           RemoteInvocationResult result = invokeAndCreateResult(invocation, getProxy());
            //將結果的序列化對象寫入輸出流
           writeRemoteInvocationResult(request, response, result);
        }
        catch(ClassNotFoundException ex)
        {
            throw new NestedServletException("Class not found during deserialization", ex);
        }
    }

在handlerRequest函數中,我們很清楚地看到了HttpInvoker處理的大致框架,HttpInvoker服務簡單點說就是將請求的方法,也就是RemoteInvocation對象,從客戶端序列化並通過Web請求出入服務端,服務端在對傳過來的序列化對象進行反序列化還原RemoteInvocation實例,然後通過實例中的相關信息進行相關方法的調用,並將執行結果再次的返回給客戶端。從handlerRequest函數中我們也可以清晰地看到程序執行的框架結構。

(1)從request中讀取序列化對象

主要是從HttpServletRequest提取相關的信息,也就是提取HttpServletRequest中的RemoteInvocation對象的序列化信息以及反序列化的過程。

    protected RemoteInvocation readRemoteInvocation(HttpServletRequest request)
        throws IOException, ClassNotFoundException
    {
        return readRemoteInvocation(request, ((InputStream) (request.getInputStream())));
    }

   protected RemoteInvocation readRemoteInvocation(HttpServletRequest request, InputStream is)
        throws IOException, ClassNotFoundException
    {
        try{
       ObjectInputStream ois = createObjectInputStream(decorateInputStream(request, is));
        RemoteInvocation remoteinvocation = doReadRemoteInvocation(ois);
        }finally{
        <pre name="code" class="java">           ois.close();

} }

    protected RemoteInvocation doReadRemoteInvocation(ObjectInputStream ois)
        throws IOException, ClassNotFoundException
    {
        Object obj = ois.readObject();
        if(!(obj instanceof RemoteInvocation))
            throw new RemoteException((new StringBuilder()).append("Deserialized object needs to be assignable to type [").append(org/springframework/remoting/support/RemoteInvocation.getName()).append("]: ").append(obj).toString());
        else
            return (RemoteInvocation)obj;
    }
對於序列化提取與轉換過程其實並沒有太多需要解釋的東西,這裏完全是按照標準的方式進行操作,包括創建ObjectInputStream以及從ObjectInputStream中提取對象實例。

(2)執行調用。

根據反序列化方式得到的RemoteInvocation對象中的信息,進行方法調用。注意,此時調用的實體並不是服務接口或者服務類,而是之前在初始化的時候構造的封裝了服務接口以及服務類的代理。

完成了RemoteInvocation實例的提取,也就意味着可以通過RemoteInvocation實例中提供的信息進行方法調用了。

    protected RemoteInvocationResult invokeAndCreateResult(RemoteInvocation invocation, Object targetObject)
    {
        try
        {
            //激活代理類中對應invacation中的方法
           Object value = invoke(invocation, targetObject);
            //封裝結果以便於序列化
           return new RemoteInvocationResult(value);
        }
        catch(Throwable ex)
        {
            return new RemoteInvocationResult(ex);
        }
    }

  • 對應方法的激活也就是invoke方法的調用,雖然經過層層環繞,但是最終還是實現了一個我們熟知的調用invocation.invoke(targetObject),也就是執行RemoteInvocation類中的invoke方法,大致的邏輯還是通過RemoteInvocation中對應的方法信息在targetObject上去執行,此方法在分析RMI功能的時候已經分析過,不再贅述。但是在對於當前方法的targetObject參數,此targetObject是代理類,調用代理類的時候需要考慮增強方法的調用,這是讀者需要注意的地方。
  • 對於返回結果需要使用RemoteInvocationResult進行封裝,之所以需要通過使用RemoteInvocationResult類進行封裝,是因爲無法保證對於所有操作的返回結果都繼承Serializable接口,也就是說無法保證所有返回結果都可以直接進行序列化。那麼,就必須使用RemoteInvocationResult類進行統一封裝。

(3)將結果的序列化對象寫入輸出流

同樣這裏也包括結果的序列化過程。

    protected void writeRemoteInvocationResult(HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result)
        throws IOException
    {
        response.setContentType(getContentType());
        writeRemoteInvocationResult(request, response, result, ((OutputStream) (response.getOutputStream())));
    }

    protected void writeRemoteInvocationResult(HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result, OutputStream os)
        throws IOException
    {
        //獲取輸出流
       ObjectOutputStream oos = createObjectOutputStream(decorateOutputStream(request, response, os));
        try{
        //將序列化對象寫入輸出流
       doWriteRemoteInvocationResult(result, oos);
        }finally{
        oos.close();
        }
 }
    protected void doWriteRemoteInvocationResult(RemoteInvocationResult result, ObjectOutputStream oos)
        throws IOException
    {
        oos.writeObject(result);
    }




發佈了53 篇原創文章 · 獲贊 14 · 訪問量 27萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章