Spring MVC Controller异常统一处理:AOP or ExceptionHandler

        Spring MVC项目开发过程中,为了不抛出异常堆栈信息给前端页面,每次编写Controller层代码都要尽可能的catch住所有service层、dao层等异常,代码耦合性较高,且不美观,不利于后期维护。为解决该问题,计划将Controller层异常信息统一封装处理,且能区分对待Controller层方法返回给前端的String、Map、JSONObject、ModelAndView等结果类型。鉴于项目有些http请求也需要返回json,通过AJAX请求头多出x-requested-with参数来区分http、ajax请求可能失效,而返回json数据在Controller层方法前面加ResponseBody较主流,于是考虑根据方法的ResponseBody注解来区分http、ajax请求,并封装自定义的异常码与异常信息。

1.AOP

        之前学习Spring,了解到切面可以很好的解决代码间的耦合,于是首先想到了运用AOP来解决前面所说的问题,为了不影响项目已有Controller中的业务处理,决定自定义一个controller接口IController,先处理实现了该接口的controller请求:

Spring MVC Controller异常统一处理:AOP or ExceptionHandler

        切面类,切入点用了  @Pointcut("this(com.zuche.carvmsicrm.common.mvc.exception.IController) && @annotation(org.springframework.web.bind.annotation.RequestMapping)"),不熟悉切面pointcut 表达式的同学可参考https://wenku.baidu.com/view/7e91a92d4b73f242336c5ff4.html

Spring MVC Controller异常统一处理:AOP or ExceptionHandler

        接下来就是主要需要处理的的异常封装逻辑了,需要注意的是异常的返回值要与切入的controller中RequestMapping对应的method返回值一致,所以要取request所对应的requestMappingMethod的returnType做判断,这次只处理 responseBody 是String、Map、JSONObject的情况,应该能含括所有需要返回json的情况了。

Spring MVC Controller异常统一处理:AOP or ExceptionHandler

       比较不好解决的可能是获得request所对应controller中的method。网上查阅了很多文档,大多集中在借助切点joinPoint中的controller对象、方法名,然后,通过java的反射机制来获得controller中的method。但实际上,这种方法并不能很好的支持方法的重载(试想一个controller中两个method的名称、参数个数都一致,只是处理的RequestMapping不同,此时,仅仅通过比较反射得到的方法名称和参数个数无法取到正确的method)。

       通过之前学习Spring MVC,了解到Spring其实集成了controller的request请求信息在RequestMappingHandlerMapping中,与是决定通过RequestMappingHandlerMapping来获得request所对应controller中的method。然而问题又来了,RequestMappingHandlerMapping集成了所有controller中的RequestMapping信息,难到逐一便利找到该method?这种方法必然不可行。翻阅RequestMappingHandlerMapping源码,其继承的AbstractHandlerMapping提供了getHandler方法来获得HandlerExecutionChain:
Spring MVC Controller异常统一处理:AOP or ExceptionHandler

        而HandlerExecutionChain中其实封装了HandlerMethod属性,只要调用该属性就能获得我们所需要的controller中的method,话不多说,直接上代码:

Spring MVC Controller异常统一处理:AOP or ExceptionHandler

        怎么样,是不是很简单粗暴?当然,这个方案可能也有问题,后续再进一步研究。至此,AOP实现了Controller层异常信息统一封装处理。需要注意的是,切面要生效,Controller层和你自己定义的切面,必须在相同的spring上下文中(https://blog.csdn.net/PYXLY1314/article/details/47152827),同时,不要忘记,使用cglib来做代理,实现aop:

Spring MVC Controller异常统一处理:AOP or ExceptionHandler

2.ExceptionHandler

        前面阐述了Controller层异常信息统一封装处理的AOP方案,这套方案需要添加glib做代理,实现aop。一个完整的java项目中,用到切面的地方肯定不止一处。为了封装Controller层异常信息,我们开启了glib代理,这对整个项目其它地方的运转会有什么影响?会不会有不好的影响?想到此处,决定开发一套备用方案,以防AOP方案出现问题。ExceptionHandler异常处理网上知识较多,此处不再累述。同样,为了减少对系统原有controller的影响,编写基类BaseController,需要封装异常信息Controller层继承BaseController,实现ExceptionHandler注解处理异常:

Spring MVC Controller异常统一处理:AOP or ExceptionHandler

        注意,ExceptionHandler注解的方法只能返回一种类型,这里统一返回了ModelAndView,以兼容

http、ajax请求。如此看来,ExceptionHandler封装Controller层异常信息似乎更简单一些,你觉得呢?