您的位置:首页技术文章
文章详情页

使用spring aop统一处理异常和打印日志方式

浏览:3日期:2023-07-09 17:07:58
我们很容易写出的代码

我们很容易写出带有很多try catch 和 logger.warn(),logger.error()的代码,这样一个方法本来的业务逻辑只有5行,有了这些,代码就变成了10行或者更多行,如:

public ResultDTO<UserDTO> queryUserByCardId(String cardId) {ResultDTO<UserDTO> result = new ResultDTO<UserDTO>();StringBuilder log = new StringBuilder();log.append('queryUserByCardId:' + cardId);try { checkCardIdNotNull(cardId); StationUserDO userDO = userDAO.queryUserByCardId(cardId); UserDTO stationUserDTO = DataTypeConvertUtils.DOToDTO(userDO); result.setData(stationUserDTO); logger.warn(log.append(' result:').toString() + result);} catch (StationErrorCodeException e) { //logger.error(log.append('catch StationErrorCodeException!').toString(), e); result.setSuccess(false); result.setErrorCode(e.getErrorCode().getErrorCode()); result.setErrorMessage(e.getErrorCode().getErrorMessage());} catch (Exception e) { logger.error(log.append('catch Exception!').toString(), e); result.setSuccess(false); result.setErrorCode(StationErrorCodeConstants.STA10001.getErrorCode()); result.setErrorMessage(StationErrorCodeConstants.STA10001.getErrorMessage());}return result;}

实际上,我们的业务逻辑就几行而已,中间却夹杂着那么多的异常处理代码及日志信息代码。

如何改进代码

我们可以使用springaop,做一个切面,这个切面专门做记录日志和异常处理的工作,这样就能减少重复代码。

代码如下:

@Overridepublic ResultDTO<StationUserDTO>queryUserByCardId(String cardId) {ResultDTO<StationUserDTO> result = new ResultDTO<StationUserDTO>();checkCardIdNotNull(cardId);StationUserDO userDO = stationUserDAO.queryStationUserByCardId(cardId);StationUserDTO stationUserDTO = DataTypeConvertUtils.DOToDTO(userDO);result.setData(stationUserDTO);return result;}

我们在切面中做异常处理和记录日志:

@Aspectpublic class CardServiceAspect { private final Logger logger = LoggerFactory.getLogger('card'); // 切入点表达式按需配置 @Pointcut('execution(* *.*(..)))') private void myPointcut() { } @Before('execution(* *.*(..)))') public void before(JoinPoint joinPoint) {String className = joinPoint.getTarget().getClass().getName();String methodName = joinPoint.getSignature().getName();logger.warn(className + '的' + methodName + '执行了');Object[] args = joinPoint.getArgs();StringBuilder log = new StringBuilder('入参为');for (Object arg : args) { log.append(arg + ' ');}logger.warn(log.toString()); } @AfterReturning(value = 'execution(* *.*(..)))', returning = 'returnVal') public void afterReturin(Object returnVal) {logger.warn('方法正常结束了,方法的返回值:' + returnVal); } @AfterThrowing(value = 'StationCardServiceAspect.myPointcut()', throwing = 'e') public void afterThrowing(Throwable e) {if (e instanceof StationErrorCodeException) { logger.error('通知中发现异常StationErrorCodeException', e);} else { logger.error('通知中发现未知异常', e);} } @Around(value = 'StationCardServiceAspect.myPointcut()') public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {logger.warn('前置增强...');Object result = null;try { result = proceedingJoinPoint.proceed();} catch (Exception e) { ResultDTO resultDTO = new ResultDTO(); if (e instanceof StationErrorCodeException) {StationErrorCodeException errorCodeException = (StationErrorCodeException) e;resultDTO.setSuccess(false);resultDTO.setErrorCode(errorCodeException.getErrorCode().getErrorCode());resultDTO.setErrorMessage(errorCodeException.getErrorCode().getErrorMessage()); } else {resultDTO.setSuccess(false);resultDTO.setErrorCode(StationErrorCodeConstants.STA10001.getErrorCode());resultDTO.setErrorMessage(StationErrorCodeConstants.STA10001.getErrorMessage()); } return resultDTO;}return result; }}

然后我们在spring配置文件中配置切面

<!-- 配置切面的类 --><bean /><!-- 配置成注解方式寻找要被代理的对象 --><aop:aspectj-autoproxy/>

这样,我们就可以统一处理异常和日志了。

不足点

利用这种方式,只能打入参和出参,还有抛出异常时打异常日志,不能打方法运行中的中间值,目前我只能想到,方法中间值的日志,就是用原来的方式打出,不知道大家有没有什么好的方法。

spring aop的其他使用

推荐使用aspectJ来完成面向切面编程。我们还可以利用aop完成其他功能如记录程序运行时间等。

aop实现统一记录请求方法返回值日志及统一异常处理

接到将请求返回值写入到日志方便查问题需求,首先考虑的是用拦截器实现,无奈拦截器postHandle方法里获取不到返回值就此作罢。

继续寻找新的方法,网上查询一番找到一个便捷的方法,利用log4j2,在log4j2.xml配置文件里添加如下配置:

<AsyncLogger name='org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor' level='debug' additivity='false'> <AppenderRef ref='Console'/> <AppenderRef ref='allLog'/> </AsyncLogger>

这样就能将方法返回值记录到日志里了,但是这样记录的日志和系统其它日志不一样不方便查看,此方法pass。最后只能用spring aop来实现此功能了,步骤如下:

1、引入aop依赖的jar包

<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.0.0.RELEASE</version></dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.0.0.RELEASE</version></dependency>2、配置xml文件

引入aop命名空间

xmlns:aop='http://www.springframework.org/schema/aop'http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-4.0.xsd<!-- 开启自动切面代理 --><aop:aspectj-autoproxy/><!-- 使用annotation 自动注册bean --><context:component-scan base-package='com.zzz.dealer.**'/>3、编写切面类

@Aspect //指定当前类为切面类@Component //把普通pojo实例化到spring容器中,相当于配置文件中的<bean id='' class=''/>public class MethodLogAndExceptionAop { @Around('@annotation(org.springframework.web.bind.annotation.RequestMapping)') public Object around(ProceedingJoinPoint jp) throws Throwable {String targetName = jp.getTarget().getClass().getName();String methodName = jp.getSignature().getName();Object[] arguments = jp.getArgs();Object[] args = new Object[arguments.length];for (int i = 0; i < arguments.length; i++) { if (arguments[i] instanceof ServletRequest || arguments[i] instanceof ServletResponse || arguments[i] instanceof MultipartFile) { //ServletRequest不能序列化,从入参里排除,否则报异常:java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false) //ServletResponse不能序列化 从入参里排除,否则报异常:java.lang.IllegalStateException: getOutputStream() has already been called for this responsecontinue; } args[i] = arguments[i];}Object result = null;try { //StopWatch 计时 StopWatch clock = new StopWatch(); clock.start(); result = jp.proceed(); clock.stop(); long executeTime = clock.getTime(); LoggerUtil.info(targetName, methodName, '调用Controller方法返回结果', result, executeTime, args);} catch (Exception exception) { LoggerUtil.error(targetName, methodName, '统一异常处理', exception, args); ResultVo resultVo = new ResultVo(false); // 为安全起见,只有业务异常我们对前端可见,否则统一归为系统异常 if (exception instanceof BusinessException) {resultVo.setResultAndCode(false, ((BusinessException) exception).getErrorCode(), ((BusinessException) exception).getErrorMessage()); } else {resultVo.setResultAndCode(false, ErrorCode.DEALER_ERR_100000.getCode(), '系统异常,请联系管理员'); } result = resultVo;}return result; }}

系统本来的统一异常处理是通过实现HandlerExceptionResolver接口自定义异常处理,实现这个aop后发现,在这里也可以实现系统异常统一处理,于是就把自定义异常处理给干掉了。一举两得。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持好吧啦网。

标签: Spring
相关文章: