问题背景

项目中使用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版本不一致导致的坑相关推荐

  1. Spring Boot 集成 Swagger 生成 RESTful API 文档

    原文链接: Spring Boot 集成 Swagger 生成 RESTful API 文档 简介 Swagger 官网是这么描述它的:The Best APIs are Built with Swa ...

  2. springboot集成swagger2构建RESTful API文档

    在开发过程中,有时候我们需要不停的测试接口,自测,或者交由测试测试接口,我们需要构建一个文档,都是单独写,太麻烦了,现在使用springboot集成swagger2来构建RESTful API文档,可 ...

  3. Spring Boot 集成Swagger2生成RESTful API文档

    Swagger2可以在写代码的同时生成对应的RESTful API文档,方便开发人员参考,另外Swagger2也提供了强大的页面测试功能来调试每个RESTful API. 使用Spring Boot可 ...

  4. 关于eclpse java项目与tomcat jdk版本不一致的解决方法

    最近,在eclipse中tomcat(jdk1.7)添加项目的时候,项目添加不进去,报jdk(项目中jdk1.8)版本不一致的错误.下面是我的解决过程: 选中项目按ALT+回车 一.选择替换jdk如下 ...

  5. Idea编辑器打开现有项目,JDK版本不一致解决办法

    Information:java: javacTask: 源发行版 1.6 需要目标发行版 1.6 Information:java: javacTask: 源发行版 1.7 需要目标发行版 1.7 ...

  6. java lang报错_java.lang.UnsupportedClassVersionError:JDK版本不一致报错

    08-15 14:13:29 ERROR doPost(jcm.framework.rmi.RMIServlet:155) -SchedulerService.forceRunJobFlow erro ...

  7. java 编译环境不一致_安装多JDK后,java编译环境和运行环境版本(JDK版本) 不一致解决:...

    LeetCode() Min Stack 不知道哪里不对,留待. class MinStack { public: MinStack() { coll.resize(2); } void push(i ...

  8. Eclipse中更改JDK版本,解决ant编译报错的问题

    要改一个JDK版本 主要是为了解决ant编译报错,JDK版本不一致的问题. 1. Windows--Preferences--Java--Compiler(配置的为1.8)--Installed JR ...

  9. 第04篇 JDK版本导致Unsupported major.minor version 52.0 error

    出现问题原因-->>分析 { JDK版本不一致的问题 } 在eclipse中开发的项目有个Java build path中可以配置的JDK java compiler中可以配置compil ...

  10. Elasticsearch之需要注意的问题(es和jdk版本)

    (1)在使用java代码操作es集群的时候 要保证本地使用的es的版本和集群上es的版本保持一致.   (2)保证集群中每个节点的JDK版本和es基本配置一致 这个很简单,不多说.   (3)es集群 ...

最新文章

  1. 大数据分析处理框架——离线分析(hive,pig,spark)、近似实时分析(Impala)和实时分析(storm、spark streaming)...
  2. 判断一个字符串的所有字符是否都在另一个字符串中
  3. 使用Consul实现服务发现:instance-id自定义(3种方式)
  4. win7 linux 共享文件夹权限设置,samba 配置共享 win7 无权限访问
  5. 【VirtualBox】VirtualBox的桥接网络模式,为啥网络不稳定?
  6. AutoCAD .net 二次开发官方教程及源码C#版(4)-(源码下载)
  7. boat启动器 minecraft_minecraft boat
  8. Flash位图锯齿的处理办法
  9. 终端编译opengl程序编译运行_ubuntu – 通过SSH编写opengl代码,通过机器显示运行程序...
  10. 华为RH2285H V2设备管理口白屏的解决方法
  11. Milano Store OpenCart 2.0 主题模板 ABC-0473
  12. 翻译: TensorFlow 2.0 中的新功能
  13. 19美亚团队赛刷题,1-61,91-105windows部分+RAID重组,细致学习,积极备战,希望与各位一起进步
  14. MATLAB遗传算法解决旅行商(TSP)问题
  15. IBM WebSphere 9.0.5 笔记大全
  16. 老司机开车|消费升级如何具体化?
  17. C语言实现三子棋(嘎嘎权威)
  18. “互联网+”大学生创新创业大赛概述
  19. 安装opensips时创建MySQL表_Centos7.6安装opensips并实现通话成功
  20. MyBatis逆向工程去除表名前缀

热门文章

  1. 14. Django基础:关系映射
  2. Java编程:树(实际应用)
  3. @Resource 注解和 @Autowired 注解的对比
  4. opencv4.3.0+Visual Studio 2019环境配置
  5. 当你发现你的Alter报错的时候请看看是不是粗心了
  6. 开源矿工 - 记一个完整的软件是如何开发和运行的
  7. HTML容器标签和文本标签
  8. [NOIP2013] 华容道
  9. 判断用户输入的是数字还是字符串
  10. easyui小清新俺也晒晒 视频管理软件bs项目