SpringMVC,Gson和GAE集成

时间:2013-10-06 00:28:14

标签: java google-app-engine rest spring-mvc gson

我正在使用SpringMVC,Gson创建一个简单的RESTful服务器,并将其部署在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!")));
    }
}

打印出所需的结果。但我想摆脱那个annoyinh 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!"));
    }

但是当我尝试点击localhost时: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请求的方法。任何提示都表示赞赏。

更新

我还尝试使用RestTemplate中的GsonHttpMessageConverter类。引起我注意的另一件事是,如果我删除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;
}

但这并不能覆盖requestMappingHandlerAdapterconfigureMessageConverters方法。如果没有更改,堆栈跟踪是相同的。

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>

我的网络服务:

    @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!"));    
        }
    }

主要问题是我试图使用Jersey来公开Web服务。而不是我改变,现在我使用SpringMVC注释。

2 个答案:

答案 0 :(得分:5)

您正在使用<mvc:annotation-driven /> AND 您还注册AnnotationMethodHandlerAdapterRequestMappingHandlerAdapter注册的<mvc:annotation-driven />' (So basically the AnnotationMethodHandlerAdapter`无关并且只吃掉服务器内存。)

相反,您应该使用命名空间来注册转换器。

<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!"));    
    }
}

您可能想要查看spring-android项目并使用他们的GsonHttpMessageConverter,而不是创建自己的转换器。

答案 1 :(得分:0)

当序列化GSON可以从对象推断出类型时,所以不需要传递类型:

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

而只是尝试:

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