2013-10-06 25 views
2

我正在用SpringMVC,Gson製作一個簡單的RESTful服務器並將其部署到GAE中。一切工作正常,如果我嘗試映射我的要求是這樣的:SpringMVC,Gson和GAE集成

import java.util.Arrays; 
import java.util.List; 

import javax.ws.rs.GET; 
import javax.ws.rs.Path; 
import javax.ws.rs.Produces; 
import javax.ws.rs.core.MediaType; 

import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.stereotype.Component; 
import org.springframework.web.bind.annotation.ResponseBody; 

import com.generic.server.model.Widget; 
import com.generic.server.services.WidgetService; 
import com.google.gson.Gson; 

@Component 
@Path("/widget") 
public class WidgetRestService { 

    /** 
    * @return All the widgets info. 
    * @uri http://localhost:8888/rest/widget/ 
    */ 
    @GET @Path("/") @Produces(MediaType.APPLICATION_JSON) 
    public @ResponseBody String getAll() { 
    Gson g = new Gson(); 
    return g.toJson(Arrays.asList(new Widget("BuyerApp", "Buy something now!"), 
      new Widget("DogSwitcher", "Tired of your dog? Switch it right now!"))); 
    } 
} 

本刊所期望的結果。但我想擺脫那個惱人的Gson實例。所以我做了我自己的定製HttpMessageConverter

@Component 
public class GSONHttpMessageConverter extends AbstractHttpMessageConverter<Object> { 

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); 

    private GsonBuilder gsonBuilder = new GsonBuilder() 
      .excludeFieldsWithoutExposeAnnotation() 
      .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); 

    public GSONHttpMessageConverter() { 
     super(new MediaType("application", "json", DEFAULT_CHARSET)); 
    } 

    @Override 
    protected boolean supports(Class<?> clazz) { 
     // should not be called, since we override canRead/Write instead 
     throw new UnsupportedOperationException(); 
    } 

    @Override 
    public boolean canRead(Class<?> clazz, MediaType mediaType) { 
     return MediaType.APPLICATION_JSON.isCompatibleWith(mediaType); 
    } 

    public boolean canWrite(Class<?> clazz, MediaType mediaType) { 
     return MediaType.APPLICATION_JSON.isCompatibleWith(mediaType); 
    } 

    public void registerTypeAdapter(Type type, Object serializer) { 
     gsonBuilder.registerTypeAdapter(type, serializer); 
    } 

    @Override 
    protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { 
     try { 
      Gson gson = gsonBuilder.create(); 
      return gson.fromJson(StringUtils.convertStreamToString(inputMessage.getBody()), clazz); 
     } catch (JsonParseException e) { 
      throw new HttpMessageNotReadableException("Could not read JSON: " + e.getMessage(), e); 
     } 
    } 

    @Override 
    protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { 
     Type genericType = TypeToken.get(o.getClass()).getType(); 

     BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputMessage.getBody(), DEFAULT_CHARSET)); 
     try { 
      // See http://code.google.com/p/google-gson/issues/detail?id=199 for details on SQLTimestamp conversion 
      Gson gson = gsonBuilder.create(); 
      writer.append(gson.toJson(o, genericType)); 
     } finally { 
      writer.flush(); 
      writer.close(); 
     } 
    } 
} 

,並將其添加到applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:security="http://www.springframework.org/schema/security" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" 
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" 
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:util="http://www.springframework.org/schema/util" 
    xsi:schemaLocation=" 
      http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans.xsd 
      http://www.springframework.org/schema/tx 
      http://www.springframework.org/schema/tx/spring-tx-3.1.xsd 
      http://www.springframework.org/schema/aop 
      http://www.springframework.org/schema/aop/spring-aop-3.1.xsd 
      http://www.springframework.org/schema/context 
      http://www.springframework.org/schema/context/spring-context-3.1.xsd 
      http://www.springframework.org/schema/security 
      http://www.springframework.org/schema/security/spring-security-3.0.3.xsd 
      http://www.springframework.org/schema/mvc 
      http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> 


    <mvc:annotation-driven /> 

    <!-- If you hit /home/ on the browser this will redirect you to the login 
     view --> 
    <mvc:view-controller path="/" view-name="login" /> 
    <mvc:resources location="/resources/" mapping="/resources/**" /> 

    <context:component-scan 
     base-package="com.generic.server.services, 
            com.generic.server.model, 
            com.generic.server.rest, 
            com.generic.server.ui.controller" /> 

    <bean id="viewResolver" 
     class="org.springframework.web.servlet.view.InternalResourceViewResolver"> 
     <property name="prefix"> 
      <value>/WEB-INF/client/</value> 
     </property> 
     <property name="suffix"> 
      <value>.html</value> 
     </property> 
    </bean> 

    <mvc:default-servlet-handler /> 

    <bean 
     class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> 
     <property name="messageConverters"> 
      <list> 
       <bean class="com.generic.server.util.GSONHttpMessageConverter"/> 
      </list> 
     </property> 
    </bean> 
