迁移到SpringBoot 02 - 异常处理

首先说一下背景信息。云认证平台是一个传统的Spring项目,从上到下可以分为:API层(对应Spring Controller)、Service层、DAO层(MyBatis)几层。为了方便处理,从开发规范上我们要求了Service层的异常在Service层自行处理。

为了实现这一点,在接口定义上,从API层调用Service层的请求会有统一的返回基类:CommonResult。其定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CommonResult implements Serializable {
private static final long serialVersionUID = 1L;

/**
* success 请求成功与否的标志。
*/
protected boolean success = Boolean.FALSE;
/**
* resultCode 返回码。
*/
protected String resultCode = "";
/**
* message 详细信息。
*/
protected String message = "";
...
}

所有需要从API层调用到的Service层的返回值都是该类或者该类的子类。如果是Service层之间互相调用,则不受此限制。

本章的内容主要与AOP有关。

1. 传统Spring中的处理

基本的背景介绍完毕。为了在Service层处理异常,实现了一个ServiceExceptionHandler,专门拦截异常,并根据异常类型,设置返回值中的相应字段;然后通过AOP功能将功能织入整个流程。

1.1 ServiceExceptionHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class ServiceExceptionHandler {
private static Logger logger = LoggerFactory.getLogger(ServiceExceptionHandler.class);

/**
* 异常处理。使用aop:around进行拦截,当方法执行过程中出错的时候可以根据异常类型生成返回值。
*
* @param joinPoint a {@link org.aspectj.lang.ProceedingJoinPoint} object.
* @throws java.lang.ClassNotFoundException if any.
* @throws java.lang.IllegalAccessException if any.
* @throws java.lang.InstantiationException if any.
* @return a {@link java.lang.Object} object.
*/
public Object processAndCatchException(ProceedingJoinPoint joinPoint) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Object result = null;
Signature signature = joinPoint.getSignature();
@SuppressWarnings("rawtypes")
Class returnType = ((MethodSignature) signature).getReturnType();
try {
result = joinPoint.proceed();
} catch (DataAccessException e) {
logger.error("Database exception occured: ", e);
result = prepareResult(returnType, ResultCode.DATABASE_ERROR);
}
catch (Exception e) {
logger.error("Unknow exception occured:", e);
result = prepareResult(returnType, ResultCode.UNKNOW_ERROR);
} catch (Throwable e) {
logger.error("Unknown throwable occured:", e);
result = prepareResult(returnType, ResultCode.UNKNOW_ERROR);
}
return result;
}

/**
* 准备返回值
* @param returnType
* @param errorCode
* @return
*/
private Object prepareResult(@SuppressWarnings("rawtypes") Class returnType, ResultCode errorCode) {
Object result = null;
try {
result = Class.forName(returnType.getName()).newInstance();
if (!(result instanceof CommonResult)) {
logger.error("Return type is not subclass of CommonResult, please contact developer!!!");
return null;
}
CommonResult ret = (CommonResult)result;
ret.setResultCode(errorCode.getCode());
ret.setMessage(errorCode.getDesc());
ret.setSuccess(false);
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
logger.error("prepareResult failed. ", e);
}
return result;
}
}

1.2 AOP配置

然后通过Spring的AOP配置,将异常处理织入处理过程中:

1
2
3
4
5
6
7
8
9
10
<!-- Service Exception Handler -->
<bean id="serviceExceptionHandlerAspect" class="com.eveus.cloudauth.service.exception.ServiceExceptionHandler" />
<aop:config>
<aop:aspect ref="serviceExceptionHandlerAspect" order="1">
<aop:pointcut id="capServicePointcut" expression="execution(com.eveus.cloudauth.service.bean..* com.eveus.cloudauth.service.impl.*ServiceImpl..*(..))" />
<aop:around pointcut-ref="capServicePointcut" method="processAndCatchException"/>
<aop:pointcut id="uidServicePointCut" expression="execution(com.eveus.cloudauth.service.bean..* com.eveus.cloudauth.service.impl.uid.*ServiceImpl..*(..))" />
<aop:around pointcut-ref="uidServicePointCut" method="processAndCatchException"/>
</aop:aspect>
</aop:config>

