springAOP及一些簡單的應用(一)


以下內容僅爲做一個記錄,以備可能會用到。

什麼是AOP

AOP就是面向切面編程,它是spring框架的核心之一,開應用程序開發過程中可以解決很多問題,比如:日誌記錄,事務,權限等。同時,利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率

AOP中的相關術語

  • Aspect(切面):橫切關注點的模塊化,在spring中通常是一個類,裏面可以定義切入點和通知。
  • JointPoint(連接點):指在程序執行過程中某個特定的點,一般是方法的調用。
  • Advice(通知):在切面的某個特定的連接點上執行的動作,在spring中有before,after,afterReturning,afterThrowing,around這五種。
  • Pointcut(切入點):其實就是帶有通知的連接點,在程序中主要體現爲書寫切入點表達式。
  • 引入(Introduction):用來給一個類型聲明額外的方法或屬性(也被稱爲連接類型聲明(inter-type declaration))。Spring允許引入新的接口(以及一個對應的實現)到任何被代理的對象。例如,你可以使用引入來使一個bean實現IsModified接口,以便簡化緩存機制
  • AOP代理(AOP Proxy):AOP框架創建的對象,用來實現切面契約(例如通知方法執行等等)。在Spring中,AOP代理可以是JDK動態代理或者CGLIB代理。
  • 織入(Weaving):把切面連接到其它的應用程序類型或者對象上,並創建一個被通知的對象。這些可以在編譯時(例如使用AspectJ編譯器),類加載時和運行時完成。Spring和其他純Java AOP框架一樣,在運行時完成織入。

通知類型

  • 前置通知(Before advice):在某連接點之前執行的通知,但這個通知不能阻止連接點之前的執行流程(除非它拋出一個異常)。
  • 後置通知(After returning advice):在某連接點正常完成後執行的通知:例如,一個方法沒有拋出任何異常,正常返回。
  • 異常通知(After throwing advice):在方法拋出異常退出時執行的通知。
  • 最終通知(After (finally) advice):當某連接點退出的時候執行的通知(不論是正常返回還是異常退出)。
  • 環繞通知(Around Advice):包圍一個連接點的通知,如方法調用。這是最強大的一種通知類型。環繞通知可以在方法調用前後完成自定義的行爲。它也會選擇是否繼續執行連接點或直接返回它自己的返回值或拋出異常來結束執行,像事務,日誌等都是環繞通知。

springboot中使用AOP例子

  • 工程目錄:
    在這裏插入圖片描述
    我們需要加入spring-boot-starter-aop啓動器
  • 業務類
package com.springboot.springbootaop.service;

import org.springframework.stereotype.Service;

@Service
public class TestService {

	public void test() {
		System.out.println("this is test");
	}

}

  • 切面類
@Component
@Aspect
public class TestAspect {

	/**
	 * 切入點,這裏我們直接切入到這個方法
	 */
	@Pointcut("execution(* com.springboot.springbootaop.service.TestService.test(..))")
	public void pointCut() {
	}

	/**
	 * 前置通知
	 * 
	 * @param joinPoint
	 */
	@Before("pointCut()")
	public void doBefore(JoinPoint joinPoint) {
		System.out.println("AOP 前置通知...");
	}

	/**
	 * 最終通知,不管方法執行是否異常
	 * 
	 * @param joinPoint
	 */
	@After("pointCut()")
	public void doAfter(JoinPoint joinPoint) {
		System.out.println("AOP 最終通知...");
	}

	/**
	 * 後置通知,方法正常執行返回後
	 * 
	 * @param joinPoint
	 * @param returnVal
	 */
	@AfterReturning(pointcut = "pointCut()", returning = "returnVal")
	public void afterReturn(JoinPoint joinPoint, Object returnVal) {
		System.out.println("AOP 後置通知:" + returnVal);
	}

