AOP 面向切面編程

簡介

AOP的原理:基于代理思想潘酗,對原來的目標對象船响,創(chuàng)建代理對象。在不修改原對象代碼情況下袖迎,通過代理對象季眷,調(diào)用增加功能的代碼余蟹,對原有業(yè)務方法進行增強。

應用場景

  • 記錄日志
  • 監(jiān)控方法運行時間
  • 權限控制
  • 緩存優(yōu)化
  • 事務管理

Spring AOP兩種編程方式

  • Spring 1.2 開始支持AOP編程 (傳統(tǒng)SpringAOP 編程)
  • Spring 2.0 之后支持第三方 AOP框架(AspectJ )
AOP 編程底層實現(xiàn)機制

Spring AOP 是基于動態(tài)代理的子刮,兩種方式

  • JDK 動態(tài)代理
  • CGLIB 動態(tài)代理

動態(tài)代理和靜態(tài)代理的區(qū)別威酒?

動態(tài)代理:在虛擬機內(nèi)部,運行的時候,動態(tài)生成代理類(運行時生成兼搏,runtime生成) 卵慰,并不是真正存在的類,Proxy$$(Proxy$$Customer)

靜態(tài)代理:存在代理類

JDK 動態(tài)代理 (對象必須有接口)
  • 必須對接口生成代理

  • 采用Proxy對象佛呻,通過newProxyInstance方法為目標創(chuàng)建代理對象

    該方法接收三個參數(shù) :目標對象類加載器裳朋、目標對象實現(xiàn)的接口、代理后的處理程序 InvocationHandler

    使用 Proxy類提供 newProxyInstance 方法對目標對象接口進行代理

  • 實現(xiàn) InvocationHandler 接口中 invoke方法吓著,在目標對象每個方法調(diào)用時鲤嫡,都會執(zhí)行invoke

代碼

JdkProxyFactory.java

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JdkProxyFactory implements InvocationHandler {

    private Object target;

    //構造注入
    public JdkProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxyObject() {

        //匿名內(nèi)部內(nèi)
       /* Object proxyObject = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass()
                .getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //....
                return null;
            }
        });
        return proxyObject;*/

        /**
         * loader - 定義代理類的類加載器
         * interfaces - 目標對象的所有接口(原因:jdk要根據(jù)目標的接口來生成子對象)
         * h - 回調(diào)函數(shù),代理對象要調(diào)用的方法绑莺。(可以寫增強的代碼)
         *
         */
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
        //this代表當前實現(xiàn)類的對象, 實際上就相當于  new JdkProxyFactory()
    }

    /**
     * @param proxy  代理對象(慎用,會遞歸)
     * @param method 目標對象原來的方法
     * @param args   目標對象的參數(shù)
     *

     * @return Object方法調(diào)用的返回值, 不是 method.incoke()這個返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        if ("save".equals(method.getName())) {
            //增強代碼
            writeLog();
            //執(zhí)行原來的代碼
            return method.invoke(target, args);
        }
        return method.invoke(target, args);
    }

    private static void writeLog() {
        System.out.println("增強了寫日志方法...");
    }

  /*
    //內(nèi)部內(nèi)
    private class MyInvocationHandler implements InvocationHandler{

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //.....
            return null;
        }
    }*/
}
Cglib 動態(tài)代理 (不需要接口也可以代理)

Cglib的引入為了解決類的直接代理問題(生成代理子類)暖眼,**不需要接口也可以代理 **

代碼

pom.xml

<dependencies>
    <!--Spring核心容器-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.0.2.RELEASE</version>
    </dependency>
    <!--SpringAOP相關的坐標-->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.7</version>
    </dependency>
    
    <!--Spring整合單元測試-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.0.2.RELEASE</version>
    </dependency>
    <!--單元測試-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

CglibProxyFactory.java

