spring integration之http-rest例子解析 – JAVA編程語言程序開發技術文章

spring integration例子代碼下載地址,以及如何導入eclipse並使用maven構建等請參看上一篇:

Spring Integration實例代碼分析之basic–http

先看一下http-rest例子的readme
This sample demonstrates how you can send an HTTP request to a Spring Integration's HTTP service while utilizing Spring Integration's new HTTP Path usage;
This sample also uses Spring Security for HTTP Basic authentication. With HTTP Path facility, the client program can send requests with URL Variables.
It consists of two parts – Client and Server.
The following client program can be used to test the HTTP Path usage.
1. RestHttpClientTest. It uses Spring's RestTemplate to assemble and send HTTP request
Server is Spring Integration's HTTP endpoint configuration.

此示例演示瞭如何發送一個HTTP請求到一個Spring集成的HTTP服務,同時利用Spring Integration的新的HTTP Path;
此示例還使用瞭Spring HTTP基本身份驗證安全性。 HTTP Path,客戶端程序可以發送請求的URL變量。
它由兩部分組成 – 客戶端和服務器。
客戶端程序,可以用來測試使用HTTP path。RestHttpClientTest。它使用Spring的RestTemplate的組裝和發送HTTP請求
服務端是Spring集成的HTTP端點配置。
代碼結構

 

仍然先看web.xml
先是配置幾個變量,指定spring配置路徑和log4j的配置路徑
[html]
      <context-param> 
       <param-name>contextConfigLocation</param-name> 
       <param-value> 
            <!– Spring application context declaration –> 
             /WEB-INF/config/web-application-config.xml                
       </param-value> 
    </context-param> 
     
    <!– 
Key of the system property that should specify the root directory of this 
web app. Applied by WebAppRootListener or Log4jConfigListener. 
–> 
<context-param> 
    <param-name>webAppRootKey</param-name> 
    <param-value>rest-http.root</param-value> 
</context-param> 
  
    <!– 
Location of the Log4J config file, for initialization and refresh checks. 
Applied by Log4jConfigListener. 
–> 
<context-param> 
    <param-name>log4jConfigLocation</param-name> 
    <param-value>/WEB-INF/classes/log4j.properties</param-value> 
</context-param> 

然後配置spring的listener,用於啟動WebApplicationContext
[html] 
<listener> 
    <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> 
</listener> 
  
   <!– 
    – Loads the root application context of this web app at startup, 
    – by default from "/WEB-INF/applicationContext.xml". 
    – Note that you need to fall back to Spring's ContextLoaderServlet for 
    – J2EE servers that do not follow the Servlet 2.4 initialization order. 
    – 
    – Use WebApplicationContextUtils.getWebApplicationContext(servletContext) 
    – to access it anywhere in the web application, outside of the framework. 
    – 
    – The root context is the parent of all servlet-specific contexts. 
    – This means that its beans are automatically available in these child contexts, 
    – both for getBean(name) calls and (external) bean references. 
–> 
    <listener> 
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 
</listener> 

