一、AOP簡介
AOP是Aspect Orient Programming的縮寫,即面向切面程式設計。基于動态代理的,可以使用jdk和cglib兩種代理方式。
- Aspect: 切面,給你的目标類增加的功能,就是切面。像上面用的日志,事務都是切面。切面的特點:一般都是非業務方法,獨立使用的。
- Orient:面向,對着。
- Programming:程式設計
AOP就是動态代理的規範化,把動态代理的實作步驟,方式都定義好了,讓開發人員用一種統一的方式,使用動态代理。
作用:
- 在目标類不修改的情況下增加功能
- 減少代碼的重複
- 使開發人員專注業務功能的實作
- 解耦合:業務功能和日志、事務等非業務功能的耦合
二、動态代理的實作方式
- jdk動态代理
使用jdk中的Proxy,Method,InvocaitonHanderl建立代理對象。jdk動态代理要求目标類必須實作接口
- cglib動态代理
第三方的工具庫,建立代理對象,原理是繼承。 通過繼承目标類,建立子類。子類就是代理對象。要求目标類不能是final的,方法也不能是final的。
三、AOP中名詞概念
- aspect(切面):表示給業務方法增加的功能,一般為體制輸出、事務、權限檢查等
- JoinPoint:連接配接點,連接配接業務方法和切面的位置,就某類中的業務方法
- pointcut(切入點):是一個或多個JoinPoint的集合,表示切面功能執行的位置
- 目标對象:給哪個類的方法增加功能,這個類就是目标對象
- advice(通知):也叫增強。表示切面執行的時間,在方法前或後
四、何時使用AOP
- 某項目功能類不完善,需要增加功能,但是沒有源代碼
- 給項目的多個類需要增加相同功能
- 為業務功能增加事務、日志輸出
- …
五、AOP的實作
1、Spring
Spring在内部實作了aop規範,能做aop的工作。spring主要在事務處理時使用aop,在項目開發中很少使用spring的aop實作,因為spring的aop比較笨重。
2、使用Aspectj架構實作AOP
一個開源的專門做aop的架構。spring架構中內建了aspectj架構,通過spring就能使用aspectj的功能。
aspectJ架構實作aop有兩種方式:
1)使用xml的配置檔案 : 配置全局事務
2)使用注解,項目中要做aop功能,一般都使用注解
- 使用相應注解确定切面執行的時間
- 使用切面表達式确定切面執行的位置
切面位置的切入點表達式:execution(修飾符 傳回值 包名.類名.方法名(方法參數) 異常)。用來指定切面執行的位置。
aspectj中常用注解如下:
- @Aspect:聲明該類是切面類
/*
* @Aspect:是Aspectj架構中的注解
* 作用:聲明該類是切面類
* 切面類:為目标類增加功能的類,包含切面的功能代碼
* 位置:在類的上面
* */
@Aspect
public class MyAspect {}
- @Before:前置通知,在目标方法之前執行切面的功能
@Aspect
public class MyAspect {
/*
* 定義方法,是實作切面功能的方法
* 方法定義要求:
* 1、方法時公共的
* 2、方法名自定義
* 3、方法沒有傳回值
* 4、可以有參數,也可以無參數
* 如果有參數,參數不是自定義的
* @Before:前置通知注解
* 屬性:value,是切入點表達式,表明切面的功能執行的位置
* 位置:在方法的上面
* 特點:
* 1、在目标方法前執行
* 2、不會改變和影響目标方法的執行
* */
@Before(value = "execution(public void cn.krain.ba01.SomeServiceImpl.doSome(String,Integer))")
public void myBefore(){
System.out.println("前置通知,切面功能,在目标方法執行前輸出執行的時間:"+new Date());
}
- @AfterReturning:後置通知,在目标方法之後執行切面的功能,能擷取傳回值
@Aspect
public class MyAspect {
/*
* @AfterReturning定義方法,是實作切面功能的方法
* 方法定義要求:
* 1、方法時公共的
* 2、方法名自定義
* 3、方法沒有傳回值
* 4、方法有參數
* @AfterReturning:後置通知
* 屬性:1、value:切入點表達式
* 2、returning:自定義變量,目标方法的傳回值
* 自定義變量名稱和通知方法的形參名一緻
* 位置:在方法定義的上面
* 特點:
* 1、在目标方法之後執行
* 2、能擷取目标方法的傳回值
* 3、可修改這個傳回值
* */
@AfterReturning(value = "execution(* cn.krain.ba02.SomeServiceImpl.doOther(..))",
returning="res")
public void myAfterReturning(Object res){
System.out.println("後置通知,切面功能,在目标方法執行前輸出執行的時間:"+res);
if (res!=null){
res = "Hello";
}
}
}
- @Around:環繞通知,能在目标方法前後增強功能,能夠控制目标方法的執行,修改傳回值
@Aspect
public class MyAspect {
/*
* 環繞通知的定義格式
* 方法定義要求:
* 1、方法時公共的
* 2、方法名自定義
* 3、必須有傳回值
* 4、方法有固定的參數:proceedingJoinPoint
* @Around:環繞通知
* 屬性:value 切入點表達式
* 位置:在方法的定義上面
* 特點:
* 1、是功能最強的通知
* 2、能夠在目标方法前後增強功能
* 3、控制目标方法是否被執行
* 4、修改原來目标方法的執行結果,影響最後的調用結果
* */
@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))" )
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
//擷取第一個參數的值
String name = null;
Object args [] = joinPoint.getArgs();
if (args!=null && args.length>=1){
Object arg = args[0];
name = (String) arg;
}
Object result = null;
//實作環繞通知
System.out.println("環繞通知,在目标方法之前,輸出時間:"+new Date());
//1、目标方法調用
//控制目标方法的執行
if (name.equals("張三")){
result = joinPoint.proceed(); //等同于執行目标方法
}
System.out.println("環繞通知,在目标方法之後,送出事務");
//修改目标函數傳回結果
if (result!=null){
result = "Hello Aop";
}
return result;
}
}
- @AfterThrowing:異常通知,在目标方法抛出異常後執行
@Aspect
public class MyAspect {
/* 異常通知的定義格式
* 方法定義要求:
* 1、方法時公共的
* 2、方法名自定義
* 3、方法沒有傳回值
* 4、固定參數Exception,如果還有參數則是:JoinPoint
* 5、throwing的值要與Exception的參數名相同
* @AfterThrowing:異常通知
* 屬性:1、value 切入點表達式
* 2、throwing 自定義變量,表示目标方法抛出的異常對象
* 如果有異常,通過郵件、短信通知
* 特點:
* 1、在目标方法出現異常時執行
* 2、監控目标方法是否存在異常
*
* 執行過程:
* try{
* SomeServiceImpl.doSecond();
* }catch(Exception e){
* myAfterThrowing(e);
* }
* */
@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond())",
throwing = "ex")
public void myAfterThrowing(Exception ex){
System.out.println("異常通知,方法執行異常,執行:"+ex.getMessage());
//發送短信
}
}
- @After:最終通知,總是會執行
@Aspect
public class MyAspect {
/* 最終通知的定義格式
* 方法定義要求:
* 1、方法時公共的
* 2、方法名自定義
* 3、方法沒有傳回值
* 4、沒有參數,如果有參數則是:JoinPoint
* */
/*
* @After 最終通知
* 屬性:value 切入表達式
* 位置:在方法的上面
*
* 特點:
* 1、總是會執行
* 2、在目标方法之後執行
*
* 執行過程:
* try{
* //目标方法
* }catch(){
*
* }finally{
* myAfter();
* }
* */
@After(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void myAfter(){
System.out.println("執行最終通知,總是會被執行的代碼");
}
}
- @Pointcut:定義和管理切入點的輔助注解
@Aspect
public class MyAspect {
@After(value = "myPT()")
public void myAfter(){
System.out.println("執行最終通知,總是會被執行的代碼");
}
@Before(value = "myPT()")
public void myBefore(){
System.out.println("執行前置通知");
}
/*
* @Pointcut:定義和管理切入點表達式,如果項目中的切入點表達式是重複的,可使用Pointcut
* 屬性:value 切入點表達式
* 位置:在自定義方法上面
* 為切入點表達式定義别名,使用"方法名稱()"代替切入點表達式
* */
@Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
private void myPT(){
//無功能代碼
}
}
- 編寫spring主配置檔案
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--把對象交給spring,有spring統一建立和管理-->
<!--建立SomeService目标對象-->
<bean name="someService" class="cn.krain.ba.SomeServiceImpl" />
<!--建立aspect切面對象-->
<bean name="myAspect" class="cn.krain.ba.MyAspect" />
<!--
聲明自動代理生成器:使用aspectj的内部功能,建立目标對象的代理對象。
建立代理對象是在記憶體中實作的,修改目标對象在記憶體中的結構,建立成代理對象
是以,目标對象就是被修改後的代理對象
-->
<aop:aspectj-autoproxy />
<!--
如果項目中有接口時,仍希望使用CGHLIB動态代理
proxy-target-class="true":告訴架構使用cglib動态代理
-->
<aop:aspectj-autoproxy proxy-target-class="true" />
</beans>
- 測試類MyTest.java
public class MyTest {
public void Test(){
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//根據對象名擷取代理對象
SomeService proxy = (SomeService) ac.getBean("someService");
//proxy."目标方法名"();
}
}