Spring源碼分析總結——HttpInvoker

該文章基於《Spring源碼深度解析》撰寫,感謝郝佳老師的奉獻

HttpInvoker

因爲RMI使用標準的Java標準的對象私有化,很難穿越防火牆,但是Hessian/Burlap因爲是基於HTTP的服務卻能很好的穿越防火牆。
在這種情況下,Spring的HttpInvoker應運而生,基於HTTP的遠程調用並且使用Java序列化機制,現在給出小Demo:
首先其代碼結構如下
代碼架構
下面給出源代碼

<!--web.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/remoting-servlet.xml</param-value>
    </context-param>


    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Test httpInvoker configuration -->
    <servlet>
        <servlet-name>remoting</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>remoting</servlet-name>
        <url-pattern>/remoting/*</url-pattern>
    </servlet-mapping>
</web-app>
<!--remoting-servlet.xml-->
<?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.0.xsd">
    <bean name="/hit" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
        <property name="service" ref="httpinvokertest"/>
        <property name="serviceInterface" value="HttpInvoker.HttpInvokerTestI"/>
    </bean>
    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/hit">/hit</prop>

            </props>
        </property>
    </bean>
    <bean name="httpinvokertest" class="HttpInvoker.Impl.HttpInvokertestImpl"/>
</beans>
<!--client.xml-->
<?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.0.xsd">
    <bean id="remoteService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
        <property name="serviceUrl" value="http://localhost:8080/remoting/hit"/>
        <property name="serviceInterface" value="HttpInvoker.HttpInvokerTestI"/>
    </bean>
</beans>
/*HttpInvokerTestI.java*/
package HttpInvoker;

public interface HttpInvokerTestI {
    public String getTestPo(String desp);
}
/*HttpInvokertestImpl.java*/
package HttpInvoker.Impl;

import HttpInvoker.HttpInvokerTestI;

public class HttpInvokertestImpl implements HttpInvokerTestI {
    @Override
    public String getTestPo(String desp) {
        return "getTestPo"+desp;
    }
}
/*Test.java*/
import HttpInvoker.HttpInvokerTestI;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args){
        ApplicationContext context = new ClassPathXmlApplicationContext("client.xml");
        HttpInvokerTestI httpInvokerTestI = (HttpInvokerTestI)context.getBean("remoteService");
        System.out.println(httpInvokerTestI.getTestPo("work"));
    }
}

首先啓動服務器,然後運行Test文件,可以看見如下結果:
這裏寫圖片描述

服務器端實現

根據remote-servlet.xml中我們可以看見HttpInvokerServiceExporter應該就是服務器端實現的重要類,其類層次結構如下:
HttpInvokerServiceExporter類層次
通過觀察其類層次結構可以看到幾個我們關注的接口:InitializingBean(用於初始化Bean時調用afterpropertiesSet方法)HttpRequestHandler(當有請求時,通過調用handleRequest方法進行處理)

首先從InitializingBean的afterpropertiesSet開始進行分析:在初始化時,通過JDK的方式創建了代理,創建代理的主要目的時加入RemoteInvocationTraceInterceptor增強器,主要用於對增強對象的目標方法進行日誌的打印

然後分析HttpRequestHandler的handleRequest,該過程的調用比較複雜:
(1)從request中讀取序列化信息,主要是從HttpServletRequest中提取RemoteInvocation對象的序列化以及反序列化的過程
(2)執行調用該方法比較重要直接給出源代碼:

protected RemoteInvocationResult invokeAndCreateResult(RemoteInvocation invocation, Object targetObject) {
    try {
        Object value = this.invoke(invocation, targetObject);
        return new RemoteInvocationResult(value);
    } catch (Throwable var4) {
        return new RemoteInvocationResult(var4);
    }
}

該方法有兩個需要說明的地方:1.this.invoke(invocation, targetObject);在調用時話考慮代理類targetObject的增強方法;2.返回的結果統一使用RemoteInvocationResult進行封裝,原因是因爲無法保證所有結果都能夠直接進行序列化
(3)將結果的序列化對象寫入輸出流

客戶端實現

服務器端一直提到的RemoteInvocation,是從HttpServletRequest中提取出來的,所以我們對於客戶端的分析,如何創建RemoteInvocation將成爲一個非常重要的問題,但是我們還是先鎖定HttpInvokerProxyFactoryBean,查看其類層次結構:
HttpInvokerProxyFactoryBean類層次結構
幾個重要的常見的接口就不再進行介紹了,下面仍然針對InitalizingBean接口分析初始化過程中的邏輯,HttpInvokerProxyFactoryBean在初始化的過程中創建了封裝服務接口的代理,並使用自身作爲攔截器類,又因爲實現了FactoryBean接口,所以獲取Bean的時候實際上獲取的是創建的代理,而在調用相應方法時實際調用的是代理類中的服務方法,所以將會使用增強器進行增強,所以現在談到的問題就是調用的問題,HttpInvokerProxyFactoryBean的invoke的邏輯如下:
(1)構建RemoteInvocation實例,因爲是代理中增強方法的調用,所以當進入invoke方法時,實際上MethodInvocation中已經包含了調用的接口的相關信息,所以我們首先需要將這一部分的消息提取出來,並構建RemoteInvocation實例
(2)遠程執行方法該邏輯比較複雜,下面是它的執行步驟:
1.將調用方法的實例寫入輸出流中
2.創建HttpPost,因爲對服務端方法的調用是通過Post方法進行的,所以需要先創建HttpPost
3.設置RequestBody並將序列化流加入到postMethod中
4.執行遠程方法,通過HttpClient所提供的方式來直接執行遠程方法
5.根據返回的響應碼進行驗證
6.提取返回的輸入流對於信息的處理可能存在差別,不同的方式採用不同的辦法進行提取
7.提取返回結果
(3)提取結果從服務器端的實現可知,實際上返回的結果類型爲RemoteInvocationResult,所以我們還需要對結果類型進行拆封

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章