Spring HttpInvoke,一種較為常用的、基于Spring架構的伺服器之間的遠端調用實作,可以說是輕量級的RMI。
最初,我們使用Spring HttpInvoke同步配置資料,重新整理多個伺服器上的緩存,當然如果用分布式緩存是不是更好 !
使用Spring HttpInvoke,你可以調用遠端接口,進行資料互動、業務邏輯操作等等。
廢話不說了,上代碼!
使用者操作接口:
Java代碼
/**
* @since 1.0
*/
public interface UserService {
/**
* 獲得使用者
*
* @param username
* 使用者名
* @return
*/
User getUser(String username);
}
使用者類,注意實作Serializable接口,這是執行遠端調用傳遞資料對象的第一要求——資料對象必須實作Serializable接口,因為,要執行序列化/反序列化操作!
Java代碼
/**
* @since 1.0
*/
public class User implements Serializable {
private static final long serialVersionUID = 5590768569302443813L;
private String username;
private Date birthday;
/**
* @param username
* @param birthday
*/
public User(String username, Date birthday) {
this.username = username;
this.birthday = birthday;
}
// 省略
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("%s\t%s\t", username, birthday);
}
}
覆寫toString()方法,輸出使用者資訊!
再看UserServiceImpl實作:
Java代碼
/**
* @since 1.0
*/
public class UserServiceImpl implements UserService {
private Logger logger = Logger.getLogger(UserServiceImpl.class);
/*
* (non-Javadoc)
*
* @see
* org.zlex.spring.httpinvoke.service.UserService#getUser(java.lang.String)
*/
@Override
public User getUser(String username) {
if (logger.isDebugEnabled()) {
logger.debug("username:[" + username + "]");
}
User user = new User(username, new Date());
if (logger.isDebugEnabled()) {
logger.debug("user:[" + user + "]");
}
return user;
}
}
隻把使用者資訊打出來即可說明調用效果!
看applicationContext.xml
Xml代碼
<bean
id="userService"
class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property
name="service">
<bean
class="org.zlex.spring.httpinvoke.service.impl.UserServiceImpl" />
</property>
<property
name="serviceInterface"
value="org.zlex.spring.httpinvoke.service.UserService" />
</bean>
我們要把userService暴露出去,這樣外部就可以通過http接口調用這個接口的實作了。
說說HttpInvokerServiceExporter,這個類用來在伺服器端包裝需要暴露的接口。
屬性service,定義具體的實作類!
Xml代碼
<property
name="service">
<bean
lass="org.zlex.spring.httpinvoke.service.impl.UserServiceImpl" />
</property>
屬性serviceInterface指向需要暴露的接口,注意使用value标注接口名稱!
Xml代碼
<property
name="serviceInterface"
value="org.zlex.spring.httpinvoke.service.UserService" />
最後再看service-servlet.xml配置
Xml代碼
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property
name="urlMap">
<map>
<entry
key="userTest"
value-ref="userService" />
</map>
</property>
</bean>
直接将請求指向剛才配置的userService
現在我們之間通路一下http://localhost:8080/spring/service/
[點選檢視原始大小圖檔]
這就說明,伺服器端配置已經成功了!如果在日志中頻繁得到這種異常,那很可能伺服器被惡意通路了!
再看用戶端實作:
Java代碼
/**
* @since 1.0
*/
public class UserServiceTest {
private Logger logger = Logger.getLogger(UserServiceTest.class);
private ApplicationContext context;
private UserService userService;
@Before
public void initialize() {
context = new ClassPathXmlApplicationContext("applicationContext.xml");
userService = (UserService) context.getBean("userService");
}
@Test
public void getUser() {
User user = userService.getUser("zlex");
if (logger.isDebugEnabled()) {
logger.debug("user[" + user + "]");
}
}
}
我們做了什麼?Nothing!就跟調用一般Spring容器中的實作一樣!
再看applicationContext.xml:
Xml代碼
<bean
id="userService"
class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property
name="serviceUrl"
value="http://localhost:8080/spring/service/userTest" />
<property
name="serviceInterface"
value="org.zlex.spring.httpinvoke.service.UserService" />
</bean>
這裡我們可以通過Spring容器調用userService,而實際上,他是一個HttpInvokerProxyFactoryBean,在這個配置裡,定義了通路位址serviceUrl,和通路接口serviceInterface。
執行測試!
[點選檢視原始大小圖檔]
如果我們這樣寫,其實預設調用了SimpleHttpInvokerRequestExecutor做實作,這個實作恐怕隻能作為示範來用!
這也是效率問題所在!!!
為提高效率,應該通過Commons-HttpClient!
我們需要做什麼?導入這個jar,改改xml就行!
Xml代碼
<bean
id="userService"
class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property
name="serviceUrl"
value="http://localhost:8080/spring/service" />
<property
name="serviceInterface"
value="org.zlex.spring.httpinvoke.service.UserService" />
<property
name="httpInvokerRequestExecutor">
<ref
bean="httpInvokerRequestExecutor" />
</property>
</bean>
<bean
id="httpInvokerRequestExecutor" class="org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor">
<property
name="httpClient">
<bean
class="org.apache.commons.httpclient.HttpClient">
<property
name="connectionTimeout"
value="2000" />
<property
name="timeout"
value="5000" />
</bean>
</property>
</bean>
通過HttpClient,我們可以配置逾時時間timeout和連接配接逾時connectionTimeout兩個屬性,這樣,伺服器執行操作時,如果逾時就可以強行釋放連接配接,這樣可憐的tomcat不會因為HttpInvoke連接配接不釋放而被累死!
回頭看了一眼我N多年前的代碼,萬歲,我當時确實是這麼實作的!好在沒有犯低級錯誤!!!
執行操作!
[點選檢視原始大小圖檔]
這時,轉為org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor實作了!
不過同僚認為,這個效率還是不夠高!!!
再改,改什麼?還是xml!
Xml代碼
<bean
id="httpInvokerRequestExecutor"
class="org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor">
<property
name="httpClient">
<bean
class="org.apache.commons.httpclient.HttpClient">
<property
name="connectionTimeout"
value="2000" />
<property
name="timeout"
value="5000" />
<property
name="httpConnectionManager">
<ref
bean="multiThreadedHttpConnectionManager" />
</property>
</bean>
</property>
</bean>
<bean
id="multiThreadedHttpConnectionManager"
class="org.apache.commons.httpclient.MultiThreadedHttpConnectionManager">
<property
name="params">
<bean
class="org.apache.commons.httpclient.params.HttpConnectionManagerParams">
<property
name="maxTotalConnections"
value="600" />
<property
name="defaultMaxConnectionsPerHost"
value="512" />
</bean>
</property>
</bean>
改用MultiThreadedHttpConnectionManager,多線程!!!
測試就不說了,實踐證明:
預設實作,伺服器平均10s左右才能響應一個請求。
多線程實作,伺服器平均20ms左右響應一個請求。
這簡直不是一個數量級!!!
注意:在HttpClient的3.1版本中,已不支援如下配置,相應的方法已經廢棄!
Xml代碼
<property
name="connectionTimeout"
value="2000" />
<property
name="timeout"
value="5000" />
如果仔細看看文檔,
引用
HttpClient that uses a default MultiThreadedHttpConnectionManager.
commons 系列的實作怎麼會不考慮多線程呢?人家預設實作就是多線程的!同僚多慮了!
當然,同僚還補充了一句,需要控制連接配接數!
難怪,這裡要設定
Xml代碼
<bean
class="org.apache.commons.httpclient.params.HttpConnectionManagerParams">
<property
name="maxTotalConnections"
value="600" />
<property
name="defaultMaxConnectionsPerHost"
value="512" />
</bean>
預設啥情況?
引用
maxConnectionsPerHost 每個主機的最大并行連結數,預設為2
public static final int DEFAULT_MAX_HOST_CONNECTIONS = 2;
maxTotalConnections 用戶端總并行連結最大數,預設為20
public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 20;
--以下是藝術家補充
增加一個service-servlet.xm(對應urlMap)l,放在web-inf目錄下
web.xml中配置
<servlet>
<servlet-name>service</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>service</servlet-name>
<url-pattern>/service/*</url-pattern>
</servlet-mapping>