面向切面思想实现项目全局异常处理(简单切面+Spring提供的封装)


以前写项目的时候所谓,异常直接在各个层里面捕捉处理后向外返回错误信息,但是实际上有些运行时异常很容易被忽略,这样就会导致这些异常出现后会直接返回给调用方。当时的解决方案就是在 Controller 层的每个方法加 try-catch 块,捕捉所有的异常并处理后返回给调用方,但是这样处理起来比较麻烦,因为每个方法上都要加 try-catch,显得代码很不优雅。后来随着项目接触的多,有使用拦截器或者过滤器的,当然现在使用最多的还是切面,并且在 Spring Boot 中还提供了封装好的切面,实现即可。

1. 使用切面思想统一处理

使用切面的思想实现,主要的就两步,第一步是是编写切面的逻辑方法,第二步只要将切面作用到对应的控制层的方法上即可。当然在 Spring 中已经提供了对应实现封装,只要使用注解和配置即可,但是为了循序渐进的了解这个实现过程,还是先看一下原始的写法是什么样的。

1.1 原始写法

编写全局异常处理的实现类:

/*全局异常处理切面类*/
@Aspect
@Service
public class ExceptionAspectHandler {

    @Pointcut(value = "@annotation(com.itcrud.jpa.annotaion.MyExceptionHandler)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object exceptionHandle(ProceedingJoinPoint joinPoint) throws Throwable {
        //TODO 可以获取请求参数进行打印操作
        try {
            Object object = joinPoint.proceed();//执行业务方法
            //可以打印响应结果信息
            System.out.println("响应信息:"+ object);
            return object;
        } catch (ServiceException e) {
            //TODO 自定义异常处理逻辑
            return new Object();//模拟异常处理结果返回
        } catch (Exception e) {
            //TODO 系统异常处理逻辑
            return new Object();//模拟异常处理结果返回
        }
    }
}

上面的代码注释很清楚,注释写的也很详细,就是没有具体的业务处理代码,偷懒一下,有兴趣的自己补充。另外这里使用的注解方式实现,需要在控制层内的方法上加@MyExceptionHandler注解。但是这种实现是不友好的,可以考虑将注解加到类上,也可以考虑使用表达式,在这里感觉表达式是最给力的。

自定义注解代码:

@Target({ElementType.METHOD}) //只能作用在方法上的注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExceptionHandler {

}

如果使用表达式,则是使用如下的写法:

execution(* com.itcrud.blog.*.controller..*.*(..))

大概说一下这个表达式,在 blog 和 controller 中间加了一个*表示的是功能模块,如在一个大的 maven 项目下有 web、api 等,因为分类不同,这里的*可能就不同,所以使用通配符更好,如果你的项目没有涉及到这种分类,那就可以不同考虑这个*,直接去掉就好。

到这里一个原始的写法基本就是这样。

1.2 Spring 提供的方法

Spring 对于异常的处理切面给出了一套注解,以@ControllerAdvice@RestControllerAdvice为主。下面来介绍一下。

  • @ControllerAdvice@RestControllerAdvice是直接写在异常处理类上的,类似于上面的切面类,这两个注解的区别就是在于有没有@ResponseBody。可以类比@Controller@RestController之间的区别。
  • @ExceptionHandler是用在具体处理异常的方法上,可以通过 value 属性来指定此方法处理的异常类型。
  • @ResponseStatus是用在具体处理异常的方法上,指定响应给调用方的状态吗,如 404、500 等,状态码的指定使用 HttpStatus 枚举类。
  • @ResponseBody可以用在处理异常的方法上,对响应的结果进行 json 的转换,如果在类上使用了@RestControllerAdvice注解,方法上就不用加@ResponseBody啦。(但是这里还是建议分开写,细化)

示例代码:

@ControllerAdvice
public class WebExceptionHandler {

    /*自定义异常处理*/
    @ExceptionHandler(value = ServiceException.class)
    @ResponseBody
    public Map<String,String> localHandle(){
        return new HashMap<String, String>();
    }

    /*空指针异常处理*/
    @ExceptionHandler(NullPointerException.class)
    @ResponseBody
    public String NullPointHandle(){
        return "";
    }

    /*最终异常处理*/
    @ExceptionHandler(Exception.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public String exceptionHandle(){
     return "";
    }
}

上面的一段简洁代码就能实现,不需要在控制层内加任何注解,这个类会对被@RequestMapping作用的方法自动拦截,实现异常的统一处理。

这种处理方式让我想起了一个问题,如果现在控制层里面定义了一个方法,这个方法是控制层中的一个公用方法,但是不处理外部的请求,使用之前原始写法的表达式方式就会出现问题,这个公用方法也会被切面切到,是不符合设计效果的,因此还是要在切面中去判断被切到的方法是否被@RequestMapping系列的注解修饰。

总的来说,Spring 这个叫春的东西真的很好用,提供的功能都很强大。

2. 总结

在用原始写法的时候,一定要注意加上必要的判断,防止出现一些预想不到的问题。合理使用第三方提供的功能封装能够大大的提高开发的效率。

其实这个原始写法还是很有用的,正如我注释中写的,可以将请求的出参入参打印出来,另外的就是可以做日志编号的统一管理,实现日志链,方便在日志中查找。当然了这里也可以通过实现 Spring 的 HandlerInterceptor 接口,然后重写preHandlepostHandle以及afterCompletion方法来实现。总感觉 Spring 什么地方都可以插一腿。


文章作者: 程序猿洞晓
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 程序猿洞晓 !
评论
 上一篇
Spring Data JPA使用必备(二):Spring Data JPA方法命名规则实现SQL自动生成 Spring Data JPA使用必备(二):Spring Data JPA方法命名规则实现SQL自动生成
Spring data JPA是一个好东西,但是对于很多习惯于写SQL,直接怼数据库的人来说,这个真的用不习惯,还被一致认为是一个不易于程序员发展的技术。因为JPA提供了标准的封装,在操作数据库的时候,不需要写SQL,完全通过操作对象即可完成。久而久之,SQL就会被慢慢的遗忘,生疏,等以后面试的时候,也许这就是上升的一个短板。现在……
下一篇 
Spring Data JPA使用必备(一):Spring Data JPA整合Spring以及简单的使用 Spring Data JPA使用必备(一):Spring Data JPA整合Spring以及简单的使用
最近公司部分新开的项目需要使用Spring data JPA技术,作为一个从来没有用过JPA的小白来说,需要重新的学习。N年前简单的看过JPA,这么多年没用过,完全忘记了有木有。接下来的系列文章就一起来整理一下。使用了@Entity注解后会默认映射到数据库的表,按照驼峰规则,如类名是CustomerBaseInfo,那么就会对应到表名为……
  目录