javascript
【SSM框架系列】Spring 的 AOP(面向切面编程)
什么是 AOP
- AOP 為 Aspect Oriented Programming 的縮寫,意思為面向切面編程,是通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。
- AOP 是 OOP (面向對象)的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生范型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
- AOP 可以通過編譯方式和運行期動態代理實現在不修改源代碼的情況下,為程序統一添加/增強功能的一種技術
- 如果說面向對象編程是關注將需求功能劃分為不同的并且相對獨立,封裝良好的類,并且讓它們有著屬于自己的行為,依靠繼承和多態等來定義彼此的關系的話,那么面向切面編程則是希望能夠將通用需求功能從不相關的類當中抽離出來,能夠使得很多類共享一個行為,一旦發生變化,不必修改很多類,而只需要修改這個行為即可
- 面向對象編程思想好像一個父類的思想,面向切面編程思想更像一個接口思想
AOP 的作用及其優勢
-
作用:在程序運行期間,在不修改源碼的情況下對方法進行功能增強
-
優勢:減少重復代碼,提高開發效率,并且便于維護
-
解耦合:將日志記錄,性能統計,事務處理,異常處理等代碼從業務邏輯代碼中劃分出來,通過對這些行為的分離,我們希望可以將它們獨立到非指導業務邏輯的方法中,進而改變這些行為的時候,不影響業務邏輯的代碼
-
AOP 是一個概念,并沒有設定具體語言的實現,他能克服那些只有單繼承特性語言的缺點(如java)。可以簡單的把AOP理解成一種橫切增強操作,就像臨時查酒駕一樣,在程序里面的橫切行為大致如下:
- 日志記錄,跟蹤,優化和監控
- 性能的統計,優化
- 事務的處理
- 持久化
- 異常捕捉及處理
- 資源池(如數據庫連接池)的管理
- 系統統一的認證,權限管理等
- 針對具體行業應用的橫切行為
AOP 的底層實現
實際上,AOP 的底層是通過 Spring 提供的的動態代理技術實現的。在運行期間,Spring通過動態代理技術動態的生成代理對象,代理對象方法執行時進行增強功能的介入,在去調用目標對象的方法,從而完成功能的增強。
也就是業務代碼該怎么寫就怎么寫,在需要增強業務代碼的時候,可以通過AOP動態的將工具類代碼植入進去
AOP 的動態代理技術
常用的動態代理技術
JDK 代理 : 基于接口的動態代理技術
cglib 代理:基于父類的動態代理技術
JDK 的動態代理
實現邏輯:
- 目標類必須是實現一個接口
- 代理類是在內存中創建的一個同樣實現上述接口的類(目標類和代理類是兄弟關系)
- 因為實現相同接口,所以兩者方法個數及每個方法對應的聲明一致,在生成的代理類中做目標類方法的增強,代理類的方法會調用目標方法執行原來具體的功能,同時會有自己的增強代碼,目標方法+代理的增強代碼共同構成了增強后的代理方法
①目標類接口
public interface TargetInterface {public void method(); }②目標類
public class Target implements TargetInterface {public void method() {System.out.println("Target running....");} }③動態代理代碼
public class JDKDynamicProxy {public static void main(String[] args) {// 1. 創建目標對象:目標對象類 目標對象 = new 目標對象類();final Target target = new Target();// 2. 創建代理對象// 該方法返回值即為動態代理對象,該代理對象將方法調用分派調用處理程序TargetInterface proxyTarget = (TargetInterface) Proxy.newProxyInstance(// 類加載器,第三類加載器,可以使用任意自定義的類獲取// 目標對象.getClass().getClassLoader()target.getClass().getClassLoader(), // 目標對象實現的所有接口,讓生成的代理對象,也實現相同接口,擁有和目標對象相同的方法// 目標對象.getClass().getInterfaces()target.getClass().getInterfaces(), // 代理對象調用方法的時候,都是通過這個來處理,最終由處理器的invoke方法執行new InvocationHandler() {// proxy: 動態代理對象,我們一般不用,讓程序使用// method: 目標方法,目標對象的某個被增強的方法的包裝類對象// args: 目標方法的參數列表public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {// 前置增強(在原方法運行前,添加代碼,以增強/修改方法體)System.out.println("前置增強代碼...");// 目標方法的返回值 = Method.invoke(目標對象, 目標方法的參數列表);Object invoke = method.invoke(target, args);// 后置增強(在原方法運行后,添加代碼,以增強/修改方法體)System.out.println("后置增強代碼...");// 這里可以增強(修改)原方法的返回值// return 被增強方法的返回值 或 null;return invoke;}});//3. 調用代理對象方法// 調用的是被代理對象的方法。我們并沒有編寫一個類(代理類),讓其實現TargetInterface接口。// 但是jdk動態代理會在程序運行時動態的生成一個代理類及其對象,并調用增強方法。// 增強方法會執行增強代碼,同時也會調用被增強方法以實現原有功能// 動態代理對象.增強的方法(方法名與增強前相同)();proxyTarget.method();}}④ 調用代理對象的方法測試
//3. 調用代理對象方法 proxyTarget.method();cglib 的動態代理
實現邏輯:
- “代理”的目的是構造一個和被代理的對象(目標對象)有同樣行為(行為相同,但行為能力已被增強)的對象,一個對象的行為是在類中定義的,對象只是類的實例,所有構造代理,不一定非得通過持有,包裝對象這一種方式
- 與JDK動態代理中的代理類和目標類需要實現同一個接口不同,cglib動態代理中代理類不需要和目標類實現同一個接口,而是代理類繼承了目標類,通過“繼承”可以繼承父類所有的公開方法,然后可以重寫這些方法,在重寫時,對這些方法增強,這就是cglib的思想,當然這些增強都是程序運行時在內存中進行的
①目標類
public class Target {public void method() {System.out.println("Target running....");} }②動態代理代碼
Target target = new Target(); //創建目標對象 // 創建增強器 增強器類 增強器引用 = new 增強器類(); Enhancer enhancer = new Enhancer(); //創建增強器 // 使用增強器設置父類(父類就是目標類)enhancer.setSuperclass(Class clazz) // 增強器引用.設置父類(父類字節碼文件對象); enhancer.setSuperclass(Target.class); //設置父類 // 使用增強器設置回調(該方法無返回值)enhancer.setCallback(Callback callback) /* * 在回調是需要傳入一個Callback接口對象,通過創建實現了MethodInterceptor接口的匿名內部類的對 * 象來實現,創建對象時,需要明確方法的增強邏輯,增強邏輯的代碼需要寫在實現了MethodInterceptor * 接口的類的intercept()方法中,可以實現前置,后置,參數列表和返回值的增強 */ enhancer.setCallback(new MethodInterceptor() { //設置回調/*o 代理類對象,即thismethod 父類方法包裝對象objects 被增強方法的參數methodProxy 子類增強方法包裝對象*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("前置代碼增強....");// 這里建議使用methodProxy.invokeSuper(proxy, args)// 不要使用method對象,避免出現遞歸調用造成內存溢出Object invoke = method.invoke(target, objects);System.out.println("后置代碼增強....");return invoke;} }); // 使用增強器創建對象,返回的就是代理類對象(子類對象)enhancer.creat() Target proxy = (Target) enhancer.create(); //創建代理對象③調用代理對象的方法測試
//測試,當調用接口的任何方法時,代理對象的代碼都無序修改 proxy.method();AOP 相關概念
- Spring的 AOP 實現底層就是對上面的動態代理的代碼進行封裝,封裝后我們只需要對需要關注的部分進行代碼編寫,并通過配置的方式完成指定目標的方法增強
- AOP 相關的術語,常用的術語如下:
- Target(目標對象):代理的目標對象,目標類,被代理(被增強)的類
- Proxy (代理):一個類被 AOP 織入增強后,就產生一個結果代理類,代理(增強)之后的類
- Joinpoint(連接點):所謂連接點是指那些被攔截到的點。在spring中,這些點指的是方法,因為spring只支持方法類型的連接點,可以被攔截的方法(有很多)
- Pointcut(切入點):所謂切入點是指我們要對哪些 Joinpoint 進行攔截的定義,(目標方法,攔截到并被增強的方法叫切點)
- Advice(通知/ 增強):所謂通知是指攔截到 Joinpoint 之后所要做的事情就是通知,(增強方法,攔截到方法之后,所要做的具體的增強內容)
- Aspect(切面):是切入點和通知(引介)的結合(目標方法 + 增強方法)
- Weaving(織入):是指把增強應用到目標對象來創建新的代理對象的過程。spring采用動態代理織入,而AspectJ采用編譯期織入和類裝載期織入,(是一個過程,目標方法 + 增強方法結合在一起的動作叫做織入)
- 補充:將增強應用到切點的過程叫做織入,Spring采用動態代理織入,Aspectj使用編輯器織入和類裝載器織入
- Aspectj是一個基于java語言的AOP框架,Spring2.0開始,Spring AOP引入對Aspectj擴展了java語言,提供了一個專門的編譯器,在編譯時提供橫向代碼的織入
- 早期Spring AOP開發使用的是自己的AOP技術,目前階段 Spring AOP開發通常使用Aspectj技術來實現
AOP 開發明確的事項
- 編寫核心業務代碼(目標類的目標方法)
- 編寫切面類,切面類中有通知(增強功能方法,增強方法需要寫在切面類內部)
- 在配置文件中,配置織入關系,即將哪些通知與哪些連接點進行結合
- Spring 框架監控切入點方法的執行,一旦監控到切入點方法被運行,使用代理機制,動態創建目標對象的代理對象,根據通知類別,在代理對象的對應位置,將通知對應的功能織入,完成完整的代碼邏輯運行
- Spring 框架監控切點方法的執行,通過配置指定哪些方法是切點,一旦監控到切點方法被執行,就使用動態代理創建(切點所屬類的)目標對象的代理對象,然后根據通知(增強)類別(前置增強,后置增強),在代理對象的對應位置,將通知(增強)的內容織入,完成完整的代碼邏輯運行
- 在Spring中,框架會根據目標類是否實現了接口來決定采用哪種動態代理的方式
- 有父接口:選擇JDK動態代理
- 無父接口:選擇cglib動態代理
基于 XML 的 AOP 開發
①導入 AOP 相關坐標
<!--導入spring的context坐標,context依賴aop--> <dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.5.RELEASE</version> </dependency> <!-- aspectj的織入 --> <dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.13</version> </dependency>②創建目標接口和目標類(內部有切點)
public interface TargetInterface {public void method(); }public class Target implements TargetInterface {@Overridepublic void method() {System.out.println("Target running....");} }③創建切面類(內部有增強方法)
public class MyAspect {//前置增強方法public void before(){System.out.println("前置代碼增強.....");} }④將目標類和切面類的對象創建權交給 spring
<!--配置目標類--> <bean id="target" class="cs.wy.aop.Target"></bean> <!--配置切面類--> <bean id="myAspect" class="cs.wy.aop.MyAspect"></bean>⑤在 applicationContext.xml 中配置織入關系,配置切點表達式和前置增強的織入關系
<aop:config><!--引用myAspect的Bean為切面對象--><aop:aspect ref="myAspect"><!--配置Target的method方法執行時要進行myAspect的before方法前置增強--><aop:before method="before" pointcut="execution(public void cs.wy.aop.Target.method())"></aop:before></aop:aspect> </aop:config>XML 配置 AOP 詳解
1) 切點表達式的寫法
表達式語法:
execution([修飾符] 返回值類型 包名.類名.方法名(參數))-
訪問修飾符可以省略
-
返回值類型、包名、類名、方法名可以使用星號* 代表任意
-
包名與類名之間一個點 . 代表當前包下的類,兩個點 … 表示當前包及其子包下的類
-
參數列表可以使用兩個點 … 表示任意個數,任意類型的參數列表
例如:
<!-- aop包下Target類下無參數的method方法--> execution(public void cs.wy.aop.Target.method()) execution(void cs.wy.aop.Target.*(..)) execution(* cs.wy.aop.*.*(..)) execution(* cs.wy.aop..*.*(..)) execution(* *..*.*(..))2) 通知的類型
通知的配置語法:
<aop:通知類型 method=“切面類中方法名” pointcut=“切點表達式"></aop:通知類型>3) 切點表達式的抽取
當多個增強的切點表達式相同時,可以將切點表達式進行抽取,在增強中使用 pointcut-ref 屬性代替 pointcut 屬性來引用抽取后的切點表達式。
<aop:config><!--引用myAspect的Bean為切面對象--><aop:aspect ref="myAspect"><aop:pointcut id="myPointcut" expression="execution(* cs.wy.aop.*.*(..))"/><aop:before method="before" pointcut-ref="myPointcut"></aop:before></aop:aspect> </aop:config>基于注解的 AOP 開發
①創建目標接口和目標類(內部有切點)
public interface TargetInterface {public void method(); }public class Target implements TargetInterface {@Overridepublic void method() {System.out.println("Target running....");} }②創建切面類(內部有增強方法)
public class MyAspect {//前置增強方法public void before(){System.out.println("前置代碼增強.....");} }③將目標類和切面類的對象創建權交給 spring
@Component("target") public class Target implements TargetInterface {@Overridepublic void method() {System.out.println("Target running....");} } @Component("myAspect") public class MyAspect {public void before(){System.out.println("前置代碼增強.....");} }④在切面類中使用注解配置織入關系
@Component("myAspect") @Aspect public class MyAspect {@Before("execution(* com.itheima.aop.*.*(..))")public void before(){System.out.println("前置代碼增強.....");} }⑤在配置文件中開啟組件掃描和 AOP 的自動代理
<!--組件掃描--> <context:component-scan base-package="cs.wy.aop"/><!--aop的自動代理--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>注解配置 AOP 詳解
1) 注解通知的類型
通知的配置語法:@通知注解(“切點表達式")
2) 切點表達式的抽取
同 xml配置
aop 一樣,我們可以將切點表達式抽取。抽取方式是在切面內定義方法,在該方法上使用@Pointcut注解定義切點表達式,然后在在增強注解中進行引用。具體如下:
總結
以上是生活随笔為你收集整理的【SSM框架系列】Spring 的 AOP(面向切面编程)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【工具类】JDBCUtils,数据库连接
- 下一篇: 【SSM框架系列】Spring - Jd