wb.ouyang

毕竟几人真得鹿,不知终日梦为鱼

导航

springmvc请求参数异常统一处理,结合钉钉报告信息定位bug位置

参考之前一篇博客:springmvc请求参数异常统一处理

1、ExceptionHandlerController

package com.oy.controller;

import java.text.MessageFormat;
import java.util.ResourceBundle;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;

import com.alibaba.fastjson.JSONObject;

import com.oy.exception.ForbiddenException;
import com.oy.exception.JwebException;
import com.oy.utils.ErrCode;
import com.oy.utils.Response;
import com.oy.utils.UtilFunctions;

@ControllerAdvice
public class ExceptionHandlerController {

    @ExceptionHandler(RuntimeException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public JSONObject runtimeExceptionHandler(RuntimeException ex) {
        UtilFunctions.log.error("runtimeExceptionHandler, msg: {}, exception: {}", ex.toString(), ex);
        UtilFunctions.reportError("runtimeExceptionHandler: " + ex.toString(), ex);
        JSONObject response = new JSONObject();
        response.put("message", "Internal Server Error");
        return response;
    }

    @ExceptionHandler(NullPointerException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public JSONObject nullPointerExceptionHandler(NullPointerException ex) {
        UtilFunctions.log.error("nullPointerExceptionHandler, msg: {}, exception: {}", ex.toString(), ex);
        UtilFunctions.reportError("runtimeExceptionHandler: " + ex.toString(), ex);
        JSONObject response = new JSONObject();
        response.put("message", "Internal Server Error");
        return response;
    }

    /*----- REQUEST ERROR -----*/
    @ExceptionHandler({ ForbiddenException.class })
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ResponseBody
    public JSONObject requestForbidden(ForbiddenException ex) {
        UtilFunctions.log.error("ForbiddenExceptionHandler, msg: {}, exception: {}", ex.toString(), ex);
        JSONObject response = new JSONObject();
        response.put("message", ex.getMessage());
        return response;
    }

    @ExceptionHandler({ TypeMismatchException.class })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public JSONObject requestTypeMismatch(TypeMismatchException ex) {
        UtilFunctions.log.error("TypeMismatchExceptionHandler, msg: {}, exception: {}", ex.toString(), ex);
        JSONObject response = new JSONObject();
        // response.put("message", "Bad Request");
        // response.put("message", "Bad Request,  parameter type of " + ex.getPropertyName() + " need be " + ex.getRequiredType());
        
        if (Double.class.equals(ex.getRequiredType()) ||  Integer.class.equals(ex.getRequiredType())) {
            response.put("message", "Bad Request, " +  ex.getValue() + " not a number");
        } else {
            String strTemplate = "Bad Request, {0} is invalid, a type of {1} is needed";
            response.put("message", MessageFormat.format(strTemplate, ex.getValue(), ex.getRequiredType().getName()));
        }
        return response;
    }

    @ExceptionHandler({ MissingServletRequestParameterException.class })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public JSONObject requestMissingServletRequest(MissingServletRequestParameterException ex) {
        UtilFunctions.log.error("MissingServletRequestParameterExceptionHandler, msg: {}, exception: {}", ex.toString(), ex);
        JSONObject response = new JSONObject();
        // response.put("message", "Bad Request");
        String strTemplate = "Bad Request, param:{0} is required, type:{1}";
        response.put("message", MessageFormat.format(strTemplate, ex.getParameterName(), ex.getParameterType()));
        return response;
    }
    
    @ExceptionHandler({ NoSuchRequestHandlingMethodException.class })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public JSONObject NoSuchRequestHandlingMethodExceptionHandler(NoSuchRequestHandlingMethodException ex) {
        UtilFunctions.log.error("NoSuchRequestHandlingMethodExceptionHandler, msg: {}, exception: {}", ex.toString(), ex);
        JSONObject response = new JSONObject();
        response.put("message", "Not Found");
        return response;
    }
    
    /*----- REQUEST ERROR -----*/
    @ExceptionHandler({ HttpMessageNotReadableException.class })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public JSONObject requestNotReadable(HttpMessageNotReadableException ex) {
        UtilFunctions.log.error("HttpMessageNotReadableExceptionHandler, msg: {}, exception: {}", ex.toString(), ex);
        JSONObject response = new JSONObject();
        response.put("message", "Bad Request");
        return response;
    }

    @ExceptionHandler({ HttpRequestMethodNotSupportedException.class })
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ResponseBody
    public JSONObject request405(HttpRequestMethodNotSupportedException ex) {
        UtilFunctions.log.error("HttpRequestMethodNotSupportedExceptionHandler, msg: {}, exception: {}", ex.toString(), ex);
        JSONObject response = new JSONObject();
        // response.put("message", "Method Not Allowed");
        response.put("message", ex.getMessage());
        return response;
    }
    
    @ExceptionHandler({ JwebException.class })
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public JSONObject jwebExceptionHandler(JwebException ex, HttpServletRequest request) {
        UtilFunctions.log.error("jwebExceptionHandler, info: {}, exception: {}", ex.getMessage(), ex);
        UtilFunctions.reportError("jwebExceptionHandler, " + ex.toString(), null);
        //JSONObject response = new JSONObject();
        //response.put("code", ErrCode.SER_INTERNAL_ERR);
        //response.put("message", ex.getMessage());
        //response.put("message", "The system is busy. Please try again later.");
        ResourceBundle resourceBundle = (ResourceBundle) request.getAttribute("resourceBundle");
        String message = UtilFunctions.getMessage(resourceBundle, "SYSTEM_BUSY");
        return new Response(ErrCode.SER_INTERNAL_ERR, message).toJson();
    }
}

 

2、springmvc全局异常捕获,整合钉钉打印异常相关信息

public class UtilFunctions {
    public static Logger log = LoggerFactory.getLogger("jweb");

    public static void reportError(String msg, Exception err) {
        if (err != null) {
            log.error("msg:{}, err:{}", msg, err);
        }
        
        JSONObject content = new JSONObject();
        //content.put("content", "hostname:" + Config.HOSTNAME + "\n" + "errMsg:" + msg);
        content.put("content", "hostname:" + Config.HOSTNAME + "\n" + "errMsg:" + msg + "\n" + getStackTraceInfo(err));
        
        JSONObject obj = new JSONObject();
        obj.put("msgtype", "text");
        obj.put("text", content.toString());
        String query = obj.toString();
        try {
            URL url = new URL("https://oapi.dingtalk.com/robot/send?access_token=" + Config.DINGTOKEN);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setDoInput(true);
            connection.setDoOutput(true);
            connection.setRequestMethod("POST");
            connection.setUseCaches(false);
            connection.setInstanceFollowRedirects(true);
            connection.setRequestProperty("Content-Type", "application/json");
            connection.connect();
            try (OutputStream os = connection.getOutputStream()) {
                os.write(query.getBytes("UTF-8"));
            }
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {

            }
            connection.disconnect();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static String printStackTraceToString(Throwable t) {
     if (t == null) {
            return "";
        } StringWriter sw
= null; PrintWriter pw = null; try { sw = new StringWriter(); pw = new PrintWriter(sw); t.printStackTrace(pw); pw.flush(); sw.flush(); return sw.getBuffer().toString(); } finally { if (sw != null) { try { sw.close(); } catch (IOException e1) { e1.printStackTrace(); } } if (pw != null) { pw.close(); } } } public static String getStackTraceInfo(Throwable t) { String stackTraceInfo = ""; String stackTrace = printStackTraceToString(t); String regex = "at com.oy(\\S*)"; Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(stackTrace); while (matcher.find()) { stackTraceInfo += matcher.group() + "\n"; // UtilFunctions.log.info("===== matcher.group:{}" + matcher.group()); } return stackTraceInfo; } }

 

3、测试

  3.1、代码出现RuntimeException,比如 int a = 1/0; 钉钉报告信息:

hostname:APC058
errMsg:runtimeExceptionHandler:java.lang.ArithmeticException:/by zero
at com.oy.controller.TradeController.getOrder(TradeController.java:220)

  通过钉钉报告信息可以定位bug位置是TradeController#getOrder()方法220行。

 

  3.2、手动抛出JwebException

  注意:不要轻易抛出JwebException,即使时try...catch...捕获异常时。因为抛出JwebException,则程序交给ExceptionHandlerController来统一处理,并且程序中断。

  需要根据业务来判断,比如需求:登陆成功后记录登陆信息,要记录ip及国家城市信息,如果查询不到城市信息,如果抛出JwebException,则导致登陆失败,这是不可以的。此种情况可以记录log,发送钉钉。

if (true) {
    String errMsg = MessageFormat.format("TradeController#getOrder error, tradeOrder:{0}, uid:{1}, hashId:{2}", tradeOrder, uid, hashId);
    throw new JwebException(errMsg);
}

  钉钉报告信息:

hostname:APC058
errMsg:jwebExceptionHandler, com.oy.exception.JwebException:TradeController#getOrder error, tradeOrder:null, uid:222, hashId:xxx

  通过钉钉报告信息可以定位bug位置是TradeController#getOrder()方法,tradeOrder为null。

 

4、其他

  4.1、上面ExceptionHandlerController中捕获JwebException后,通过注解@ResponseStatus(HttpStatus.OK)给前端返回http状态码200,并返回json数据:

{
    "code": 50005,
    "message": "The system is busy. Please try again later!"
}

 

  4.2、前面代码中ResourceBundle是用来支持国际化的。

 

  4.3、将异常栈信息保存为一个字符串

public static String printStackTraceToString(Throwable t) {
     if (t == null) {
        return "";
    }
    StringWriter sw = null;
    PrintWriter pw = null;
    try {
        sw = new StringWriter();
        pw =  new PrintWriter(sw);
        t.printStackTrace(pw);
        pw.flush();
        sw.flush();
        return sw.getBuffer().toString();
    } finally {
        if (sw != null) {
            try {
                sw.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
        if (pw != null) {
            pw.close();
        }
    }
}

 

  4.4、从异常栈信息中截取需要的数据

@Test
public void test3() {
    try{
        int a = 1/0;
    } catch(Exception e) {
        System.out.println(printStackTraceToString(e));
        getStackTraceInfo(e);
    }
}

public static String getStackTraceInfo(Throwable t) {
    String stackTraceInfo = "";
    
    String stackTrace = printStackTraceToString(t);
    String regex = "com.oy.Hello(\\S*)";
    // String regex = "at (\\S*)$"; // 截取异常栈最后一句。$表示字符串结尾
    Pattern pattern = Pattern.compile(regex);
    
    Matcher matcher = pattern.matcher(stackTrace);
    while (matcher.find()) {
        stackTraceInfo += matcher.group();
        System.out.println("=====matcher.group:{}" + matcher.group());
    }
    return stackTraceInfo;
}

 

posted on 2019-05-02 22:38  wenbin_ouyang  阅读(761)  评论(0)    收藏  举报