接著是filter,包含指定字符的,和security的,用於認證。
[html] 
<filter> 
            <filter-name>charEncodingFilter</filter-name> 
            <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> 
            <init-param> 
              <param-name>encoding</param-name> 
              <param-value>UTF-8</param-value> 
            </init-param> 
            <init-param> 
              <param-name>forceEncoding</param-name> 
              <param-value>true</param-value> 
            </init-param> 
        </filter> 
        <filter-mapping> 
            <filter-name>charEncodingFilter</filter-name> 
            <url-pattern>/*</url-pattern> 
        </filter-mapping> 
        <filter> 
            <filter-name>springSecurityFilterChain</filter-name> 
            <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 
        </filter> 
        <filter-mapping> 
            <filter-name>springSecurityFilterChain</filter-name> 
            <url-pattern>/*</url-pattern> 
        </filter-mapping> 

最後才是用於spring mvc的DispatcherServlet,指定配置文件為contextConfigLocation的 /WEB-INF/config/web-application-config.xml
[html] 
<servlet> 
            <servlet-name>Spring Integration Rest HTTP Path Usage</servlet-name> 
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 
            <init-param> 
              <param-name>contextConfigLocation</param-name> 
              <param-value></param-value> 
            </init-param> 
            <load-on-startup>2</load-on-startup> 
        </servlet> 
        <servlet-mapping> 
            <servlet-name>Spring Integration Rest HTTP Path Usage</servlet-name> 
            <url-pattern>/*</url-pattern> 
        </servlet-mapping> 

Spring 配置
先看:web-application-config.xml
這個配置裡引入瞭security-config.xml 和 applicationContext-http-int.xml兩個配置
並且使用context:component-scan指定自動掃描加載註解的位置。
[html] 
<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"> 
         
    <import resource="security-config.xml" /> 
    <import resource="classpath:META-INF/spring/integration/applicationContext-http-int.xml"/> 
     
    <context:component-scan base-package="org.springframework.integration.samples.rest"/> 
         
</beans> 

我們主要看一下applicationContext-http-int.xml
先配置一個int:annotation,查到其作用是Enables annotation support for Message Endpoints,也就是開啟消息端點的註解支持吧。
[html]
<int:annotation-config/> 
inbound gateway's 和 inbound adapter 都需要指定這個,用於其path屬性。
[html] 
<bean class="org.springframework.integration.http.inbound.UriPathHandlerMapping"/> 
接著指定兩個channel通道,一個用於請求一個用於響應。
[html] 
<!– Inbound/Outbound Channels –> 
<int:channel id="employeeSearchRequest" /> 
<int:channel id="employeeSearchResponse" /> 
下面就是gateway(能翻譯為網關麼??)瞭,使用http inbound-gateway來通過http接受信息,關聯上之前的兩個通道,指定path路徑,通過路徑解析出參數id.
[html]
<!– To receive messages over HTTP, you need to use an HTTP Inbound Channel Adapter or Gateway.  –> 
<int-http:inbound-gateway id="inboundEmployeeSearchRequestGateway"        
    supported-methods="GET, POST"  
    request-channel="employeeSearchRequest" 
    reply-channel="employeeSearchResponse"       
    mapped-response-headers="Return-Status, Return-Status-Msg, HTTP_RESPONSE_HEADERS"        
    view-name="/employee"  
    path="/services/employee/{id}/search" 
    reply-timeout="50000"> 
    <int-http:header name="employeeId" expression="#pathVariables.id"/> 
</int-http:inbound-gateway> 
配置一個ContentNegotiatingViewResolver,其作用主要是使用內容協商來實現多視圖,可以參考http://www.cnblogs.com/zhaoyang/archive/2012/01/07/2315428.html這個帖子,詳細講解。大意就是說使用它來支持返回json xml等多種內容形式。

其中用於將對象轉為xml的為marshaller,它的contextPath="org.springframework.integration.samples.rest.domain",就是對這個包下的進行轉換。
[html] 
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> 
        <property name="order" value="1" />    
        <property name="defaultContentType" value="application/xml"/> 
        <property name="favorParameter" value="true"/>     
        <property name="ignoreAcceptHeader" value="true" />        
        <property name="mediaTypes"> 
            <map> 
                <entry key="json" value="application/json" /> 
                <entry key="xml" value="application/xml" />                
            </map> 
        </property> 
        <property name="defaultViews"> 
            <list> 
                <bean 
                    class="org.springframework.integration.samples.rest.json.view.ExtendedMappingJacksonJsonView" > 
                    <property name="objectMapper" ref="jaxbJacksonObjectMapper"/> 
                </bean>    
                <bean class="org.springframework.web.servlet.view.xml.MarshallingView"> 
                    <constructor-arg ref="marshaller"/>                    
                </bean>                
            </list> 
        </property>                
    </bean> 
<!– 將java bean 轉換為xml string並利用ContentNegotiatingViewResolver來提供輸出 
As stated in the introduction, a marshaller serializes an object to XML, and an unmarshaller deserializes XML stream to an object. –> 
    <oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.integration.samples.rest.domain" /> 
     
    <bean id="jaxbJacksonObjectMapper" class="org.springframework.integration.samples.rest.json.JaxbJacksonObjectMapper"/>     

用於json的定義一個ExtendedMappingJacksonJsonView
MappingJacksonJsonView輸出會返回{model類名:{內容}} 的json格式, 例如期望的返回是 {success:true,message:”return ok”};
但實際返回的卻是 {"jsonResult":{"success":true,"msg":"return ok"}}
原因是MappingJacksonJsonView中對返回值的處理未考慮modelMap中隻有一個值的情況,直接是按照mapName:{mapResult}的格式來返回數據的。
修改方法如下代碼,重載MappingJacksonJsonView類並重寫filterModel方法,處理一下map.size為1的情況。
代碼如下:
[java] 
public class ExtendedMappingJacksonJsonView extends MappingJacksonJsonView { 
 
    @SuppressWarnings({"rawtypes" }) 
    @Override 
    protected Object filterModel(Map<String, Object> model){ 
        Object result = super.filterModel(model); 
        if (!(result instanceof Map)){ 
            return result; 
        } 
     
        Map map = (Map) result; 
        if (map.size() == 1){ 
            return map.values().toArray()[0];    
        } 
        return map; 
    } 

最後定義一個service-activator,關聯上兩個通道,並關聯employeeSearchService的getEmployee方法即可。
[html] 
<int:service-activator id="employeeServiceActivator"  
                input-channel="employeeSearchRequest" 
                output-channel="employeeSearchResponse"  
                ref="employeeSearchService"  
                method="getEmployee"  
                requires-reply="true"   
                send-timeout="60000"/> 

代碼解析
把配置看明白瞭,代碼就很簡單瞭,看下JAVA代碼,
Employee.java
[java]
@XmlAccessorType(XmlAccessType.FIELD) 
@XmlType(name = "", propOrder = { 
    "employeeId", 
    "fname", 
    "lname" 
}) 
@XmlRootElement(name = "Customer") 
public class Employee { 
 
    private Integer employeeId; 
    private String fname; 
    private String lname; 
     
    public Employee() {} 
     
    public Employee(Integer employeeId, String fname, String lname) { 
        this.employeeId = employeeId; 
        this.fname = fname; 
        this.lname = lname; 
    } 
        //get and set … 

EmployeeList
[java]
@XmlAccessorType(XmlAccessType.FIELD) 
@XmlType(name = "", propOrder = { 
    "employee", 
    "returnStatus", 
    "returnStatusMsg" 
}) 
@XmlRootElement(name = "EmployeeList") 
public class EmployeeList { 
     
    @XmlElement(name = "Employee", required = true) 
    private List<Employee> employee; 
     
    @XmlElement(name = "returnStatus", required = true) 
    private String returnStatus; 
     
    @XmlElement(name = "returnStatusMsg", required = true) 
    private String returnStatusMsg; 
 
    /**
     * @return the employee
     */ 
    public List<Employee> getEmployee() { 
        if (employee == null){ 
            employee = new ArrayList<Employee>(); 
        } 
        return employee; 
    } 