</beans> 

現在我會改變的方式映射到返回對象的要求:

/** 
    * @return All the widgets info. 
    * @uri http://localhost:8888/rest/widget/ 
    */ 
    @GET @Path("/") @Produces(MediaType.APPLICATION_JSON) 
    public @ResponseBody List<Widget> getAll() { 
     return Arrays.asList(new Widget("BuyerApp", "Buy something now!"), 
       new Widget("DogSwitcher", "Tired of your dog? Switch it right now!")); 
    } 

但是,當我試着打本地主機: 8888/rest/widget /服務器崩潰並顯示此消息:

javax.ws.rs.WebApplicationException 
at com.sun.jersey.spi.container.ContainerResponse.write(ContainerResponse.java:268) 
at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1029) 
at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:941) 
at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:932) 
at com.sun.jersey.spi.container.servlet.WebComponent.service(WebComponent.java:384) 
at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:451) 
at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:632) 
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717) 
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511) 
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166) 
at com.google.appengine.api.socket.dev.DevSocketFilter.doFilter(DevSocketFilter.java:74) 
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157) 
at com.google.appengine.tools.development.ResponseRewriterFilter.doFilter(ResponseRewriterFilter.java:123) 
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157) 
at com.google.appengine.tools.development.HeaderVerificationFilter.doFilter(HeaderVerificationFilter.java:34) 
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157) 
at com.google.appengine.api.blobstore.dev.ServeBlobFilter.doFilter(ServeBlobFilter.java:63) 
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157) 
at com.google.apphosting.utils.servlet.TransactionCleanupFilter.doFilter(TransactionCleanupFilter.java:43) 
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157) 
at com.google.appengine.tools.development.StaticFileFilter.doFilter(StaticFileFilter.java:125) 
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157) 
at com.google.appengine.tools.development.DevAppServerModulesFilter.doDirectRequest(DevAppServerModulesFilter.java:368) 
at com.google.appengine.tools.development.DevAppServerModulesFilter.doDirectModuleRequest(DevAppServerModulesFilter.java:351) 
at com.google.appengine.tools.development.DevAppServerModulesFilter.doFilter(DevAppServerModulesFilter.java:116) 
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157) 
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388) 
at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216) 
at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182) 
at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765) 
at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418) 
at com.google.appengine.tools.development.DevAppEngineWebAppContext.handle(DevAppEngineWebAppContext.java:97) 
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152) 
at com.google.appengine.tools.development.JettyContainerService$ApiProxyHandler.handle(JettyContainerService.java:485) 
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152) 
at org.mortbay.jetty.Server.handle(Server.java:326) 
at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542) 
at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:923) 
at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:547) 
at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:212) 
at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404) 
at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:409) 
at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582) 

我是GAE的新手,但我搜索了SO和另一個頁面,我認爲這是映射REST請求的方法。任何提示表示讚賞。

UPDATE:

我也嘗試使用GsonHttpMessageConverter類從RestTemplate。另一件引起我注意的事情是,如果我刪除AnnotationMethodHandlerAdapter bean,我會得到相同的堆棧跟蹤。 GsonHttpMessageConverter未被使用。

我也試圖實現自己的WebMvcConfigurationSupport和編程添加我的信息轉換器那裏,就像這樣:

@Configuration 
public class WebConfig extends WebMvcConfigurationSupport { 

@Bean 
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { 
    RequestMappingHandlerAdapter handlerAdapter = super.requestMappingHandlerAdapter(); 
    handlerAdapter.getMessageConverters().add(0, new GSONHttpMessageConverter()); 
    return handlerAdapter; 
} 

但這並沒有工作,沒有重寫requestMappingHandlerAdapter也不configureMessageConverters方法。堆棧跟蹤是相同的,如果沒有改變。

SOLUTION:

我的應用程序上下文:

<beans xmlns="http://www.springframework.org/schema/beans" 
     xmlns:security="http://www.springframework.org/schema/security" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" 
     xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" 
     xmlns:aop="http://www.springframework.org/schema/aop" xmlns:util="http://www.springframework.org/schema/util" 
     xsi:schemaLocation=" 
       http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd 
       http://www.springframework.org/schema/tx 
       http://www.springframework.org/schema/tx/spring-tx-3.1.xsd 
       http://www.springframework.org/schema/aop 
       http://www.springframework.org/schema/aop/spring-aop-3.1.xsd 
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context-3.1.xsd 
       http://www.springframework.org/schema/security 
       http://www.springframework.org/schema/security/spring-security-3.0.3.xsd 
       http://www.springframework.org/schema/mvc 
       http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd"> 


