jersery集成jackson实现restful api,由于jdk版本不一致导致的坑
问题背景
项目中使用jersey+jackson实现 restful api,返回信息格式为json,开发测试环境都是OK的,然鹅在线上当访问时一直报500,错误信息如下:
com.sun.jersey.spi.container.ContainerResponse write
: The registered message body writers compatible with the MIME media type are:
application/json ->com.sun.jersey.json.impl.provider.entity.JSONJAXBElementProvider$Appcom.sun.jersey.json.impl.provider.entity.JSONArrayProvider$Appcom.sun.jersey.json.impl.provider.entity.JSONObjectProvider$Appcom.sun.jersey.json.impl.provider.entity.JSONRootElementProvider$Appcom.sun.jersey.json.impl.provider.entity.JSONListElementProvider$Appcom.sun.jersey.core.impl.provider.entity.FormProvidercom.sun.jersey.core.impl.provider.entity.StringProvidercom.sun.jersey.core.impl.provider.entity.ByteArrayProvidercom.sun.jersey.core.impl.provider.entity.FileProvidercom.sun.jersey.core.impl.provider.entity.InputStreamProvidercom.sun.jersey.core.impl.provider.entity.DataSourceProvidercom.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$Generalcom.sun.jersey.core.impl.provider.entity.ReaderProvidercom.sun.jersey.core.impl.provider.entity.DocumentProvidercom.sun.jersey.core.impl.provider.entity.StreamingOutputProvidercom.sun.jersey.core.impl.provider.entity.SourceProvider$SourceWritercom.sun.jersey.json.impl.provider.entity.JSONJAXBElementProvider$Generalcom.sun.jersey.json.impl.provider.entity.JSONArrayProvider$Generalcom.sun.jersey.json.impl.provider.entity.JSONObjectProvider$Generalcom.sun.jersey.json.impl.provider.entity.JSONWithPaddingProvidercom.sun.jersey.server.impl.template.ViewableMessageBodyWritercom.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$Generalcom.sun.jersey.core.impl.provider.entity.XMLListElementProvider$Generalcom.sun.jersey.json.impl.provider.entity.JSONRootElementProvider$Generalcom.sun.jersey.json.impl.provider.entity.JSONListElementProvider$GeneralMapped exception to response: 500 (Internal Server Error)
javax.ws.rs.WebApplicationException: com.sun.jersey.api.MessageException: A message body writer for Java class com.account.manage.common.AccountInfoRes
ponse, and Java type class com.account.manage.common.AccountInfoResponse, and MIME media type application/json was not foundat com.sun.jersey.spi.container.ContainerResponse.write(ContainerResponse.java:285)at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1479)at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1391)at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1381)at com.sun.jersey.spi.container.servlet.WebComponent.service(WebComponent.java:416)at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:538)at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:716)at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:85)at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
相关依赖:
jersery 依赖
<dependency><groupId>com.sun.jersey</groupId><artifactId>jersey-server</artifactId><version>1.17</version></dependency><dependency><groupId>com.sun.jersey.contribs</groupId><artifactId>jersey-apache-client</artifactId><version>1.17</version></dependency><dependency><groupId>com.sun.jersey.contribs</groupId><artifactId>jersey-multipart</artifactId><version>1.17</version></dependency><dependency><groupId>com.sun.jersey</groupId><artifactId>jersey-servlet</artifactId><version>1.17</version></dependency>
jackson依赖
<dependency><groupId>com.fasterxml.jackson.jaxrs</groupId><artifactId>jackson-jaxrs-json-provider</artifactId><version>2.7.4</version><exclusions><exclusion><artifactId>jackson-databind</artifactId><groupId>com.fasterxml.jackson.core</groupId></exclusion></exclusions></dependency><dependency><groupId>com.fasterxml.jackson.jaxrs</groupId><artifactId>jackson-jaxrs-base</artifactId><version>2.7.4</version></dependency>
问题分析
开发测试与线上环境唯一不同的就是jdk版本,线上是jdk1.6,本地及测试时jdk7及以上,猜测是不是由于版本不一致导致的问题,有了猜测,还需要进一步的去验证猜测是不是正确:
- 在开发DEA中切切换项目的jdk的版本,当设置为1.6进行测试时,果然不出所料报了和线上一样的错误,这就进一步验证了确实是因为版本不一致导致的,但我们不能止步于此,需要更深入的了解到,到底是在哪里出错的,这个只能读源码了;
- 找到报错的位置,很明显通过一层层调用,最后是在调用com.sun.jersey.spi.container.ContainerResponse.write(ContainerResponse.java:285)这个方法时里面报错,打开源代码很快能够定位好发生异常的代码处
final MessageBodyWriter p = getMessageBodyWorkers().getMessageBodyWriter(entity.getClass(), entityType,annotations, contentType);if (p == null) {String message = "A message body writer for Java class " + entity.getClass().getName() +", and Java type " + entityType +", and MIME media type " + contentType + " was not found";LOGGER.severe(message);Map<MediaType, List<MessageBodyWriter>> m = getMessageBodyWorkers().getWriters(contentType);LOGGER.severe("The registered message body writers compatible with the MIME media type are:\n" +getMessageBodyWorkers().writersToString(m));if (request.getMethod().equals("HEAD")) {isCommitted = true;responseWriter.writeStatusAndHeaders(-1, this);responseWriter.finish();return;} else {throw new WebApplicationException(new MessageException(message), 500);}}
- 很显然是在获取MessageBodyWriter时获取为null,进入_getMessageBodyWriter方法,通过debug,能够知道该方法是通过mediaType获取对应的MessageBodyWriter的provider,在此处获取不到,那么MessageBodyWriter是什么呢,通过看源码它是一个接口,jersey所有的写response的操作都直接或间接的实现它,与之相对应的是MessageBodyReader接口;
- 那MessageBodyWriter和MessageBodyReader是什么时候加载的呢,对于这种通过spring实现的web项目,当时是在应用启动的时候加载与初始化了应用所需的所有资源,启动debug,在jerseyserver启动实现类WebApplicationImpl的初始化方法_initiate()
// Initiate context resolverscrf.init(providerServices, injectableFactory);// Initiate the exception mappersexceptionFactory.init(providerServices);// Initiate message body readers/writersbodyFactory.init();// Initiate string readersstringReaderFactory.init(providerServices);// Inject on all componentsErrors.setReportMissingDependentFieldOrMethod(true);cpFactory.injectOnAllComponents();cpFactory.injectOnProviderInstances(resourceConfig.getProviderSingletons());
前后代码省略,显然bodyFactory.init() 也就是MessageBodyFactory.init()方法初始化MessageBodyReader和MessageBodyWriter,
public void init() {initReaders();initWriters();}
此处我们只看initWriters()方法,initReaders也是同样的逻辑,MessageBodyFactory的属性
Map<MediaType, List> writerProviders,通过map保存MediaType与之对应的MessageBodyWriter
- 一步步跟踪源代码,最终进入ProviderServicel.getServiceClasses(Class<?> service, Set sp)方法中,service类型就是MessageBodyWriter,获取所有实现MessageBodyWriter的Class:
Class<?>[] pca = ServiceFinder.find(service, true).toClassArray();
最关键的是ServiceFinder.toClassArray()方法,
public Class<T>[] toClassArray() throws ServiceConfigurationError {List<Class<T>> result = new ArrayList<Class<T>>();Iterator<Class<T>> i = classIterator();while (i.hasNext())result.add(i.next());return result.toArray((Class<T>[])Array.newInstance(Class.class,result.size()));}
会创建一个Class的Iterator,通过迭代将能够通过反射创建实例的Class放置在result中,i.next()方法其实调用的是ServiceFinder.LazyClassIterator.next()方法,代码如下:
@SuppressWarnings("unchecked")public Class<T> next() {if (!hasNext()) {throw new NoSuchElementException();}String cn = nextName;nextName = null;try {return (Class<T>)ReflectionHelper.classForNameWithException(cn, loader);} catch (ClassNotFoundException ex) {fail(serviceName,SpiMessages.PROVIDER_NOT_FOUND(cn, service));} catch (NoClassDefFoundError ex) {fail(serviceName,SpiMessages.DEPENDENT_CLASS_OF_PROVIDER_NOT_FOUND(ex.getLocalizedMessage(), cn, service));} catch (ClassFormatError ex) {fail(serviceName,SpiMessages.DEPENDENT_CLASS_OF_PROVIDER_FORMAT_ERROR(ex.getLocalizedMessage(), cn, service));} catch (Exception x) {fail(serviceName,SpiMessages.PROVIDER_CLASS_COULD_NOT_BE_LOADED(cn, service, x.getLocalizedMessage()),x);}return null; /* This cannot happen */}
在这里当nextName为Jackson的处理类JacksonJsonProvider时,即 return (Class)ReflectionHelper.classForNameWithException(cn, loader) 在这一步不会返回JacksonJsonProvider的Class,导致在下一步没法初始化实例
for (ProviderClass pc : getServiceClasses(provider)) {Object o = getComponent(pc);if (o != null) {ps.add(provider.cast(o));}}
最终导致在处理业务请求后,jersey通过jackson写响应操作时,获取不到对应的Writer而失败; 但是在jdk1.7版本及以上正常的,由于最终调用的jdk的native方法就再没有深入研究为啥会出现
这个情况,总之这是jdk本身的一个坑。
public static Class classForNameWithException(String name, ClassLoader cl)throws ClassNotFoundException {if (cl != null) {try {return Class.forName(name, false, cl);} catch (ClassNotFoundException ex) {}}return Class.forName(name);}private static native Class<?> forName0(String name, boolean initialize,ClassLoader loader,Class<?> caller)throws ClassNotFoundException;
解决方案
为避免jdk版本不同而踩坑,通过采用另一种解决方案
替换jackson依赖
<dependency><groupId>com.sun.jersey</groupId><artifactId>jersey-json</artifactId><version>1.17</version>
</dependency>
web.xml配置
<servlet><servlet-name>jersey-serlvet</servlet-name><servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class><init-param><param-name>jersey.config.server.provider.packages</param-name><param-value>******</param-value></init-param><init-param><param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name><param-value>true</param-value></init-param>
若采用jersey-json,则必须配置com.sun.jersey.api.json.POJOMappingFeature,因为处理jackson的provider为JacksonProviderProxy其中会通过该属性判断json是否与entity映射
public void setFeaturesAndProperties(FeaturesAndProperties fp) {this.jacksonEntityProviderFeatureSet = fp.getFeature("com.sun.jersey.api.json.POJOMappingFeature");}public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {return this.jacksonEntityProviderFeatureSet && (this.jaxbProvider.isWriteable(type, genericType, annotations, mediaType) || this.pojoProvider.isWriteable(type, genericType, annotations, mediaType));}
在判断是否可以进行写操作时jacksonEntityProviderFeatureSet必须为ture,否则还是会报500错误,错误信息跟之前一样。
jersery集成jackson实现restful api,由于jdk版本不一致导致的坑相关推荐
- Spring Boot 集成 Swagger 生成 RESTful API 文档
原文链接: Spring Boot 集成 Swagger 生成 RESTful API 文档 简介 Swagger 官网是这么描述它的:The Best APIs are Built with Swa ...
- springboot集成swagger2构建RESTful API文档
在开发过程中,有时候我们需要不停的测试接口,自测,或者交由测试测试接口,我们需要构建一个文档,都是单独写,太麻烦了,现在使用springboot集成swagger2来构建RESTful API文档,可 ...
- Spring Boot 集成Swagger2生成RESTful API文档
Swagger2可以在写代码的同时生成对应的RESTful API文档,方便开发人员参考,另外Swagger2也提供了强大的页面测试功能来调试每个RESTful API. 使用Spring Boot可 ...
- 关于eclpse java项目与tomcat jdk版本不一致的解决方法
最近,在eclipse中tomcat(jdk1.7)添加项目的时候,项目添加不进去,报jdk(项目中jdk1.8)版本不一致的错误.下面是我的解决过程: 选中项目按ALT+回车 一.选择替换jdk如下 ...
- Idea编辑器打开现有项目,JDK版本不一致解决办法
Information:java: javacTask: 源发行版 1.6 需要目标发行版 1.6 Information:java: javacTask: 源发行版 1.7 需要目标发行版 1.7 ...
- java lang报错_java.lang.UnsupportedClassVersionError:JDK版本不一致报错
08-15 14:13:29 ERROR doPost(jcm.framework.rmi.RMIServlet:155) -SchedulerService.forceRunJobFlow erro ...
- java 编译环境不一致_安装多JDK后,java编译环境和运行环境版本(JDK版本) 不一致解决:...
LeetCode() Min Stack 不知道哪里不对,留待. class MinStack { public: MinStack() { coll.resize(2); } void push(i ...
- Eclipse中更改JDK版本,解决ant编译报错的问题
要改一个JDK版本 主要是为了解决ant编译报错,JDK版本不一致的问题. 1. Windows--Preferences--Java--Compiler(配置的为1.8)--Installed JR ...
- 第04篇 JDK版本导致Unsupported major.minor version 52.0 error
出现问题原因-->>分析 { JDK版本不一致的问题 } 在eclipse中开发的项目有个Java build path中可以配置的JDK java compiler中可以配置compil ...
- Elasticsearch之需要注意的问题(es和jdk版本)
(1)在使用java代码操作es集群的时候 要保证本地使用的es的版本和集群上es的版本保持一致. (2)保证集群中每个节点的JDK版本和es基本配置一致 这个很简单,不多说. (3)es集群 ...
最新文章
- 大数据分析处理框架——离线分析(hive,pig,spark)、近似实时分析(Impala)和实时分析(storm、spark streaming)...
- 判断一个字符串的所有字符是否都在另一个字符串中
- 使用Consul实现服务发现:instance-id自定义(3种方式)
- win7 linux 共享文件夹权限设置,samba 配置共享 win7 无权限访问
- 【VirtualBox】VirtualBox的桥接网络模式,为啥网络不稳定?
- AutoCAD .net 二次开发官方教程及源码C#版(4)-(源码下载)
- boat启动器 minecraft_minecraft boat
- Flash位图锯齿的处理办法
- 终端编译opengl程序编译运行_ubuntu – 通过SSH编写opengl代码,通过机器显示运行程序...
- 华为RH2285H V2设备管理口白屏的解决方法
- Milano Store OpenCart 2.0 主题模板 ABC-0473
- 翻译: TensorFlow 2.0 中的新功能
- 19美亚团队赛刷题,1-61,91-105windows部分+RAID重组,细致学习,积极备战,希望与各位一起进步
- MATLAB遗传算法解决旅行商(TSP)问题
- IBM WebSphere 9.0.5 笔记大全
- 老司机开车|消费升级如何具体化?
- C语言实现三子棋(嘎嘎权威)
- “互联网+”大学生创新创业大赛概述
- 安装opensips时创建MySQL表_Centos7.6安装opensips并实现通话成功
- MyBatis逆向工程去除表名前缀