EmployeeSearchService
[java]
@Service("employeeSearchService") 
public class EmployeeSearchService { 
 
    private static Logger logger = Logger.getLogger(EmployeeSearchService.class); 
    /**
     * The API <code>getEmployee()</code> looks up the mapped in coming message header's id param
     * and fills the return object with the appropriate employee details. The return
     * object is wrapped in Spring Integration Message with response headers filled in.
     * This example shows the usage of URL path variables and how the service act on
     * those variables.
     * @param inMessage
     * @return an instance of <code>{@link Message}</code> that wraps <code>{@link EmployeeList}</code>
     */ 
    @Secured("ROLE_REST_HTTP_USER") 
    public Message<EmployeeList> getEmployee(Message<?> inMessage){ 
     
        EmployeeList employeeList = new EmployeeList(); 
        Map<String, Object> responseHeaderMap = new HashMap<String, Object>(); 
         
        try{ 
            MessageHeaders headers = inMessage.getHeaders(); 
            String id = (String)headers.get("employeeId"); 
            boolean isFound; 
            if (id.equals("1")){ 
                employeeList.getEmployee().add(new Employee(1, "John", "Doe")); 
                isFound = true; 
            }else if (id.equals("2")){ 
                employeeList.getEmployee().add(new Employee(2, "Jane", "Doe")); 
                isFound = true; 
            }else if (id.equals("0")){ 
                employeeList.getEmployee().add(new Employee(1, "John", "Doe")); 
                employeeList.getEmployee().add(new Employee(2, "Jane", "Doe"));              
                isFound = true; 
            }else{               
                isFound = false; 
            }            
            if (isFound){ 
                setReturnStatusAndMessage("0", "Success", employeeList, responseHeaderMap); 
            }else{ 
                setReturnStatusAndMessage("2", "Employee Not Found", employeeList, responseHeaderMap);                               
            } 
             
        }catch (Throwable e){ 
            setReturnStatusAndMessage("1", "System Error", employeeList, responseHeaderMap); 
            logger.error("System error occured :"+e); 
        } 
        Message<EmployeeList> message = new GenericMessage<EmployeeList>(employeeList, responseHeaderMap); 
        return message;      
    } 
     
