Spring HTTP invoker 是 spring 框架中的一個遠程調用模型,執行基於 HTTP 的遠程調用(意味着能夠經過防火牆),並使用 java 的序列化機制在網絡間傳遞對象。這須要在遠端和本地都使用Spring才行。客戶端能夠很輕鬆的像調用本地對象同樣調用遠程服務器上的對象,這有點相似於 webservice ,但又不一樣於 webservice ,區別以下:html
WebService | Http Invoker |
跨平臺,跨語言 | 只支持 java 語言 |
支持 SOAP ,提供 wsdl | 不支持 |
結構龐大,依賴特定的 webservice 實現,如 xfire等 | 結構簡單,只依賴於 spring 框架自己 |
說明:java
1. 服務器端:經過 HTTP invoker 服務將服務接口的某個實現類提供爲遠程服務web
2. 客戶端:經過 HTTP invoker 代理向服務器端發送請求,遠程調用服務接口的方法spring
3. 服務器端與客戶端通訊的數據均須要序列化服務器
先來個項目結構圖:網絡
1). 添加 springJAR 文件,這就不用說了,直接照着圖片添加相應的類庫。app
2). 建立服務接口和相應的DTO(Data Transmission Object)框架
這裏咱們須要調用遠端的服務來查詢一個User對象,所以須要DTO啦。下面這個User類就是用於在網絡中傳輸的POJO類,也就是DTO啦,所以須要實現Serializable接口:ide
package com.abc.invoke.bean; import java.io.Serializable; public class User implements Serializable { private static final long serialVersionUID = -6970967506712260305L; private String name; private int age; private String email; //Getter and Setter @Override public String toString() { return "User [name=" + name + ", age=" + age + ", email=" + email + "]"; } }
3). UserService是一個接口,裏面定義了服務的方法,這裏面的方法將會被客戶端調用:函數
package com.abc.invoke.server.service; import com.abc.invoke.bean.User; public interface UserService { public User getUserbyName(String name); }
4). 建立服務接口的具體實現類。這裏的UserServiceImpl是實現了UserService方法:
package com.abc.invoke.server.service.impl; import com.abc.invoke.bean.User; import com.abc.invoke.server.service.UserService; public class UserServiceImpl implements UserService { public User getUserbyName(String name) { User u = new User(); u.setName(name); u.setEmail("abc@abc.com"); u.setAge(20); return u; } }
這裏面我沒有寫DAO等層面的東西,由於那些不是這篇文章要講述的內容,於是我只是簡單的將傳給服務端的參數封裝到對象裏的一個字段就返回了。
5). 公開服務
下面是web.xml文件的內容:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <display-name>SpringInvoke</display-name> <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:service-servlet.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> <!-- 其實下面這個welcome-file-list沒啥用,我留着只是爲了在起好Tomcat後不會報一個404而已 --> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app>
這裏咱們使用/service做爲service的前綴,那麼客戶端請求調用時須要加上這個前綴,好比:
http://{host}:{port}/InvokeServer/service/{serviceName}
裏面用到的service-servlet文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- 這個Bean映射了當URL是/userService時,處理器爲userServiceInvoker --> <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/userService">userServiceInvoker</prop> </props> </property> </bean> <!-- Announce that this interface is a HTTP invoker service. --> <bean id="userServiceInvoker" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"> <property name="service" ref="userServiceImpl" /> <property name="serviceInterface" value="com.abc.invoke.server.service.UserService" /> </bean> <bean id="userServiceImpl" class="com.abc.invoke.server.service.impl.UserServiceImpl" /> </beans>
注意:
<prop key="/userService">userServiceInvoker</prop>中的/userService是請求的服務的URL中的一部分,就是說這樣的URL會被userServiceInvoker處理
這裏將com.abc.invoke.server.service.UserService映射給了com.abc.invoke.server.service.impl.UserServiceImpl類了。
到此爲止,服務器算是配置好了,接下來開始配置客戶端。
先來看看項目結構圖:
1). 添加 springJAR 文件,這也不用說了,直接照着圖片添加相應的類庫。
2). 建立服務接口和相應的DTO。
特別注意:這個類和Server端聲明的DTO要同樣,包名和字段名都要同樣才行。由於客戶端發起請求查詢User,服務端處理後先將User序列化後在返回給客戶端,而客戶端拿到這個User後須要將其反序列化。若是包名或者字段名不一樣,則會被認爲是不一樣的對象,會反序列化失敗,調用也就出錯了。我以前就是將User類的包名寫得不同(User類的包名在服務端爲com.abc.invoke.server.bean,而在客戶端則爲com.abc.invoke.client.bean),報瞭如下錯誤:
Exception in thread "main" org.springframework.remoting.RemoteAccessException: Could not deserialize result from HTTP invoker remote service [http://localhost:8080/InvokeServer/service/userService]; nested exception is java.lang.ClassNotFoundException: com.abc.invoke.server.bean.User at org.springframework.remoting.httpinvoker.HttpInvokerClientInterceptor.convertHttpInvokerAccessException(HttpInvokerClientInterceptor.java:208) at org.springframework.remoting.httpinvoker.HttpInvokerClientInterceptor.invoke(HttpInvokerClientInterceptor.java:145) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202) at com.sun.proxy.$Proxy0.getUserbyName(Unknown Source) at com.abc.invoke.client.Test.main(Test.java:14)
很明顯能夠看出,Could not deserialize result from HTTP invoker remote service......,就是由於Server端與Client端的DTO的包名不一樣致使反序列化失敗。
3). 建立服務接口
這也沒啥好說的,接口和Server端定義的同樣就行,不同確定報錯。能夠直接將DTO和接口定義的類拷貝到客戶端便可。這個接口將會被看作是客戶端和服務端通訊的「契約」。
4). 訪問服務
來看看application-context.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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- 客戶端使用 HttpInvokerProxyFactoryBean 代理客戶端向服務器端發送請求,請求接口爲 UserService 的服務 --> <bean id="userService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean" > <property name="serviceUrl" value="http://localhost:8080/InvokeServer/service/userService"/> <property name="serviceInterface" value="com.abc.invoke.client.service.UserService" /> </bean> </beans>
這裏使用了org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean這個類來做爲一個service的代理類。注意到serviceUrl屬性爲http://localhost:8080/InvokeServer/service/userService (固然,我在本機啓動的服務端並在本機經過main函數調用service,我在另外一臺機器上運行Test類的main函數,調用結果正確)。這個localhost:8080應改成實際的IP地址和端口。),這個URL的地址以/service開始,所以會被Server端攔截下來,而URL中的 /userService則爲service路徑,該路徑與在Server端中service-servlet.xml中聲明的
<prop key="/userService">userServiceInvoker</prop>
路徑一致,所以這個調用會被userServiceInvoker處理。
最後再寫一個簡單的測試類Test.java:
package com.abc.invoke.client; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.abc.invoke.bean.User; import com.abc.invoke.client.service.UserService; public class Test { public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext( "classpath:application-context.xml"); UserService service = (UserService)ac.getBean("userService"); User u = service.getUserbyName("Alvis"); System.out.println(u); } }
這個類也很簡單,就是從Spring的Context中取出了定義的userService這個Bean(這其實就是服務端service的一個代理類),而後直接調用該service的方法得到結果並打印。
到此爲止,客戶端配置完成。
直接在項目InvokeServer上啓動Tomcat,能夠看到路徑/userService的處理者是userServiceInvoker:
下面是遠程調用的執行結果:
從結果中能夠看到,我代碼裏寫的名字叫Alvis,用客戶端調用服務端的service後,返回的對象中名字是客戶端設置的名字,測試成功。
這裏是項目源代碼,供須要的朋友參考。
參考頁面:http://hanqunfeng.iteye.com/blog/868210