我正在使用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;
}
但这并不能覆盖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>
我的网络服务:
@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注释。
答案 0 :(得分:5)
您正在使用<mvc:annotation-driven />
AND 您还注册AnnotationMethodHandlerAdapter
与RequestMappingHandlerAdapter
注册的<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));