AOP(Aspect Oriented Programming),即面向切面編程,可以說是OOP(Object Oriented Programming,面向對象編程)的補充和完善。OOP允許開發者定義縱向的關係,但並不適合定義橫向的關係,例如日誌功能。一般來說,我們項目中的日誌幾乎會遍佈所有controller,不利於統一管理,這種散佈在各處的無關的代碼被稱爲橫切(cross cutting),在OOP設計中,它導致了大量代碼的重複,而不利於各個模塊的重用。
使用”橫切”技術,AOP把軟件系統分爲兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。橫切關注點的一個特點是,他們經常發生在覈心關注點的多處,而各處基本相似,比如權限認證、日誌、事務。AOP的作用在於分離系統中的各種關注點,將核心關注點和橫切關注點分離開來。
接下來,我們看一下如何使用AOP給springboot項目添加日誌:
一.引入AOP的依賴包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
二.添加AOP的全局類
@Aspect
@Component
public class WebLogAspect {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private LogService logService;
@Autowired
private UserService userService;
private String url;
private String ip;
private Integer port;
private User user;
@Pointcut("execution(public * com.ytint.bb6.controller..*.*(..))")
public void webLog() {
}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 記錄請求內容,這裏只記錄非GET請求的日誌信息
if (!request.getMethod().equals("GET")) {
logger.info("請求URL: " + request.getRequestURL().toString());
url = request.getRequestURI();
ip = request.getRemoteHost();
port = request.getRemotePort();
user = userService.getCurrentUser();
}
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 處理完請求,返回內容
Integer logType = LogType.OPERATION.getId();
if (ret instanceof ResultInfo) {
ResultInfo result = (ResultInfo) ret;
String msg = result.getMsg();
String module = result.getModule();
Integer code = result.getCode();
if (UtilValidate.isNotEmpty(msg)) {
logger.info(msg);
// 操作模塊返回200才存儲到日誌表
if (code.equals(200)) {
logService.save(user, ip, port, msg, logType, module, url);
}
}
}
}
@AfterThrowing(pointcut = "webLog()", throwing = "e")
public void doException(JoinPoint jp, Throwable e) {
if (e != null) {
Logger logger = LoggerFactory.getLogger(jp.getSignature().getClass());
logger.error("程序發生了異常:" + e.getMessage(), e);
}
}
}
Spring使用AspectJ註解來聲明通知方法
@After:會在目標方法返回或拋出異常後調用
@AfterReturning:會在目標方法返回後調用
@AfterThrowing:會在目標方法拋出異常後調用
@Around:會將目標方法封裝起來
@Before:會在目標方法調用之前執行
使用@Pointcut定義切面
@Pointcut後面括號裏的內容是切面表達式:
@Pointcut("execution(public * com.yt.controller..*.*(..))")
表達式含義如下:
1) execution(): 表達式主體;
2) public *:表示返回類型, *號表示所有的類型;
3) 包名:表示需要攔截的包名,後面的兩個句點表示當前包和當前包的所有子包,com.yt.controller包、子孫包下所有類的方法;
4) 第二個*號:表示類名,*號表示所有的類;
5) *(..):表示方法名,*號表示所有的方法,括號裏面表示方法的參數,..表示任何參數;
webLog()方法定義了一個可重用的切點,這樣我們就不必把同樣的切點表達式傻傻的寫好多遍了,只要在每次需要的時候引用它就可以了,webLog方法的實際內容並不重要,他本身只是一個標識,供@Pointcut註解依附。
在日誌中添加模塊信息
如果我們不只想獲取當前方法的URL,參數等基本信息,還想獲取它來源於某一個controller模塊該怎麼辦呢,我這裏採用了一個有點傻的方法,可以在返回的結果對象中添加一個模塊字段module,把對應的controller信息放在模塊裏,這樣輸出日誌時就可以知道當前請求是來源於哪個controller了。如果大家有更好的方式,歡迎交流~
本篇博客部分參考了以下兩篇博客,送上花花~~~
https://www.cnblogs.com/hongwz/p/5764917.html
https://www.cnblogs.com/30go/p/8443522.html