AOP(面向切面編程),是一種橫切技術,是對OOP的補充和完善;java
使用AOP的橫切,能夠對系統進行無侵入性的日誌監聽、事務、權限管理等;程序員
思想上跟攔截器其實相似;攔截器是對action進行攔截處理,AOP是對切面進行攔截處理,其實切面也屬於一種action集合;web
AOP能夠很好解耦;spring
Aspect:切面;編程
Join point:鏈接點;app
Advice:通知,在切入點上執行的操做;spring-boot
Poincut:帶有通知的鏈接點;spa
target:被通知的對象;代理
AOP proxy;AOP代理;日誌
其中,Advice(通知)分爲如下幾種:
around > before > around > after > afterReturning
光看理論和定義,不少人可能都以爲很難理解,其實用法比較簡單,不難的,
咱們先來個簡單的例子,看完例子你可能就豁然開朗了,
所謂程序員,好看書不如多動手:
實現:
<!-- 八、集成AOP --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
package com.anson.common.aspect; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; 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 javax.servlet.http.HttpServletRequest; import java.util.Arrays; /** * @description: AOP切面 * @author: anson * @Date: 2019/12/20 10:11 */ @Aspect //一、添加AOP相關注解 @Component public class LogAspect { private static final Logger logger = LoggerFactory.getLogger(LogAspect.class); //二、定義切入點(能夠匹配、註解的方式,可混用) // @Pointcut("execution(public * com.anson.controller.*.*(..))") @Pointcut("execution(public * com.anson.controller.*.*(..)) && @annotation(com.anson.common.annotation.LogAnnotation)") // @Pointcut("execution(public * com.anson.controller.TestController.get*()) && @annotation(com.anson.common.annotation.LogAnnotation)") public void pointcut(){} //===========通知 多中通知可根據須要靈活選用,通常Before 、AfterReturning便可======================= /** * 前置通知:在鏈接點以前執行的通知 * @param joinPoint * @throws Throwable */ @Before("pointcut()") public void doBefore(JoinPoint joinPoint) throws Throwable { // 接收到請求,記錄請求內容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 記錄下請求內容 logger.info("URL : " + request.getRequestURL().toString()); logger.info("HTTP_METHOD : " + request.getMethod()); logger.info("IP : " + request.getRemoteAddr()); logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs())); } @AfterReturning(returning = "ret",pointcut = "pointcut()") public void doAfterReturning(Object ret) throws Throwable { // 處理完請求,返回內容 logger.info("RESPONSE : " + ret); } //============================================== @After("pointcut()") public void commit() { logger.info("after commit"); } @AfterThrowing("pointcut()") public void afterThrowing() { logger.info("afterThrowing afterThrowing rollback"); } @Around("pointcut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { try { System.out.println("around"); return joinPoint.proceed(); } catch (Throwable e) { e.printStackTrace(); throw e; } finally { logger.info("around"); } } }
須要注意的是:上面代碼註釋2的地方【二、定義切入點(能夠匹配、註解的方式,可混用)】;
簡單點說就是經過匹配或者註解(也可兩種同時使用)匹配哪些類的哪些方法;
再直白點說,就是咱們要對哪些類的哪些方法執行處理,要處理的範圍是哪些;
類用*做爲通配符,方法用(..)表示,括號中的兩點表示匹配任何參數,包括沒有參數;
上面這樣其實就能夠完成一個切面了,
好比將切面定義那裏改成:@Pointcut("execution(public * com.anson.controller.*.*(..))")
那麼運行程序後,全部com.anson.controller包下的全部類的全部方法,都會執行這個切面定義的方法進行相關寫日誌處理了,結果以下:
around [2019-12-20 11:19:02,205][INFO ][http-nio-8095-exec-1] URL : http://localhost:8095/user/userall (LogAspect.java:45) [2019-12-20 11:19:02,205][INFO ][http-nio-8095-exec-1] HTTP_METHOD : GET (LogAspect.java:46) [2019-12-20 11:19:02,206][INFO ][http-nio-8095-exec-1] IP : 0:0:0:0:0:0:0:1 (LogAspect.java:47) [2019-12-20 11:19:02,207][INFO ][http-nio-8095-exec-1] CLASS_METHOD : com.anson.controller.UserController.getUserAll (LogAspect.java:48) [2019-12-20 11:19:02,208][INFO ][http-nio-8095-exec-1] ARGS : [] (LogAspect.java:49) [2019-12-20 11:19:02,510][DEBUG][http-nio-8095-exec-1] ==> Preparing: select id, userName, passWord, realName from user (BaseJdbcLogger.java:143) [2019-12-20 11:19:02,606][DEBUG][http-nio-8095-exec-1] ==> Parameters: (BaseJdbcLogger.java:143) [2019-12-20 11:19:02,631][DEBUG][http-nio-8095-exec-1] <== Total: 4 (BaseJdbcLogger.java:143) [2019-12-20 11:19:02,634][INFO ][http-nio-8095-exec-1] around (LogAspect.java:77) [2019-12-20 11:19:02,635][INFO ][http-nio-8095-exec-1] after commit (LogAspect.java:60) [2019-12-20 11:19:02,635][INFO ][http-nio-8095-exec-1] RESPONSE : com.anson.common.result.ResultBody@6d9947d (LogAspect.java:55)
若是不用註解的話,上面就已經完成一個切面了,若是用註解來定義切面範圍呢,好,也簡單,咱們來定義一個註解
--------------華麗麗的分割線-------------------------------
--------------增長自定義註解的方式----------------------------
package com.anson.common.annotation; import java.lang.annotation.*; /** * @description: 自定義註解 * @author: anson * @Date: 2019/12/20 10:32 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface LogAnnotation
{ }
這樣就能夠了,而後,在須要的地方,加入這個自定義註解:
//二、獲取全部用戶 @ApiOperation(value = "獲取全部用戶", notes = "獲取全部用戶") @RequestMapping(value="/userall",method= RequestMethod.GET) @LogAnnotation //自定義註解 public ResultBody getUserAll() { List<User> users = userservice.getAll(); return ResultBody.success(users,"獲取全部用戶信息成功"); }
同時,修改切面範圍的定義便可:
//二、定義切入點(能夠匹配、註解的方式,可混用)--如下表示範圍爲:controller包下全部包含@LogAnnotation註解的方法
@Pointcut("execution(public * com.anson.controller.*.*(..)) && @annotation(com.anson.common.annotation.LogAnnotation)")
public void pointcut(){}
完了,就這麼簡單;
至於什麼地方該使用AOP,以及AOP和攔截器用哪一個比較好,這個就要根據業務場景靈活取捨了,掌握了思想,具體使用那就能夠靈活發揮了