import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxyFactory implements MethodInterceptor {

    private Object target;

    public CglibProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxyObject() {

        //實例化Cglib增強代理器
        Enhancer enhancer = new Enhancer();
        //2.在增強器上設置相應的屬性
        // 設置目標的類,通過目標對象生成代理的子對象
        enhancer.setSuperclass(target.getClass());
        //設置回調(diào)方法的函數(shù)
        enhancer.setCallback(this);
        //通過增強器,獲取帶代理對象
        Object proxyObject = enhancer.create();

        return proxyObject;
    }

    /**
     * @param proxy       代理子對象
     * @param method      目標的方法對象
     * @param agrs        目標的方法的參數(shù)
     * @param methodProxy 代理對象的方法(已經(jīng)被增強過的)
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] agrs, MethodProxy methodProxy) throws Throwable {

        //增強代碼
        System.out.println("增強了寫日志...");
        //執(zhí)行目標對象之前的代碼
        Object object = method.invoke(target, agrs);
        return object;

    }
}

jdk:基于接口的代理纺裁,會生成目標對象的接口類型的子對象诫肠。

cglib:基于類的代理,會生成目標對象類型的子對象欺缘。

總結:

  1. Spring AOP 優(yōu)先使用接口代理 (JDK動態(tài)代理)
  2. 如果沒有接口栋豫,使用Cglib動態(tài)代理
AspectJ 切面編程 (xml)
AOP 相關術語

需求: 在save()方法邏輯調(diào)用之前進行權限的校驗

  • Joinpoint 連接點:所有的方法都是連接點

  • Pointcut 切入點:就是需要增強的方法 eg:sava()方法需要增強,save()就是切入點

  • Advice 通知/增強:增強的邏輯 eg:權限的校驗邏輯

  • Target 目標對象

  • Weaving 織入

  • Proxy 代理對象

  • Aspect 切面:把通知和切入點進行結合,就是把通知的邏輯用在切入點谚殊。

    換句話:對哪些方法進行怎樣的代碼增強

通知類型
  • 前置通知 : 在目標方法調(diào)用之前調(diào)用 (權限校驗)
  • 后置通知 : 在目標方法調(diào)用之后調(diào)用 returning 返回值
  • 環(huán)繞通知 : 前置+后置 (事務) 在方法之前和方法之后執(zhí)行. 特點:可以阻止目標方法執(zhí)行
  • 異常通知: 目標方法出現(xiàn)異常執(zhí)行. 如果方法沒有異常,不會執(zhí)行. 特點:可以獲得異常的信息
  • 最終通知:指的是無論是否有異常丧鸯,總是被執(zhí)行的。
開發(fā)步驟
  • 確定目標 bean (xml配置要增強的目標)

  • 編寫 Advice通知 (普通的JavaBean即可嫩絮,不需實現(xiàn)接口)

  • 配置aop: 切入點和切面

代碼

pom.xml

<dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <!--Spring AOP-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>

        <!--Spring核心容器-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>


        <!--Spring整合單元測試-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

xml 導入約束

<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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd   http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
</beans>

xml 中聲明 增強的bean (擴展的bean) 和 被增強的bean (被擴展的bean)

<!--注冊userService-->
<bean id="userService" class="perm.coco.service.Impl.UserServiceImpl"></bean>
<!--注冊切面類-->
<bean id="myAspect" class="perm.coco.aspectj.MyAspect"></bean>

aop 配置

<!--配置Aop,切入點和切面-->
    <aop:config>
        <!--切入點-->
        <aop:pointcut id="pointcut01" expression="execution(* perm.coco.service.Impl.UserServiceImpl.save(..))"/>
        <aop:pointcut id="pointcut02" expression="execution(* perm.coco.service.Impl.UserServiceImpl.delete(..))"/>
        <aop:pointcut id="pointcut03" expression="execution(* perm.coco.service.Impl.UserServiceImpl.findAll(..))"/>
        <aop:pointcut id="pointcut04" expression="execution(* perm.coco.service.Impl.*.*(..))"/>
        <aop:pointcut id="pointcut05" expression="execution(* perm.coco.service.Impl.*.*(..))"/>

        <!--配置切面-->
        <aop:aspect ref="myAspect">
            <!--前置-->
            <aop:before method="before" pointcut-ref="pointcut01"></aop:before>
            <!--后置-->
            <aop:after-returning method="after" pointcut-ref="pointcut02"></aop:after-returning>
            <!--環(huán)繞-->
            <aop:around method="around" pointcut-ref="pointcut03"></aop:around>
            <!--異常通知-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut04" throwing="e"></aop:after-throwing>
            <!--最終通知-->
            <aop:after method="after" pointcut-ref="pointcut05"></aop:after>
        </aop:aspect>
    </aop:config>
前置通知

在原來方法執(zhí)行前執(zhí)行

應用: 權限控制 (權限不足丛肢,拋出異常)、 記錄方法調(diào)用信息日志

<!--配置Aop,切入點和切面-->
<aop:config>
        <!--切入點-->
        <aop:pointcut id="pointcut01" expression="execution(* perm.coco.service.Impl.UserServiceImpl.save(..))"/>
       
        <!--配置切面-->
        <aop:aspect ref="myAspect">
            <!--前置-->
            <aop:before method="before" pointcut-ref="pointcut01"></aop:before>
</aop:config>
//前置
public void before() {
   System.out.println("前置增強了...");
}

后置通知

<!--后置-->
<aop:after-returning method="after" pointcut-ref="pointcut02"></aop:after-returning>
//后置
public void afterReturning() {
    System.out.println("后置打印日志....");
}

....待續(xù)....

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末剿干,一起剝皮案震驚了整個濱河市蜂怎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌怨愤,老刑警劉巖派敷,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛹批,死亡現(xiàn)場離奇詭異撰洗,居然都是意外死亡,警方通過查閱死者的電腦和手機腐芍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門差导,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人猪勇,你說我怎么就攤上這事设褐。” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵助析,是天一觀的道長犀被。 經(jīng)常有香客問我,道長外冀,這世上最難降的妖魔是什么寡键? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮雪隧,結果婚禮上西轩,老公的妹妹穿的比我還像新娘。我一直安慰自己脑沿,他們只是感情好藕畔,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著庄拇,像睡著了一般注服。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上措近,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天祠汇,我揣著相機與錄音,去河邊找鬼熄诡。 笑死可很,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的凰浮。 我是一名探鬼主播我抠,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼袜茧!你這毒婦竟也來了菜拓?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤笛厦,失蹤者是張志新(化名)和其女友劉穎纳鼎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體裳凸,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡贱鄙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了姨谷。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逗宁。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖梦湘,靈堂內(nèi)的尸體忽然破棺而出瞎颗,到底是詐尸還是另有隱情件甥,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布哼拔,位于F島的核電站引有,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏倦逐。R本人自食惡果不足惜轿曙,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望僻孝。 院中可真熱鬧导帝,春花似錦、人聲如沸穿铆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荞雏。三九已至虐秦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間凤优,已是汗流浹背悦陋。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留筑辨,地道東北人俺驶。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像棍辕,于是被迫代替她去往敵國和親暮现。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容