一、错误发生的背景
最近搭建一个springboot项目,在开发到全局异常处理的时候,运行报错。具体代码以及错误信息如下:
代码:GlobalExceptionHandler(异常处理类)
/**
* 全局异常处理类
* @author HXY
* @version 1.0
*/
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
//日志处理类
private final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* Exception异常处理
* @param e
* @return HoneyResult
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public HoneyResult handleException(Exception e) {
logger.error(e.getMessage());
logger.error(ExceptionUtil.getStackTrace(e));
return HoneyResult.build(HoneyResult.ERR_STATE,e.getMessage());
}
/**
* 参数无效的异常处理
* @param e
* @return
*/
@ExceptionHandler(value = InvalidException.class)
@ResponseBody
public HoneyResult handleInvalidException(InvalidException e) {
logger.error("parameter is invalid");
logger.error(e.getMessage());
return HoneyResult.build(e.getCode(),e.getMessage());
}
}
在controller层抛出异常
@RestController
@RequestMapping("/honeybee")
public class UserController {
@Autowired
private UserService userService;
private final Logger logger = LoggerFactory.getLogger(UserController.class);
@GetMapping("/select")
public UserBean xxxx() {
logger.info("开始查询");
UserBean result = userService.select();
if (1 ==1) {
int n = 1 / 0;
//throw new InvalidException(ResultCode.INVALID_PARAMETER.getCode(),ResultCode.INVALID_PARAMETER.getMessage());
}
logger.info("查询结束:" + result.toString());
return result;
}
}
结果启动项目,除了期望的异常被正确抛出以外,还抛出了上面说到的异常。异常信息如下:
2019-01-12 12:16:52 |WARN |http-nio-8888-exec-1 |ExceptionHandlerExceptionResolver.java:392 |org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver |Failed to invoke @ExceptionHandler method: public com.honeybee.common.bean.HoneyResult com.honeybee.common.exception.GlobalExceptionHandler.handleException(java.lang.Exception)
java.lang.IllegalArgumentException: No converter found for return value of type: class com.honeybee.common.bean.HoneyResult
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:187)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:174)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:81)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:113)
at org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver.doResolveHandlerMethodException(ExceptionHandlerExceptionResolver.java:385)
at org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver.doResolveException(AbstractHandlerMethodExceptionResolver.java:59)
at org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:135)
at org.springframework.web.servlet.handler.HandlerExceptionResolverComposite.resolveException(HandlerExceptionResolverComposite.java:76)
at org.springframework.web.servlet.DispatcherServlet.processHandlerException(DispatcherServlet.java:1222)
at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1034)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:984)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1468)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
二、异常分析
这个异常你的意思大概是说没有转化器将我们自定义的返回类型转换成json到http响应。然后跟踪代码,发现最终报错的地方是这里:
AbstractMessageConverterMethodProcessor类的writeWithMessageConverters方法的186行。基于以上分析,解决方案就是需要我们自定义消息转换器。在解决这个问题之前我们不妨稍微花点时间研究一下springMvc的消息转换器的源码。
三、消息转换器
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//设置响应状态
setResponseStatus(webRequest);
//返回值为null的情况,设置请求处理完成
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
//返回值不为null,ResponseStatus有内容的情况,设置请求处理完成
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
//否则设置请求处理未完成,并使用returnValueHandlers进行处理,returnValueHandlers:HandlerMethodReturnValueHandlerComposite类对象
mavContainer.setRequestHandled(false);
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
}
throw ex;
}
}
2.HandlerMethodReturnValueHandlerComposite.java
在该类中的处理流程如下:
//迭代所有的HandlerMethodReturnValueHandler,选择一个支持返回值类型的处理器,然后调用它的handleReturnValue方法
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
//选择合适的HandlerMethodReturnValueHandler
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
//调用对应的handleReturnValue方法处理
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
3.HandlerMethodReturnValueHandler是一个接口,定义了一下两个方法
//该HandlerMethodReturnValueHandler是否支持对应的返回值类型
boolean supportsReturnType(MethodParameter returnType);
//处理返回值
void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
这个接口有很多实现类,
今天我们用到的是AbstractMessageConverterMethodProcessor.java这个类,他是一个抽象类,没有实现接口中的方法,而是在他的子类RequestResponseBodyMethodProcessor中进行了实现。AbstractMessageConverterMethodProcessor中定义了writeWithMessageConverters方法,RequestResponseBodyMethodProcessor中实现了handleReturnValue方法,调用了父类的writeWithMessageConverters方法。类图以及主要代码如下:
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
protected void writeWithMessageConverters(T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
Object outputValue;
Class> valueType;
Type declaredType;
if (value instanceof CharSequence) {
outputValue = value.toString();
valueType = String.class;
declaredType = String.class;
}
else {
outputValue = value;
valueType = getReturnValueType(outputValue, returnType);
declaredType = getGenericType(returnType);
}
HttpServletRequest request = inputMessage.getServletRequest();
//获取客户端Accept字段接收的content-type,如果@RequestMapping中的produces配置了content-type,则返回此content-type,若果没有,
//则获取所有HttpMessageConverter所支持的content-type,然后通过requestedMediaTypes和producibleMediaTypes 对比,选定一个最合适的content-type
List requestedMediaTypes = getAcceptableMediaTypes(request);
//获取转换器支持的MediaType
List producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
if (outputValue != null && producibleMediaTypes.isEmpty()) {
throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
}
//两次遍历,获取转换器支持的MediaType和请求的MediaType兼容的MediaType集合
Set compatibleMediaTypes = new LinkedHashSet();
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
//isCompatibleWith判断是否兼容
if (requestedType.isCompatibleWith(producibleType)) {
//getMostSpecificMediaType通过q值获取最具体的一个,q值可以指定
compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (compatibleMediaTypes.isEmpty()) {
if (outputValue != null) {
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
}
return;
}
List mediaTypes = new ArrayList(compatibleMediaTypes);
MediaType.sortBySpecificityAndQuality(mediaTypes);
MediaType selectedMediaType = null;
for (MediaType mediaType : mediaTypes) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter> messageConverter : this.messageConverters) {
if (messageConverter instanceof GenericHttpMessageConverter) {
//是否可以写
if (((GenericHttpMessageConverter) messageConverter).canWrite(
declaredType, valueType, selectedMediaType)) {
outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class extends HttpMessageConverter>>) messageConverter.getClass(),
inputMessage, outputMessage);
if (outputValue != null) {
addContentDispositionHeader(inputMessage, outputMessage);
//写出到响应
((GenericHttpMessageConverter) messageConverter).write(
outputValue, declaredType, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
"\" using [" + messageConverter + "]");
}
}
return;
}
}
else if (messageConverter.canWrite(valueType, selectedMediaType)) {
outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class extends HttpMessageConverter>>) messageConverter.getClass(),
inputMessage, outputMessage);
if (outputValue != null) {
addContentDispositionHeader(inputMessage, outputMessage);
((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
"\" using [" + messageConverter + "]");
}
}
return;
}
}
}
if (outputValue != null) {
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
整个处理过程到这里就结束了。分析了springMvc消息转换器对返回值的处理过程,也找到了错误原因,那么下面就开始谈谈怎么解决问题吧。
四、解决方案:自定义fastJson作为消息转换器,处理自定义类型。
com.alibaba
fastjson
1.2.47
2.继承WebMvcConfigurerAdapter,配置fastJson消息转换器。
WebMvcConfigurerAdapter:是Spring内部的一种配置方式,采用JavaBean的形式来代替传统的xml配置文件形式进行针对框架个性化定制。代码如下:
/**
* 自定义配置类实现JavaBean注解形式配置 WebMvcConfigurerAdapter在springboot2.0和spring5.0中已经废弃
* @author HXY
*/
@Configuration
public class HoneyWebMvcConfigure extends WebMvcConfigurerAdapter {
/**
* 配置消息内容转换器,定义请求返回的内容用fastjson进行转换
* @param converters
*/
@Override
public void configureMessageConverters(List> converters) {
super.configureMessageConverters(converters);
//创建fastjson消息转换器
FastJsonHttpMessageConverter fastJsonConverter = new FastJsonHttpMessageConverter();
//创建配置类
FastJsonConfig fastJsonConfig = new FastJsonConfig();
//修改配置返回内容的过滤
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat,SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteMapNullValue,
SerializerFeature.WriteNullStringAsEmpty,SerializerFeature.WriteNullListAsEmpty,
SerializerFeature.WriteNullNumberAsZero,SerializerFeature.WriteNullBooleanAsFalse);
fastJsonConverter.setFastJsonConfig(fastJsonConfig);
//添加StringHttpMessageConverter,解决中文乱码问题
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
List mediaTypes = Collections.singletonList(MediaType.APPLICATION_JSON_UTF8);
stringConverter.setSupportedMediaTypes(mediaTypes);
//讲fastJson添加到消息转换列表
converters.add(fastJsonConverter);
converters.add(stringConverter);
}
}