首先说一下背景信息。云认证平台是一个传统的Spring项目,从上到下可以分为:API层(对应Spring Controller)、Service层、DAO层(MyBatis)几层。为了方便处理,从开发规范上我们要求了Service层的异常在Service层自行处理。
为了实现这一点,在接口定义上,从API层调用Service层的请求会有统一的返回基类:CommonResult
。其定义如下:
1public class CommonResult implements Serializable {
2 private static final long serialVersionUID = 1L;
3
4 /**
5 * success 请求成功与否的标志。
6 */
7 protected boolean success = Boolean.FALSE;
8 /**
9 * resultCode 返回码。
10 */
11 protected String resultCode = "";
12 /**
13 * message 详细信息。
14 */
15 protected String message = "";
16 ...
17}
所有需要从API层调用到的Service层的返回值都是该类或者该类的子类。如果是Service层之间互相调用,则不受此限制。
本章的内容主要与AOP有关。
1. 传统Spring中的处理
基本的背景介绍完毕。为了在Service层处理异常,实现了一个ServiceExceptionHandler
,专门拦截异常,并根据异常类型,设置返回值中的相应字段;然后通过AOP功能将功能织入整个流程。
1.1 ServiceExceptionHandler
1public class ServiceExceptionHandler {
2 private static Logger logger = LoggerFactory.getLogger(ServiceExceptionHandler.class);
3
4 /**
5 * 异常处理。使用aop:around进行拦截,当方法执行过程中出错的时候可以根据异常类型生成返回值。
6 *
7 * @param joinPoint a {@link org.aspectj.lang.ProceedingJoinPoint} object.
8 * @throws java.lang.ClassNotFoundException if any.
9 * @throws java.lang.IllegalAccessException if any.
10 * @throws java.lang.InstantiationException if any.
11 * @return a {@link java.lang.Object} object.
12 */
13 public Object processAndCatchException(ProceedingJoinPoint joinPoint) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
14 Object result = null;
15 Signature signature = joinPoint.getSignature();
16 @SuppressWarnings("rawtypes")
17 Class returnType = ((MethodSignature) signature).getReturnType();
18 try {
19 result = joinPoint.proceed();
20 } catch (DataAccessException e) {
21 logger.error("Database exception occured: ", e);
22 result = prepareResult(returnType, ResultCode.DATABASE_ERROR);
23 }
24 catch (Exception e) {
25 logger.error("Unknow exception occured:", e);
26 result = prepareResult(returnType, ResultCode.UNKNOW_ERROR);
27 } catch (Throwable e) {
28 logger.error("Unknown throwable occured:", e);
29 result = prepareResult(returnType, ResultCode.UNKNOW_ERROR);
30 }
31 return result;
32 }
33
34 /**
35 * 准备返回值
36 * @param returnType
37 * @param errorCode
38 * @return
39 */
40 private Object prepareResult(@SuppressWarnings("rawtypes") Class returnType, ResultCode errorCode) {
41 Object result = null;
42 try {
43 result = Class.forName(returnType.getName()).newInstance();
44 if (!(result instanceof CommonResult)) {
45 logger.error("Return type is not subclass of CommonResult, please contact developer!!!");
46 return null;
47 }
48 CommonResult ret = (CommonResult)result;
49 ret.setResultCode(errorCode.getCode());
50 ret.setMessage(errorCode.getDesc());
51 ret.setSuccess(false);
52 } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
53 logger.error("prepareResult failed. ", e);
54 }
55 return result;
56 }
57}
1.2 AOP配置
然后通过Spring的AOP配置,将异常处理织入处理过程中:
1 <!-- Service Exception Handler -->
2 <bean id="serviceExceptionHandlerAspect" class="com.eveus.cloudauth.service.exception.ServiceExceptionHandler" />
3 <aop:config>
4 <aop:aspect ref="serviceExceptionHandlerAspect" order="1">
5 <aop:pointcut id="capServicePointcut" expression="execution(com.eveus.cloudauth.service.bean..* com.eveus.cloudauth.service.impl.*ServiceImpl..*(..))" />
6 <aop:around pointcut-ref="capServicePointcut" method="processAndCatchException"/>
7 <aop:pointcut id="uidServicePointCut" expression="execution(com.eveus.cloudauth.service.bean..* com.eveus.cloudauth.service.impl.uid.*ServiceImpl..*(..))" />
8 <aop:around pointcut-ref="uidServicePointCut" method="processAndCatchException"/>
9 </aop:aspect>
10 </aop:config>
2. SpringBoot中的处理
在SpringBoot中推荐的是基于Java的配置,不再推荐采用XML配置。因此需要稍微改写一下配置方法:
2.1 Bean配置
1@Configuration
2public class ServiceConfig {
3 // 生成ServiceExceptionHandler实例
4 @Bean(name="serviceExceptionHandler")
5 public ServiceExceptionHandler serviceExceptionHandler() throws Exception {
6 return new ServiceExceptionHandler();
7 }
8}
Bean配置比较简单,在一个@Configuration
注解的类中生成返回一个类对象即可。
2.2 AOP配置
AOP相关的配置大部分可以通过注解完成,例如:aop:aspect
使用@Aspect
替代;aop:pointcut
使用@Pointcut
代替;aop:around
使用@Around
代替。这些注解可以直接写入ServiceExceptionHandler
类。
经过修改,改写完的ServiceExceptionHandler如下:
1@Aspect
2@Order(1) // 优先级数字越小优先级越高。优先级越高越先执行。执行堆栈如下:高-低-serviceProcess-低-高。
3public class ServiceExceptionHandler {
4 private static Logger logger = LoggerFactory.getLogger(ServiceExceptionHandler.class);
5
6 // 拦截点定义。只拦截返回值为 ServiceResult 的方法。
7 @Pointcut("execution(com.eveus.cloudauth.service.bean..* com.eveus.cloudauth.service.impl.*ServiceImpl..*(..))")
8 public void capServiceProcess() {};
9
10 @Pointcut("execution(com.eveus.cloudauth.service.bean..* com.eveus.cloudauth.service.impl.uid.*ServiceImpl..*(..))")
11 public void uidServiceProcess() {};
12
13
14 /**
15 * 异常处理。使用aop:around进行拦截,当方法执行过程中出错的时候可以根据异常类型生成返回值。
16 *
17 * @param joinPoint a {@link org.aspectj.lang.ProceedingJoinPoint} object.
18 * @throws java.lang.ClassNotFoundException if any.
19 * @throws java.lang.IllegalAccessException if any.
20 * @throws java.lang.InstantiationException if any.
21 * @return a {@link java.lang.Object} object.
22 */
23 @Around("capServiceProcess() || uidServiceProcess()")
24 public Object processAndCatchException(ProceedingJoinPoint joinPoint) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
25 Object result = null;
26 Signature signature = joinPoint.getSignature();
27 @SuppressWarnings("rawtypes")
28 Class returnType = ((MethodSignature) signature).getReturnType();
29 try {
30 result = joinPoint.proceed();
31 } catch (DataAccessException e) {
32 logger.error("Database exception occured: ", e);
33 result = prepareResult(returnType, ResultCode.DATABASE_ERROR);
34 }
35 catch (Exception e) {
36 logger.error("Unknow exception occured:", e);
37 result = prepareResult(returnType, ResultCode.UNKNOW_ERROR);
38 } catch (Throwable e) {
39 logger.error("Unknown throwable occured:", e);
40 result = prepareResult(returnType, ResultCode.UNKNOW_ERROR);
41 }
42 return result;
43 }
44
45 /**
46 * 准备返回值
47 * @param returnType
48 * @param errorCode
49 * @return
50 */
51 private Object prepareResult(@SuppressWarnings("rawtypes") Class returnType, ResultCode errorCode) {
52 Object result = null;
53 try {
54 result = Class.forName(returnType.getName()).newInstance();
55 if (!(result instanceof CommonResult)) {
56 logger.error("Return type is not subclass of CommonResult, please contact developer!!!");
57 return null;
58 }
59 CommonResult ret = (CommonResult)result;
60 ret.setResultCode(errorCode.getCode());
61 ret.setMessage(errorCode.getDesc());
62 ret.setSuccess(false);
63 } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
64 logger.error("prepareResult failed. ", e);
65 }
66 return result;
67 }
68}
注意一下其中几个注解的使用方法。更多详细的内容可以参考:Spring Framework Core: AOP。