切麵:公共的,通用的,重覆的功能稱為切麵,面向切麵編程就是將切麵提取出來,單獨開發,在需要調用的方法中通過動態代理的方式進行織入 ...
1、面向切麵編程AOP
AOP(Aspect Orient Programming),面向切麵編程。
切麵:公共的,通用的,重覆的功能稱為切麵,面向切麵編程就是將切麵提取出來,單獨開發,在需要調用的方法中通過動態代理的方式進行織入。
2、AOP框架的"進化"
1)第一個版本:業務和切麵緊耦合在一起,沒有拆分.
2)第二個版本:使用子類代理的方式拆分業務和切麵.
3)第三個版本:使用靜態代理拆分業務和切麵.業務和業務介面已拆分.此時切麵緊耦合在業務中
4)第四個版本:使用靜態代理拆分業務和業務介面,切麵和切麵介面.
5)第五個版本:使用動態代理完成第四個版本的優化.
3、Spring支持的AOP的實現
Spring支持AOP的編程,常用的有以下幾種:
1)Before通知:在目標方法被調用前調用,涉及介面為 org.springframework.aop.MethodBeforeAdvice
2)After通知:在目標方法被調用後調用,涉及介面為 org.springframework.aop.AfterReturningAdvice
3)Throws通知:在目標方法拋出異常時調用,涉及介面為 org.springframework.aop.ThrowsAdvice
4)Around通知:攔截對目標對象方法調用,涉及介面為 org.aopalliance.intercept.MethodInterceptor
4、AOP常用的術語
1)切麵:就是那些重覆的,公共的,通用的功能稱為切麵,例如:日誌,事務,許可權.
2)連接點:就是目標方法.因為在目標方法中要實現目標方法的功能和切麵功能.
3)切入點(Pointcut):指定切入的位置,多個連接點構成切入點,切入點可以是一個目標方法,可以是一個類中的所有方法,可以是某個包下的所有類中的方法.
4)目標對象:操作誰,誰就是目標對象.
5)通知(Advice):來指定切入的時機,是在目標方法執行前還是執行後還是出錯時,還是環繞目標方法切入切麵功能.
5、AspectJ框架
- AspectJ 是一個優秀的面向切麵的框架,它擴展了 Java 語言,提供了強大的切麵實現。它因為是基於java語言開發的,所以無縫擴展
AspectJ 中常用的通知有四種類型:
1)前置通知@Before
2)後置通知@AfterReturning
3)環繞通知@Around
4)最終通知@After
5)定義切入點@Pointcut(瞭解)
6、AspectJ 的切入點表達式(掌握)
規範的公式:
- execution(訪問許可權 方法返回值 方法聲明(參數) 異常類型)
可簡化為:
- execution( 方法返回值 方法聲明(參數) )
用到的符號:
* 代表任意(通配符)
.. 如果出現在方法的參數中,則代表任意參數;如果出現在路徑中,則代表本路徑及其所有的子路徑(其實就相當於路徑中的省略號唄)
示例:
//任意的公共方法:
execution(public * *(..))
//任何一個以“set”開始的方法
execution(* set*(..))
//任意的返回值類型,在com.xyz.service.impl包下的任意類的任意方法
execution(* com.xyz.service.impl.*.*(..))
//任意的返回值類型 ,在com.xyz.service及其子包下的任意類的任意方法
execution(* com.xyz.service..*.*(..))
//service之前可以有任意的子包
execution(* *..service.*.*(..))
//service之前只有一個包
execution(* *.service.*.*(..))
7、AspectJ的前置通知@Before:
- 在目標方法執行前切入切麵功能,在切麵方法中不可以獲得目標方法的返回值,只能得到目標方法的簽名。
實現的步驟:
1)添加依賴:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
2)創建業務介面
3)創建業務實現
4)創建切麵類,實現切麵方法
5)在applicationContext.xml文件中進行切麵綁定
慄子:
@Aspect //交給AspectJ的框架去識別切麵類
@Component
public class MyAspect {
/**
* 所有切麵的功能都是由切麵方法來實現的
* 可以將各種切麵都在此類中進行開發
*
* 前置通知的切麵方法的規範
* 1)訪問許可權是public
* 2)方法的返回值是void
* 3)方法名稱自定義
* 4)方法沒有參數,如果有也只能是JoinPoint類型
* 5)必須使用@Before註解來聲明切入的時機是前切功能和切入點
* 參數:value 指定切入點表達式
*
* 業務方法
* public String doSome(String name, int age)
*/
@Before(value = "execution(public String com.bjpowernode.s01.SomeServiceImpl.*(String,int))")
public void myBefore(){
System.out.println("切麵方法中的前置通知功能實現............");
}
@Before(value = "execution(public * com.bjpowernode.s01.SomeServiceImpl.*(..))")
public void myBefore(){
System.out.println("切麵方法中的前置通知功能實現............");
}
@Before(value = "execution( * com.bjpowernode.s01.*.*(..))")
public void myBefore(JoinPoint jp){
System.out.println("切麵方法中的前置通知功能實現............");
System.out.println("目標方法的簽名:"+jp.getSignature());
System.out.println("目標方法的參數:"+ Arrays.toString(jp.getArgs()));
}
@Before(value = "execution( * com.bjpowernode.s01..*(..))")
public void myBefore(){
System.out.println("切麵方法中的前置通知功能實現............");
}
@Before(value = "execution( * *(..))")
public void myBefore(){
System.out.println("切麵方法中的前置通知功能實現............");
}
}
AspectJ框架切換JDK動態代理和CGLib動態代理:
<!--預設是JDK動態代理,取時必須使用介面類型-->
<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
<!--設置為CGLib子類代理,可以使用介面和實現類接,記住:使用介面來接,永遠不出錯-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
8、@AfterReturning後置通知:
- 後置通知是在目標方法執行後切入切麵功能,可以得到目標方法的返回值.如果目標方法的返回值是簡單類型(8種基本類型+String)則不可改變.如果目標方法的返回值是引用類型則可以改變.
@Aspect
@Component
public class MyAspect {
/**
* 後置通知的方法的規範
* 1)訪問許可權是public
* 2)方法沒有返回值void
* 3)方法名稱自定義
* 4)方法有參數(也可以沒有參數,如果目標方法沒有返回值,則可以寫無參的方法,但一般會寫有參,這樣可以處理無參可以處理有參),這個切麵方法的參數就是目標方法的返回值
* 5)使用@AfterReturning註解表明是後置通知
* 參數:
* value:指定切入點表達式
* returning:指定目標方法的返回值的名稱,則名稱必須與切麵方法的參數名稱一致.
*/
@AfterReturning(value = "execution(* com.bjpowernode.s02.*.*(..))",returning = "obj")
public void myAfterReturning(Object obj){
System.out.println("後置通知功能實現..............");
if(obj != null){
if(obj instanceof String){
obj = obj.toString().toUpperCase();
System.out.println("在切麵方法中目標方法的返回值:"+obj);
}
if(obj instanceof Student){
Student stu = (Student) obj;
stu.setName("李四");
System.out.println("在切麵方法中目標方法的返回值:"+stu);
}
}
}
}
9、環繞通知@Around:
- 它是通過攔截目標方法的方式 ,在目標方法前後增強功能的通知,它是功能最強大的通知,一般事務使用此通知.它可以輕易的改變目標方法的返回值.
@Aspect
@Component
public class MyAspect {
/**
* 環繞通知方法的規範
* 1)訪問許可權是public
* 2)切麵方法有返回值,此返回值就是目標方法的返回值
* 3)方法名稱自定義
* 4)方法有參數,此參數就是目標方法
* 5)迴避異常Throwable
* 6)使用@Around註解聲明是環繞通知
* 參數:
* value:指定切入點表達式
*/
@Around(value = "execution(* com.bjpowernode.s03.*.*(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
//前切功能實現
System.out.println("環繞通知中的前置功能實現............");
//目標方法調用
Object obj = pjp.proceed(pjp.getArgs());
//後切功能實現
System.out.println("環繞通知中的後置功能實現............");
return obj.toString().toUpperCase(); //改變了目標方法的返回值
}
}
10、最終通知@After
無論目標方法是否正常執行,最終通知的代碼都會被執行.
@Aspect
@Component
public class MyAspect {
/**
* 最終方法的規範
* 1)訪問許可權是public
* 2)切麵方法沒有返回值void
* 3)方法名稱自定義
* 4)方法可以沒有參數,也可以有,則JoinPoint.
* 5)使用@After註解
* 6)參數:value:指定切入點表達式
*/
@After(value = "execution(* com.bjpowernode.s04.SomeServiceImpl.*(..))")
public void myAfter(){
System.out.println("最終通知被執行.............");
}
}
11、給切入點表達式起別名@Pointcut
如果多個切麵切入到同一個切入點,可以使用別名簡化開發。使用@Pointcut註解,創建一個空方法,此方法的名稱就是別名。
@Aspect
@Component
public class MyAspect {
/**
* 最終通知方法的規範
* 1)訪問許可權是public
* 2)方法沒有返回值
* 3)方法名稱自定義
* 4)方法沒有參數,如果有也只能是JoinPoint
* 5)使用@After註解表明是最終通知
* 參數:
* value:指定切入點表達式
*/
@After(value = "mycut()")
public void myAfter(){
System.out.println("最終通知的功能........");
}
@Before(value = "mycut()")
public void myBefore(){
System.out.println("前置通知的功能........");
}
@AfterReturning(value = "mycut()",returning = "obj")
public void myAfterReturning(Object obj){
System.out.println("後置通知的功能........");
}
@Around(value = "mycut()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("環繞通知中的前置通知的功能........");
Object obj = pjp.proceed(pjp.getArgs());
System.out.println("環繞通知中的後置通知的功能........");
return obj;
}
@Pointcut(value = "execution(* com.bjpowernode.s04.*.*(..))")
public void mycut(){}
}