异常处理实践

使用AOP对异常进行捕捉处理

Posted by wenfengSAT on March 26, 2018

这是对前一段时间异常处理的一个总结

java异常分为两类 运行时异常和受检异常。两种异常的应用场景如下:

运行时异常: 调用代码不能继续执行,需要立即终止。通常是由于错误的操作,比如数据库connectionURL写错了等等。

受检异常: 调用代码需要进一步处理和恢复,需要显式捕捉,并且在代码产生异常后清理资源(finally语句块)。

Original method



    public void doSomething() throws SomeException {
        ///这是一个普通方法,声明了受检异常
    }


为了调用doSomething,可以用try catch包裹doSomething()



    public void doSomethingElse(){
        try {
            doSomething();
        } catch (SomeException e) {
            e.printStackTrace();
        }
    }


为了调用doSomething,也可以声明受检异常



    public void doSomethingElse() throws SomeException {
        doSomething();
    }


但是这两种方式不是每种情况都适用的,假如是类似FileOutputStream这样的需要释放资源的操作一般来说应该使用try catch包裹。

另外:Java 提供了在try语句块中自动释放资源的功能,类似C#的using()语句块,但是不如using()优雅。这样可以避免手动释放资源(还是比之前好一点的



        try (OutputStream outputStream = new FileOutputStream(filePath)) {
            //
        } catch (IOException e) {
            //
        }


另外的另外: Lombok提供了@Cleanup注解,更加方便了资源释放,下面是栗子:



    public static boolean isImage(@NotNull final File tempFile)
            throws Exception {
        @Cleanup ImageInputStream imageInputStream = ImageIO.createImageInputStream(tempFile);
        return imageInputStream != null;
    }


然后就是对Repository/DAO/Service设计的一点思考:

操作数据库的操作一般都要有事务控制,失败了要回滚,刚开始是手写事务,发现代码很难看!事务管理加上try catch弄得代码很复杂,而实实在在的业务代码就埋没在这一坨代码中了。

后来知道Spring有事务注解@Transactional(此处不展开

这样的话代码中就不能有try catch,因为一旦捕获了异常(没有再次抛出),Spring就会认为没有抛异常,程序正确运行从而提交了事务,导致问题。

所以:我是这么做的,使用Spring AOP的方式记录异常日志记录到数据库中并用@ControllerAdvice处理错误页面响应:

AOP的方式记录异常日志



@Aspect
@Component
@Slf4j
public class LogAspect {
    @AfterThrowing(throwing = "ex", pointcut = "execution(* com.github.izhangzhihao.SpringMVCSeedProject.*.*.*(..)))")
    public void LogToDB(JoinPoint joinPoint, Throwable ex) {
        //出错行
        int lineNumber = ex.getStackTrace()[0].getLineNumber();
        //方法签名
        Signature signature = joinPoint.getSignature();
        //参数
        Object[] args = joinPoint.getArgs();
        //拼接参数
        StringBuilder argString = new StringBuilder();
        if (args.length > 0)
            for (Object arg : args)
                argString.append(arg);
        log.error("方法" + signature, "参数" + argString, "错误行:" + lineNumber, "时间" + new Date(), "异常内容" + ex.toString());
    }
}


@ControllerAdvice处理错误页面响应



@ControllerAdvice
public class HandlerExceptionController {

    /**
     * 无权限访问跳转
     */
    @ExceptionHandler({UnauthorizedException.class})
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public ModelAndView handlerUnauthenticatedException() {
        return new ModelAndView("../../403");
    }

    /**
     * 全局Controller异常处理
     * @param ex 异常
     * @return 跳转出错页面
     */
    @ExceptionHandler({Exception.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ModelAndView handlerExceptionMethod(Exception ex) {

        //将错误信息记录到数据库
        LogToDB(ex);

        ModelAndView modelAndView = new ModelAndView("../../500");
        modelAndView.addObject("MSG", ex.toString());
        modelAndView.addObject("Line", ex.getStackTrace()[0].getLineNumber());
        modelAndView.addObject("Method", ex.getStackTrace()[0].getMethodName());
        Writer writer = new StringWriter();
        //客户端输出一下,打开F12可以看到
        ex.printStackTrace(new PrintWriter(writer));
        modelAndView.addObject("detailed", writer.toString());
        return modelAndView;
    }
}


这样依赖几乎不需要手动处理异常,代码变得干净,可以专一的处理业务逻辑。