novoland
1/25/2016 - 9:07 AM

ExceptionHandlerAdvice.java

package com.meituan.show.sell.web.advice;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.ExceptionSerializer;
import com.alibaba.fastjson.serializer.ObjectSerializer;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SimplePropertyPreFilter;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.google.common.collect.Maps;
import com.meituan.show.sell.service.exception.ValidationFailException;
import com.meituan.show.sell.web.exception.APIException;
import com.meituan.show.utils.APIUtils;

@RestController
@ControllerAdvice(basePackages = {""})
public class ExceptionHandlerAdvice {
    private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionHandlerAdvice.class);
    private static final SerializeConfig serializeConfig = new CustomForExceptionSerializeConfig();
    private static final SimplePropertyPreFilter exceptionFieldFilter = new SimplePropertyPreFilter();
    private static final Charset UTF8 = Charset.forName("UTF-8");

    @Value("#{configProperties['response_error_isPrintStackTrace']}")
    private boolean isPrintStackTrace;
    @Resource
    private FastJsonHttpMessageConverter fastJsonHttpMessageConverter;

    static {
        exceptionFieldFilter.getExcludes().add("localizedMessage");
        exceptionFieldFilter.getExcludes().add("statusCode");
        exceptionFieldFilter.getExcludes().add("stackTrace");
        exceptionFieldFilter.getExcludes().add("formattedMessage");
        exceptionFieldFilter.getExcludes().add("suppressed");
    }

    /*
     * 对 APIException 和 ValidationFailException:
     * 1. 在响应里加上 code 字段(eg. code:异常类名trim掉"Exception"后缀得到的字符串);
     * 2. 把异常序列化到客户端(eg. errorDetail:异常)
     * 方便客户端了解发生了何种异常,以及异常的具体信息。
     */
    private void writeSerializeExceptionResult(HttpServletResponse response, Map<String, Object> exceptionResult) throws IOException {
        Map<String, Object> map = Maps.newHashMap(exceptionResult);
        // 异常不保存在data中,方便C端解析
        Object exception = map.remove("data");
        map.put("errorDetail", exception);
        String exceptionClass = exception.getClass().getSimpleName();
        map.put("code", exceptionClass.endsWith("Exception") ? exceptionClass.substring(0, exceptionClass.length() - "Exception".length()) : exceptionClass);
        String json = JSON.toJSONString(map, serializeConfig, exceptionFieldFilter, fastJsonHttpMessageConverter.getFeatures());
        response.setContentType("application/json");
        response.getOutputStream().write(json.getBytes(UTF8));
    }

    @ExceptionHandler(value = {APIException.class, BussinessRuleViolationException.class})
    public void exceptionHandler(APIException ex, HttpServletResponse response) throws IOException {
        LOGGER.warn(ex.getMessage(), ex);
        String message = ex.getFormattedMessage();
        Map<String, Object> result = APIUtils.makeResponse(false, ex, null, message);
        writeSerializeExceptionResult(response, result);
    }

    @ExceptionHandler
    public Map<String, Object> defaultExceptionHandler(Throwable ex) {
        LOGGER.error(ex.getMessage(), ex);
        String message = isPrintStackTrace ? (ex.getMessage() == null ? "" : ex.getMessage()) : "服务器内部错误";
        return APIUtils.makeResponse(false, null, null, message);
    }

    // 异常的序列化用JavaBeanSerializer,不用ExceptionSerializer,后者会带上异常类名,这里并不适用
    static class CustomForExceptionSerializeConfig extends SerializeConfig {
        @Override
        public ObjectSerializer getObjectWriter(Class<?> clazz) {
            ObjectSerializer serializer = super.getObjectWriter(clazz);
            if (serializer != null && serializer.getClass() == ExceptionSerializer.class) {
                put(clazz, createJavaBeanSerializer(clazz));
            }
            return super.getObjectWriter(clazz);
        }
    }
}