    /**
     * The API <code>setReturnStatusAndMessage()</code> sets the return status and return message
     * in the return message payload and its header.
     * @param status
     * @param message
     * @param employeeList
     * @param responseHeaderMap
     */ 
    private void setReturnStatusAndMessage(String status,  
                        String message,  
                        EmployeeList employeeList,  
                        Map<String, Object> responseHeaderMap){ 
         
        employeeList.setReturnStatus(status); 
        employeeList.setReturnStatusMsg(message); 
        responseHeaderMap.put("Return-Status", status); 
        responseHeaderMap.put("Return-Status-Msg", message); 
    } 

這些代碼就都很簡單,一目瞭然,不用怎麼解釋瞭。
其中幾個組件關系如下圖所示:

運行
run as –> run on server –>選擇tomcat運行
用junit運行RestHttpClientTest,就可以得到數據。
[java
final String fullUrl = "http://localhost:8080/rest-http/services/employee/{id}/search"; 
 
    EmployeeList employeeList = restTemplate.execute(fullUrl, HttpMethod.GET, 
            new RequestCallback() { 
                @Override 
                public void doWithRequest(ClientHttpRequest request) throws IOException { 
                    HttpHeaders headers = getHttpHeadersWithUserCredentials(request); 
                    headers.add("Accept", "application/xml"); 
                } 
    }, responseExtractor, employeeSearchMap); 
[java] view plaincopy
@Test 
    public void testGetEmployeeAsJson() throws Exception{ 
        Map<String, Object> employeeSearchMap = getEmployeeSearchMap("0"); 
         
        final String fullUrl = "http://localhost:8080/rest-http/services/employee/{id}/search?format=json"; 
        HttpHeaders headers = getHttpHeadersWithUserCredentials(new HttpHeaders()); 
        headers.add("Accept", "application/json"); 
        HttpEntity<Object> request = new HttpEntity<Object>(headers); 
         
        ResponseEntity<?> httpResponse = restTemplate.exchange(fullUrl, HttpMethod.GET, request, EmployeeList.class, employeeSearchMap); 
        logger.info("Return Status :"+httpResponse.getHeaders().get("X-Return-Status")); 
        logger.info("Return Status Message :"+httpResponse.getHeaders().get("X-Return-Status-Msg")); 
        assertTrue(httpResponse.getStatusCode().equals(HttpStatus.OK)); 
        jaxbJacksonObjectMapper.writeValue(System.out, httpResponse.getBody()); 
    } 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。