使用spring aop統(tǒng)一處理異常和打印日志方式
我們很容易寫出的代碼
我們很容易寫出帶有很多try catch 和 logger.warn(),logger.error()的代碼,這樣一個方法本來的業(yè)務(wù)邏輯只有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;
}
實際上,我們的業(yè)務(wù)邏輯就幾行而已,中間卻夾雜著那么多的異常處理代碼及日志信息代碼。
如何改進代碼
我們可以使用springaop,做一個切面,這個切面專門做記錄日志和異常處理的工作,這樣就能減少重復(fù)代碼。
代碼如下:
@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 + "執(zhí)行了");
Object[] args = joinPoint.getArgs();
StringBuilder log = new StringBuilder("入?yún)?);
for (Object arg : args) {
log.append(arg + " ");
}
logger.warn(log.toString());
}
@AfterReturning(value = "execution(* *.*(..)))", returning = "returnVal")
public void afterReturin(Object returnVal) {
logger.warn("方法正常結(jié)束了,方法的返回值:" + returnVal);
}
@AfterThrowing(value = "StationCardServiceAspect.myPointcut()", throwing = "e")
public void afterThrowing(Throwable e) {
if (e instanceof StationErrorCodeException) {
logger.error("通知中發(fā)現(xiàn)異常StationErrorCodeException", e);
} else {
logger.error("通知中發(fā)現(xiàn)未知異常", 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/>
這樣,我們就可以統(tǒng)一處理異常和日志了。
不足點
利用這種方式,只能打入?yún)⒑统鰠ⅲ€有拋出異常時打異常日志,不能打方法運行中的中間值,目前我只能想到,方法中間值的日志,就是用原來的方式打出,不知道大家有沒有什么好的方法。
spring aop的其他使用
推薦使用aspectJ來完成面向切面編程。我們還可以利用aop完成其他功能如記錄程序運行時間等。
aop實現(xiàn)統(tǒng)一記錄請求方法返回值日志及統(tǒng)一異常處理
接到將請求返回值寫入到日志方便查問題需求,首先考慮的是用攔截器實現(xiàn),無奈攔截器postHandle方法里獲取不到返回值就此作罷。
繼續(xù)尋找新的方法,網(wǎng)上查詢一番找到一個便捷的方法,利用log4j2,在log4j2.xml配置文件里添加如下配置:
<AsyncLogger name="org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="allLog"/>
</AsyncLogger>
這樣就能將方法返回值記錄到日志里了,但是這樣記錄的日志和系統(tǒng)其它日志不一樣不方便查看,此方法pass。最后只能用spring aop來實現(xiàn)此功能了,步驟如下:
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/aop http://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 //指定當(dāng)前類為切面類
@Component //把普通pojo實例化到spring容器中,相當(dāng)于配置文件中的<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不能序列化,從入?yún)⒗锱懦?,否則報異常: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不能序列化 從入?yún)⒗锱懦駝t報異常:java.lang.IllegalStateException: getOutputStream() has already been called for this response
continue;
}
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, "調(diào)用Controller方法返回結(jié)果", result, executeTime, args);
} catch (Exception exception) {
LoggerUtil.error(targetName, methodName, "統(tǒng)一異常處理", exception, args);
ResultVo resultVo = new ResultVo(false);
// 為安全起見,只有業(yè)務(wù)異常我們對前端可見,否則統(tǒng)一歸為系統(tǒng)異常
if (exception instanceof BusinessException) {
resultVo.setResultAndCode(false, ((BusinessException) exception).getErrorCode(), ((BusinessException) exception).getErrorMessage());
} else {
resultVo.setResultAndCode(false, ErrorCode.DEALER_ERR_100000.getCode(), "系統(tǒng)異常,請聯(lián)系管理員");
}
result = resultVo;
}
return result;
}
}
系統(tǒng)本來的統(tǒng)一異常處理是通過實現(xiàn)HandlerExceptionResolver接口自定義異常處理,實現(xiàn)這個aop后發(fā)現(xiàn),在這里也可以實現(xiàn)系統(tǒng)異常統(tǒng)一處理,于是就把自定義異常處理給干掉了。一舉兩得。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
java中l(wèi)ambda(函數(shù)式編程)一行解決foreach循環(huán)問題
這篇文章主要介紹了java中l(wèi)ambda(函數(shù)式編程)一行解決foreach循環(huán)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
在Window系統(tǒng)下安裝Netbeans9的方法
今天小編就為大家分享一篇關(guān)于在Window系統(tǒng)下安裝Netbeans9的方法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12
如何解決通過spring-boot-maven-plugin package失敗問題
這篇文章主要介紹了如何解決通過spring-boot-maven-plugin package失敗問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04
Springboot整合Flowable6.x導(dǎo)出bpmn20的步驟詳解
這篇文章主要介紹了Springboot整合Flowable6.x導(dǎo)出bpmn20,Flowable流程引擎可用于部署B(yǎng)PMN 2.0流程定義,可以十分靈活地加入你的應(yīng)用/服務(wù)/構(gòu)架,本文給出兩種從flowable導(dǎo)出流程定義bpmn20.xml的方式,需要的朋友可以參考下2023-04-04

