Spring HttpInvoker 從實戰到源碼追溯

   Spring HttpInvoker 做爲 Spring 家族中老牌遠程調用模型 (RPC 框架),深受開發者喜好。git

   其主要目的是來執行基於 HTTP 的遠程調用(輕鬆穿越防火牆),並使用標準的 JDK 序列化機制。web

   Http 遠程調用框架不是有成熟的 Hessian、Burlap嘛,Spring 團隊爲何還要重複造輪子呢? spring

   由於它們有各自的序列化方式,首先沒法保證統一和規範性,其次沒法保證序列化比較複雜的數據類型。api

   但 Spring HttpInvoker 因與 Spring 難捨難分,沒法跨平臺也沒法跨語言,服務和客戶端必須得使用 Spring。tomcat

   僅從上手程度來講,Spring HttpInvoker 優於其餘的服務框架,因此有利有弊,權衡者在你。服務器

   本文試從項目實例入手描述 Spring HttpInvoker 的使用,在進行源碼分析帶你瞭解底層 Java 技術。app

1.項目實戰

   

   maven 依賴 spring-web 便可, 上圖爲實例工程分爲 server 服務模塊、 api 接口模塊。框架

   api 模塊打包方式爲 jar,其中定義接口和傳遞的業務實體, server 模塊打包方式爲 war,編寫業務服務實現。maven

   接口定義以下:ide

public interface UserService {


    /**
     * 經過ID獲取用戶
     *
     * @param uuid 用戶ID
     * @return 用戶實體
     */
    User getUserById(String uuid);
}

   接口返回的業務實體屬性,還需你根據具體業務拿捏,實現類:

public class UserServiceImpl implements UserService {

    @Override
    public User getUserById(String uuid) {
        User user = new User();
        user.setUuid(uuid);
        user.setName("Orson");
        user.setPasswd("xyxy");
        user.setSex("F");
        user.setPhone("13974856211");
        user.setPhoto("/photo/user/xyxy.gif");
        user.setEmail("954875698@qq.com");
        user.setCreateBy("orson");
        return user;
    }
}

   Spring 配置服務以下:

    <bean id="userServiceImpl" class="com.rambo.httpinvoker.server.impl.UserServiceImpl" />

    <bean id="userServiceInvoker" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
        <property name="service" ref="userServiceImpl" />
        <property name="serviceInterface" value="com.rambo.httpinvoker.api.UserService" />
    </bean>

    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/userService">userServiceInvoker</prop>
            </props>
        </property>
    </bean>

   web.xml 配置:

    <servlet>
        <servlet-name>service</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-httpinvoke-server.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>service</servlet-name>
        <url-pattern>/service/*</url-pattern>
    </servlet-mapping>

   配置 tomcat 或 jetty 啓動服務模塊,這時服發佈成功,是否是很簡單?

   客戶端將 api 依賴進去,spring 稍作下配置就能夠在客戶端中使用對應的服務。

    <!-- 客戶端使用 HttpInvokerProxyFactoryBean 代理客戶端向服務器端發送請求,請求接口爲 UserService 的服務 -->
    <bean id="userService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
        <property name="serviceUrl" value="http://${server.url}/service/userService"/>
        <property name="serviceInterface" value="com.rambo.httpinvoker.api.UserService"/>
    </bean>

   demo 項目地址:https://gitee.com/LanboEx/rmi-demo.git

2.源碼分析

   源碼分析時從客戶端和服務端配置兩個對象 HttpInvokerServiceExporter、HttpInvokerProxyFactoryBean下手。

   HttpInvokerServiceExporter 繼承 HttpRequestHandler 並實現 handleRequest 方法。

    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            RemoteInvocation invocation = this.readRemoteInvocation(request);
            RemoteInvocationResult result = this.invokeAndCreateResult(invocation, this.getProxy());
            this.writeRemoteInvocationResult(request, response, result);
        } catch (ClassNotFoundException var5) {
            throw new NestedServletException("Class not found during deserialization", var5);
        }
    }

   首先從 http 請求中讀取遠程調用對象,而後調用服務對應方法並組織執行結果,最後將執行結果寫入到 http 返回。   

   這幾個過程你追溯到底層代碼,你會發現 Java ObjectInputStream 反序列化對象、Java Method 反射對象。

    HttpInvokerProxyFactoryBean 實現 FactoryBean 接口並繼承 HttpInvokerClientInterceptor,spring ioc 託管該類並初始化對應屬性後返回該類代理。

    public void afterPropertiesSet() {
        super.afterPropertiesSet();
        if (this.getServiceInterface() == null) {
            throw new IllegalArgumentException("Property 'serviceInterface' is required");
        } else {
            this.serviceProxy = (new ProxyFactory(this.getServiceInterface(), this)).getProxy(this.getBeanClassLoader());
        }
    }

   注意獲取代理類時傳入的攔截器參數爲 this 即爲父類 HttpInvokerClientInterceptor。

   該攔截器 invoke 方法首先進行遠程調用對象的封裝,其次發起遠程服務請求,最後解析返回結果並封裝返回。

   追溯這幾個過程的時候你會看到,cgb 代理攔截器 MethodInterceptor、Java 序列對象 ObjectOutputStream、Java Http 鏈接對象 HttpURLConnection。

   HttpInvoker 調優時也記得去關注上述幾個對象:https://blog.csdn.net/qian_348840260/article/details/51555864

    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
            return "HTTP invoker proxy for service URL [" + this.getServiceUrl() + "]";
        } else {
            RemoteInvocation invocation = this.createRemoteInvocation(methodInvocation);

            RemoteInvocationResult result;
            try {
                result = this.executeRequest(invocation, methodInvocation);
            } catch (Throwable var7) {
                RemoteAccessException rae = this.convertHttpInvokerAccessException(var7);
                throw (Throwable)(rae != null ? rae : var7);
            }
            return this.recreateRemoteInvocationResult(result);
        }
    }

   從服務暴露到服務調用,debug 源碼過來底層老是那些熟悉的面孔,只不過 Spring 團隊作了出色的封裝和合理的抽象。

   至此全文結束,文中若有紕漏,還望斧正。

相關文章
相關標籤/搜索