AOP(Aspect Oriented Programming,面向切面編程)是通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生范型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
在Spring AOP中業務邏輯僅僅只關注業務本身,將日志記錄、性能統計、安全控制、事務處理、異常處理等代碼從業務邏輯代碼中划分出來,從而在改變這些行為的時候不影響業務邏輯的代碼。
相關注解介紹:
| 注解 | 作用 |
| @Aspect | 把當前類標識為一個切面 |
| @Pointcut | Pointcut是織入Advice的觸發條件。每個Pointcut的定義包括2部分,一是表達式,二是方法簽名。方法簽名必須是public及void型。可以將Pointcut中的方法看作是一個被Advice引用的助記符,因為表達式不直觀,因此我們可以通過方法簽名的方式為此表達式命名。因此Pointcut中的方法只需要方法簽名,而不需要在方法體內編寫實際代碼。 |
| @Around | 環繞增強,目標方法執行前后分別執行一些代碼 |
| @AfterReturning | 返回增強,目標方法正常執行完畢時執行 |
| @Before | 前置增強,目標方法執行之前執行 |
| @AfterThrowing | 異常拋出增強,目標方法發生異常的時候執行 |
| @After | 后置增強,不管是拋出異常或者正常退出都會執行 |
<!--Spring AOP 切面 模塊 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<!-- SpringBoot 攔截器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
package com.example.demo.Aspect;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.SourceLocation;
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;
@Component
@Aspect
@Slf4j
public class LogAspect {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@Pointcut("execution(* com.example.demo.Aspect.TestController.doNormal(..))")
public void pointCut(){}
@Before(value = "pointCut()")
public void before(JoinPoint joinPoint){
log.info("@Before通知執行");
//獲取目標方法參數信息
Object[] args = joinPoint.getArgs();
Arrays.stream(args).forEach(arg->{ // 大大
try {
log.info(OBJECT_MAPPER.writeValueAsString(arg));
} catch (JsonProcessingException e) {
log.info(arg.toString());
}
});
//aop代理對象
Object aThis = joinPoint.getThis();
log.info(aThis.toString()); //com.xhx.springboot.controller.HelloController@69fbbcdd
//被代理對象
Object target = joinPoint.getTarget();
log.info(target.toString()); //com.xhx.springboot.controller.HelloController@69fbbcdd
//獲取連接點的方法簽名對象
Signature signature = joinPoint.getSignature();
log.info(signature.toLongString()); //public java.lang.String com.xhx.springboot.controller.HelloController.getName(java.lang.String)
log.info(signature.toShortString()); //HelloController.getName(..)
log.info(signature.toString()); //String com.xhx.springboot.controller.HelloController.getName(String)
//獲取方法名
log.info(signature.getName()); //getName
//獲取聲明類型名
log.info(signature.getDeclaringTypeName()); //com.xhx.springboot.controller.HelloController
//獲取聲明類型 方法所在類的class對象
log.info(signature.getDeclaringType().toString()); //class com.xhx.springboot.controller.HelloController
//和getDeclaringTypeName()一樣
log.info(signature.getDeclaringType().getName());//com.xhx.springboot.controller.HelloController
//連接點類型
String kind = joinPoint.getKind();
log.info(kind);//method-execution
//返回連接點方法所在類文件中的位置 打印報異常
SourceLocation sourceLocation = joinPoint.getSourceLocation();
log.info(sourceLocation.toString());
//log.info(sourceLocation.getFileName());
//log.info(sourceLocation.getLine()+"");
//log.info(sourceLocation.getWithinType().toString()); //class com.xhx.springboot.controller.HelloController
///返回連接點靜態部分
JoinPoint.StaticPart staticPart = joinPoint.getStaticPart();
log.info(staticPart.toLongString()); //execution(public java.lang.String com.xhx.springboot.controller.HelloController.getName(java.lang.String))
//attributes可以獲取request信息 session信息等
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
log.info(request.getRequestURL().toString()); //http://127.0.0.1:8080/hello/getName
log.info(request.getRemoteAddr()); //127.0.0.1
log.info(request.getMethod()); //GET
log.info("before通知執行結束");
}
/**
* 后置返回
* 如果第一個參數為JoinPoint,則第二個參數為返回值的信息
* 如果第一個參數不為JoinPoint,則第一個參數為returning中對應的參數
* returning:限定了只有目標方法返回值與通知方法參數類型匹配時才能執行后置返回通知,否則不執行,
* 參數為Object類型將匹配任何目標返回值
*/
@AfterReturning(value = "pointCut()",returning = "result")
public void doAfterReturningAdvice1(JoinPoint joinPoint,Object result){
log.info("第一個后置返回通知的返回值:"+result);
}
@AfterReturning(value = "pointCut()",returning = "result",argNames = "result")
public void doAfterReturningAdvice2(String result){
log.info("第二個后置返回通知的返回值:"+result);
}
//第一個后置返回通知的返回值:姓名是大大
//第二個后置返回通知的返回值:姓名是大大
//第一個后置返回通知的返回值:{name=小小, id=1}
/**
* 后置異常通知
* 定義一個名字,該名字用於匹配通知實現方法的一個參數名,當目標方法拋出異常返回后,將把目標方法拋出的異常傳給通知方法;
* throwing:限定了只有目標方法拋出的異常與通知方法相應參數異常類型時才能執行后置異常通知,否則不執行,
* 對於throwing對應的通知方法參數為Throwable類型將匹配任何異常。
* @param joinPoint
* @param exception
*/
@AfterThrowing(value = "pointCut()",throwing = "exception")
public void doAfterThrowingAdvice(JoinPoint joinPoint,Throwable exception){
log.info(joinPoint.getSignature().getName());
if(exception instanceof NullPointerException){
log.info("發生了空指針異常!!!!!");
}
}
@After(value = "pointCut()")
public void doAfterAdvice(JoinPoint joinPoint){
log.info("后置通知執行了!");
}
/**
* 環繞通知:
* 注意:Spring AOP的環繞通知會影響到AfterThrowing通知的運行,不要同時使用
*
* 環繞通知非常強大,可以決定目標方法是否執行,什么時候執行,執行時是否需要替換方法參數,執行完畢是否需要替換返回值。
* 環繞通知第一個參數必須是org.aspectj.lang.ProceedingJoinPoint類型
*/
@Around(value = "pointCut()")
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
log.info("@Around環繞通知:"+proceedingJoinPoint.getSignature().toString());
Object obj = null;
try {
obj = proceedingJoinPoint.proceed(); //可以加參數
log.info(obj.toString());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
log.info("@Around環繞通知執行結束");
return obj;
}
}
package com.example.demo.Aspect;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class TestController {
@RequestMapping("/doNormal")
public String doNormal(String name, String age) {
log.info("【執行方法】:doNormal");
return "doNormal";
}
@RequestMapping("/doWithException")
public String doWithException(String name, String age) {
log.info("【執行方法】:doWithException");
int a = 1 / 0;
return "doWithException";
}
}
啟動程序,當訪問doNormal方法時,日志輸出如下:
2020-06-05 09:59:54.256 INFO 27344 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2020-06-05 09:59:54.257 INFO 27344 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2020-06-05 09:59:54.262 INFO 27344 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 4 ms 2020-06-05 09:59:54.289 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : @Around環繞通知:String com.example.demo.Aspect.TestController.doNormal(String,String) 2020-06-05 09:59:54.289 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : @Before通知執行 2020-06-05 09:59:54.295 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : null 2020-06-05 09:59:54.295 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : null 2020-06-05 09:59:54.295 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : com.example.demo.Aspect.TestController@5c7c88bb 2020-06-05 09:59:54.295 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : com.example.demo.Aspect.TestController@5c7c88bb 2020-06-05 09:59:54.296 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : public java.lang.String com.example.demo.Aspect.TestController.doNormal(java.lang.String,java.lang.String) 2020-06-05 09:59:54.296 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : TestController.doNormal(..) 2020-06-05 09:59:54.296 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : String com.example.demo.Aspect.TestController.doNormal(String,String) 2020-06-05 09:59:54.296 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : doNormal 2020-06-05 09:59:54.296 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : com.example.demo.Aspect.TestController 2020-06-05 09:59:54.296 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : class com.example.demo.Aspect.TestController 2020-06-05 09:59:54.296 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : com.example.demo.Aspect.TestController 2020-06-05 09:59:54.296 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : method-execution 2020-06-05 09:59:54.297 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint$SourceLocationImpl@77ad4f8f 2020-06-05 09:59:54.298 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : execution(public java.lang.String com.example.demo.Aspect.TestController.doNormal(java.lang.String,java.lang.String)) 2020-06-05 09:59:54.299 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : http://localhost:8080/doNormal 2020-06-05 09:59:54.299 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : 0:0:0:0:0:0:0:1 2020-06-05 09:59:54.299 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : GET 2020-06-05 09:59:54.299 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : before通知執行結束 2020-06-05 09:59:54.307 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.TestController : 【執行方法】:doNormal 2020-06-05 09:59:54.307 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : doNormal 2020-06-05 09:59:54.307 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : @Around環繞通知執行結束 2020-06-05 09:59:54.307 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : 后置通知執行了! 2020-06-05 09:59:54.307 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : 第一個后置返回通知的返回值:doNormal 2020-06-05 09:59:54.307 INFO 27344 --- [nio-8080-exec-1] com.example.demo.Aspect.LogAspect : 第二個后置返回通知的返回值:doNormal Disconnected from the target VM, address: '127.0.0.1:56402', transport: 'socket' 2020-06-05 10:03:39.155 INFO 27344 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