     <!-- If you hit /home/ on the browser this will redirect you to the login 
      view --> 
     <mvc:view-controller path="/" view-name="login" /> 
     <mvc:resources location="/resources/" mapping="/resources/**" /> 

     <context:component-scan 
      base-package="com.generic.server.services, 
             com.generic.server.model, 
             com.generic.server.rest, 
             com.generic.server.ui.controller" /> 

     <bean id="viewResolver" 
      class="org.springframework.web.servlet.view.InternalResourceViewResolver"> 
      <property name="prefix"> 
       <value>/WEB-INF/client/</value> 
      </property> 
      <property name="suffix"> 
       <value>.html</value> 
      </property> 
     </bean> 

     <mvc:annotation-driven> 
      <mvc:message-converters register-defaults="true"> 
       <bean class="com.generic.server.util.GsonHttpMessageConverter" /> 
      </mvc:message-converters> 
     </mvc:annotation-driven> 
    </beans> 

我的Web服務:

@Controller 
    @RequestMapping("/rest") 
    public class WidgetRestService { 

     /** 
     * @return All the widgets info. 
     * @uri http://localhost:8888/rest/widget/ 
     */ 
     @RequestMapping(value="/widget", method=RequestMethod.GET, produces=MediaType.APPLICATION_JSON) 
     public @ResponseBody List<Widget> getAll() { 
      return Arrays.asList(new Widget("BuyerApp", "Buy something now!"), new Widget("DogSwitcher", "Tired of your dog? Switch it right now!"));  
     } 
    } 

主要的問題是,我試圖用澤西公開Web服務。而不是我改變,現在我使用SpringMVC註釋。

+0

只是一個疑問。您已將路徑更改爲「/」,但您是否還更新了其餘的servlet條目以收聽「/」? –

+0

@HarshaR我在web服務上改變的唯一東西就是方法的返回類型。就這樣。我敢肯定,這不是一個映射錯誤或類似的東西。 – 4gus71n

+0

這有幫助嗎? http://aruld.info/handling-generified-collections-in-jersey-jax-rs/ –

回答

5

您正在使用<mvc:annotation-driven />你還註冊AnnotationMethodHandlerAdapter,這與RequestMappingHandlerAdapter註冊<mvc:annotation-driven />' (So basically the AnnotationMethodHandlerAdapter`沒有任何作用,只是吃了服務器我MORY)。

相反,您應該使用命名空間來註冊您的轉換器。

<mvc:annotation-driven> 
    <mvc:message-converters register-defaults="true"> 
     <bean class="com.generic.server.util.GSONHttpMessageConverter"/> 
    </mvc:message-converters> 
</mvc:annotation-driven> 

除此之外,您在休息端點中混合使用JAX-WS和Spring MVC註釋。假設你想要使用Spring它更改爲以下

@Controller 
@RequestMapping("/widget") 
public class WidgetRestService { 

    /** 
    * @return All the widgets info. 
    * @uri http://localhost:8888/rest/widget/ 
    */ 
    @RequestMapping(method=RequestMethod.GET, produces=MediaType.APPLICATION_JSON) 
    public @ResponseBody String getAll() { 
     return Arrays.asList(new Widget("BuyerApp", "Buy something now!"), new Widget("DogSwitcher", "Tired of your dog? Switch it right now!"));  
    } 
} 

而不是創建自己的轉換你可能想看看彈簧Android項目和使用他們的GsonHttpMessageConverter

+0

感謝您的回答,我會試一試。 – 4gus71n

+0

就像一個魅力,非常感謝你,我會更新問題,把正確的代碼。 – 4gus71n

+0

順便說一句我沒有混合使用JAX-WS和Spring MVC我使用Jersey來公開Restfull Web服務。 – 4gus71n

0

當連載GSON可以推斷從對象的類型,所以沒有必要通過類型:

writer.append(gson.toJson(o, genericType)); 

,而不是試圖只是:

writer.append(gson.toJson(o)); 
+0

我試過了,但我得到了相同的堆棧跟蹤。我做了一個更新。 – 4gus71n