利用spring aop統一處理異常和打日誌
spring aop的概念,很早就寫博客介紹了,現在在工作中真正使用。
我們很容易寫出的代碼
我們很容易寫出帶有很多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,做一個切面,這個切面專門做記錄日誌和異常處理的工作,這樣就能減少重複代碼。
代碼如下:
@Override
public 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;
}
我們在切面中做異常處理和記錄日誌:
@Aspect
public 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 id="serviceAspect" class="com.lirui.StationCardServiceAspect"/>
<!-- 配置成註解方式尋找要被代理的對象 -->
<aop:aspectj-autoproxy/>
這樣,我們就可以統一處理異常和日誌了。
不足點
利用這種方式,只能打入參和出參,還有拋出異常時打異常日誌,不能打方法運行中的中間值,目前我只能想到,方法中間值的日誌,就是用原來的方式打出,不知道大家有沒有什麼好的方法。
spring aop的其他使用
推薦使用aspectJ來完成面向切面編程。我們還可以利用aop完成其他功能如記錄程序運行時間等。