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
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
源碼分析時從客戶端和服務端配置兩個對象 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 團隊作了出色的封裝和合理的抽象。
至此全文結束,文中若有紕漏,還望斧正。