	/**
	 * 異常通知,方法執行異常時通知
	 * 
	 * @param joinPoint
	 * @param error
	 */
	@AfterThrowing(pointcut = "pointCut()", throwing = "error")
	public void afterThrowing(JoinPoint joinPoint, Throwable error) {
		System.out.println("AOP 異常通知..." + error);
	}

	/**
	 * 環繞通知
	 * 
	 * @param pjp
	 */
	@Around("pointCut()")
	public void around(ProceedingJoinPoint pjp) {
		System.out.println("AOP 環繞通知 before...");
		try {
			pjp.proceed();
		} catch (Throwable e) {
			e.printStackTrace();
		}
		System.out.println("AOP 環繞通知 after...");
	}
}

  • 測試類
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootAopApplicationTests {
	
	@Autowired
	private TestService testService;

	@Test
	public void contextLoads() {
		this.testService.test();
	}

}
  • 結果
    沒有異常時:
AOP 環繞通知 before...
AOP 前置通知...
this is test
AOP 環繞通知 after...
AOP 最終通知...
AOP 後置通知:null

有異常時:

AOP 環繞通知 before...
AOP 前置通知...
java.lang.ArithmeticException: / by zero
	at com.springboot.springbootaop.service.TestService.test(TestService.java:9)
	at com.springboot.springbootaop.service.TestService$$FastClassBySpringCGLIB$$64f0483d.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:56)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
	at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)
	at com.springboot.springbootaop.aspect.TestAspect.around(TestAspect.java:76)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
	at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
	at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:47)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
	at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:55)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
	at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
	at com.springboot.springbootaop.service.TestService$$EnhancerBySpringCGLIB$$c0bc1172.test(<generated>)
	at com.springboot.springbootaop.SpringbootAopApplicationTests.contextLoads(SpringbootAopApplicationTests.java:20)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
	at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
	at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
	at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
	at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
AOP 環繞通知 after...
AOP 最終通知...
AOP 後置通知:null

例子分析

在上述的列子結果中我們可以分析出通知執行的優先級:

  • 無異常的情況
    進入目標方法時,先織入Around,再織入Before,退出目標方法時,先織入Around,再織入After,最後才織入AfterReturning。
    在沒有異常的時候,切入點方法執行以後,除了上述例子的結果以外,也有可能先執行Around通知,再執行AfterReturning通知,再執行After通知。

  • 有異常的情況

  • 可以看到例子中有異常的情況下AfterThrowing通知沒有執行,因爲Spring AOP的環繞通知會影響到AfterThrowing通知的運行,不要同時使用,同時使用也沒啥意義,這是隻是演示而已。

切入點表達式

切入點表達式有:

  • execution - 匹配方法執行的連接點,這是你將會用到的Spring的最主要的切入點指示符。

  • within - 限定匹配特定類型的連接點(在使用Spring AOP的時候,在匹配的類型中定義的方法的執行)。

  • this - 限定匹配特定的連接點(使用Spring AOP的時候方法的執行),其中bean reference(Spring AOP 代理)是指定類型的實例。

  • target - 限定匹配特定的連接點(使用Spring AOP的時候方法的執行),其中目標對象(被代理的應用對象)是指定類型的實例。

  • args - 限定匹配特定的連接點(使用Spring AOP的時候方法的執行),其中參數是指定類型的實例。

  • @target - 限定匹配特定的連接點(使用Spring AOP的時候方法的執行),其中正執行對象的類持有指定類型的註解

  • @args - 限定匹配特定的連接點(使用Spring AOP的時候方法的執行),其中實際傳入參數的運行時類型持有指定類型的註解。

  • @within - 限定匹配特定的連接點,其中連接點所在類型已指定註解(在使用Spring AOP的時候,所執行的方法所在類型已指定註解)

  • @annotation - 限定匹配特定的連接點(使用Spring AOP的時候方法的執行),其中連接點的主題持有指定的註解

具體springboot整合AOP實際操作中切入點表達式怎麼用在另一篇文章中再詳細敘述。


參考文章:
http://shouce.jb51.net/spring/aop.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章