2. SpringBoot中的处理

在SpringBoot中推荐的是基于Java的配置,不再推荐采用XML配置。因此需要稍微改写一下配置方法:

2.1 Bean配置

1
2
3
4
5
6
7
8
@Configuration
public class ServiceConfig {
// 生成ServiceExceptionHandler实例
@Bean(name="serviceExceptionHandler")
public ServiceExceptionHandler serviceExceptionHandler() throws Exception {
return new ServiceExceptionHandler();
}
}

Bean配置比较简单,在一个@Configuration注解的类中生成返回一个类对象即可。

2.2 AOP配置

AOP相关的配置大部分可以通过注解完成,例如:aop:aspect使用@Aspect替代;aop:pointcut使用@Pointcut代替;aop:around使用@Around代替。这些注解可以直接写入ServiceExceptionHandler类。

经过修改,改写完的ServiceExceptionHandler如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Aspect
@Order(1) // 优先级数字越小优先级越高。优先级越高越先执行。执行堆栈如下:高-低-serviceProcess-低-高。
public class ServiceExceptionHandler {
private static Logger logger = LoggerFactory.getLogger(ServiceExceptionHandler.class);

// 拦截点定义。只拦截返回值为 ServiceResult 的方法。
@Pointcut("execution(com.eveus.cloudauth.service.bean..* com.eveus.cloudauth.service.impl.*ServiceImpl..*(..))")
public void capServiceProcess() {};

@Pointcut("execution(com.eveus.cloudauth.service.bean..* com.eveus.cloudauth.service.impl.uid.*ServiceImpl..*(..))")
public void uidServiceProcess() {};


/**
* 异常处理。使用aop:around进行拦截,当方法执行过程中出错的时候可以根据异常类型生成返回值。
*
* @param joinPoint a {@link org.aspectj.lang.ProceedingJoinPoint} object.
* @throws java.lang.ClassNotFoundException if any.
* @throws java.lang.IllegalAccessException if any.
* @throws java.lang.InstantiationException if any.
* @return a {@link java.lang.Object} object.
*/
@Around("capServiceProcess() || uidServiceProcess()")
public Object processAndCatchException(ProceedingJoinPoint joinPoint) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Object result = null;
Signature signature = joinPoint.getSignature();
@SuppressWarnings("rawtypes")
Class returnType = ((MethodSignature) signature).getReturnType();
try {
result = joinPoint.proceed();
} catch (DataAccessException e) {
logger.error("Database exception occured: ", e);
result = prepareResult(returnType, ResultCode.DATABASE_ERROR);
}
catch (Exception e) {
logger.error("Unknow exception occured:", e);
result = prepareResult(returnType, ResultCode.UNKNOW_ERROR);
} catch (Throwable e) {
logger.error("Unknown throwable occured:", e);
result = prepareResult(returnType, ResultCode.UNKNOW_ERROR);
}
return result;
}

/**
* 准备返回值
* @param returnType
* @param errorCode
* @return
*/
private Object prepareResult(@SuppressWarnings("rawtypes") Class returnType, ResultCode errorCode) {
Object result = null;
try {
result = Class.forName(returnType.getName()).newInstance();
if (!(result instanceof CommonResult)) {
logger.error("Return type is not subclass of CommonResult, please contact developer!!!");
return null;
}
CommonResult ret = (CommonResult)result;
ret.setResultCode(errorCode.getCode());
ret.setMessage(errorCode.getDesc());
ret.setSuccess(false);
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
logger.error("prepareResult failed. ", e);
}
return result;
}
}

注意一下其中几个注解的使用方法。更多详细的内容可以参考:Spring Framework Core: AOP

附录、参考资料

热评文章