項(xiàng)目開發(fā)中我們需要記錄各個(gè)服務(wù)的調(diào)用日志,作為審計(jì)記錄或者供debug查看,或者性能以及使用率分析等等。通過記錄日志和異常,我們能找出,哪些功能在哪個(gè)時(shí)間段被哪些模塊調(diào)用,入?yún)⒍加心男?,反?yīng)時(shí)間多長,這樣我們就能比較快的找出項(xiàng)目問題所在或者優(yōu)化項(xiàng)目。那么如何實(shí)現(xiàn)這種功能,Spring AOP給我們提供了現(xiàn)成的方法。
當(dāng)然實(shí)現(xiàn)的方法有很多,最直接的莫過于在每個(gè)調(diào)用的進(jìn)入和對(duì)出都記錄一天日志(logger),然后通過通過日志的分析,但是每個(gè)方法都需要寫日志記錄的代碼,顯得很臃腫重復(fù),因?yàn)橛涗浫罩镜拇a都是類似的,就是記錄參數(shù)和退出時(shí)間。 而使用AOP我們只需要在一個(gè)記錄編寫記錄日志大代碼,其他地方加上注解就可以了,方便快捷,便于修改。
第一步,添加依賴
主要是在你的pom文件中添加如下依賴
<properties>
<org.aspectj-version>1.7.4</org.aspectj-version>
<cglib.version>3.1</cglib.version>
</properties>
....
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>${cglib.version}</version>
</dependency>
第二步,創(chuàng)建注解
我們?cè)诿總€(gè)注解的方法上加上描述這樣就更清楚知道這個(gè)方法是干什么的了。
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemLog {
String description() default "";
}
第三步,實(shí)現(xiàn)注解
需要注意的,本例只是為了說明如何使用AOP實(shí)現(xiàn)日志記錄,所以我們只是在控制臺(tái)打印日志,并沒有記錄到數(shù)據(jù)庫或者文件。實(shí)際項(xiàng)目中我們一般都是記錄到數(shù)據(jù)庫或者日志文件。
在展示具體實(shí)現(xiàn)前,簡要介紹一下幾種通知的作用。
1.前置通知
*(before advice, 也就是代碼中的 @Before(“serviceAspect()”)):在連接點(diǎn)前面執(zhí)行,對(duì)連接點(diǎn)不會(huì)造成影響(前置通知有異常的話,會(huì)對(duì)后續(xù)操作有影響)2.正常返回通知
(after returning advice, 也就是代碼中的 @AfterReturning(pointcut = “serviceAspect()”)):在連接點(diǎn)正確執(zhí)行之后執(zhí)行,如果連接點(diǎn)拋異常,則不執(zhí)行3.異常返回通知
(after throw Advice, 也就是代碼中的@AfterThrowing(pointcut = “serviceAspect()”, throwing = “e”)):在連接點(diǎn)拋異常的時(shí)候執(zhí)行4.返回通知
(after, 也就是代碼中的@After(“serviceAspect()”)):無論連接點(diǎn)是正確執(zhí)行還是拋異常,都會(huì)執(zhí)行。
具體示例代碼在這里,歡迎加星,fork, 謝謝!
package com.yq.exceptiondemo.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
/**
* Simple to Introduction
* className: SystemLogAspect
*
* @author EricYang
* @version 2018/6/9 19:43
*/
@Aspect
@Component
public class SystemLogAspect {
//本地異常日志記錄對(duì)象
private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect. class);
@Pointcut("@annotation(com.yq.exceptiondemo.config.SystemLog)")
public void serviceAspect() {
System.out.println("我是一個(gè)切入點(diǎn)");
}
/**
* 前置通知 用于攔截記錄用戶的操作
*
* @param joinPoint 切點(diǎn)
*/
@Before("serviceAspect()")
public void before(JoinPoint joinPoint) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpSession session = request.getSession();
//讀取session中的用戶 等其他和業(yè)務(wù)相關(guān)的信息,比如當(dāng)前用戶所在應(yīng)用,以及其他信息, 例如ip
String ip = request.getRemoteAddr();
try {
System.out.println("doBefore enter。 任何時(shí)候進(jìn)入連接點(diǎn)都調(diào)用");
System.out.println("method requested:" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));
System.out.println("method description:" + getServiceMethodDescription(joinPoint));
System.out.println("remote ip:" + ip);
//日志存入數(shù)據(jù)庫
System.out.println("doBefore end");
} catch (Exception e) {
logger.error("doBefore exception");
logger.error("exceptionMsg={}", e.getMessage());
}
}
/**
* 后通知(After advice) :當(dāng)某連接點(diǎn)退出的時(shí)候執(zhí)行的通知(不論是正常返回還是異常退出)。
* @param joinPoint
*/
@After("serviceAspect()")
public void after(JoinPoint joinPoint) {
System.out.println("after executed. 無論連接點(diǎn)正常退出還是異常退出都調(diào)用");
}
/**
* 后通知(After advice) :當(dāng)某連接點(diǎn)退出的時(shí)候執(zhí)行的通知。
* @param joinPoint
*/
@AfterReturning(pointcut = "serviceAspect()")
public void AfterReturnning(JoinPoint joinPoint)
{
System.out.println("AfterReturning executed。只有當(dāng)連接點(diǎn)正常退出時(shí)才調(diào)用");
Object[] objs = joinPoint.getArgs();
}
/**
* 異常通知 用于攔截層記錄異常日志
* @param joinPoint
* @param e
*/
@AfterThrowing(pointcut = "serviceAspect()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Throwable e) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpSession session = request.getSession();
String ip = request.getRemoteAddr();
String params = "";
if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
for ( int i = 0; i < joinPoint.getArgs().length; i++) {
params += (joinPoint.getArgs()[i]) + "; ";
}
}
try {
System.out.println("doAfterThrowing enter。 只有當(dāng)連接點(diǎn)異常退出時(shí)才調(diào)用");
System.out.println("exception class:" + e.getClass().getName());
System.out.println("exception msg:" + e.getMessage());
System.out.println("exception method:" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));
System.out.println("method description:" + getServiceMethodDescription(joinPoint));
System.out.println("remote ip:" + ip);
System.out.println("method parameters:" + params);
//日志存入數(shù)據(jù)庫
System.out.println("doAfterThrowing end");
} catch (Exception ex) {
logger.error("doAfterThrowing exception");
logger.error("exception msg={}", ex.getMessage());
}
logger.error("method={}, code={}, msg={}, params={}",
joinPoint.getTarget().getClass().getName() + joinPoint.getSignature().getName(), e.getClass().getName(), e.getMessage(), params);
}
/**
* 獲取注解中對(duì)方法的描述信息
*
* @param joinPoint 切點(diǎn)
* @return 方法描述
* @throws Exception
*/
public static String getServiceMethodDescription(JoinPoint joinPoint)
throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
String description = "";
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
description = method.getAnnotation(SystemLog.class).description();
break;
}
}
}
return description;
}
}
第四步,應(yīng)用注解
我們?cè)谛枰涗浫罩镜姆椒ㄉ霞由衔覀兊淖⒔?
@SystemLog(description = "helloWorld測試")
@ApiOperation(value = "hello demo", notes = "just for demo")
@GetMapping(value = "/hello", produces = "text/plain;charset=UTF-8")
public String hello() {
ReturnResult ret = new ReturnResult(Constants.QUERY_OK, "Hello World");
return ret.toString();
}
@SystemLog(description = "devide測試")
@ApiOperation(value = "hello exception demo, service method will throw native exception", notes = "just for demo")
@ApiImplicitParams({
@ApiImplicitParam(name = "a", defaultValue = "10", value = "a", required = true, dataType = "int", paramType = "query"),
@ApiImplicitParam(name = "b", defaultValue = "3", value = "b", required = true, dataType = "int", paramType = "query")
})
@GetMapping(value = "/devide", produces = "text/plain;charset=UTF-8")
public String exceptionDemo(@RequestParam("a") int a, @RequestParam("b") int b) {
log.info("Enter exceptionDemo a={} devided by b={}", a, b);
int c= computerSvc.devide(a, b);
ReturnResult ret = new ReturnResult(Constants.QUERY_OK, null);
ret.setObj(Integer.valueOf(c));
log.info("End exceptionDemo ret={}", ret);
return ret.toString();
}
第五步,查看效果
啟動(dòng)程序,然后調(diào)用這些方法。 我們的項(xiàng)目使用Swagger,因此我只需要在swagger提供的web頁面上調(diào)用方法就可以了。 特別注意的是為了展示異常的記錄,我們編寫a /b的方法,當(dāng)b等于0就出現(xiàn)異常。
請(qǐng)看效果圖
效果圖2, 調(diào)用的rest產(chǎn)生異常時(shí)
具體示例代碼在這里,歡迎加星,fork, 謝謝!
轉(zhuǎn)載自https://blog.csdn.net/russle/article/details/80639839