Spring框架學習06——AOP底層實現原理

在Java中有多種動態代理技術,如JDK、CGLIB、Javassist、ASM,其中最經常使用的動態代理技術是JDK和CGLIB。java

一、JDK的動態代理

JDK動態代理是java.lang.reflect.*包提供的方法,必需要藉助一個接口才能產生代理對象,對於使用業務接口的類,Spring默認使用JDK動態代理實現AOP。
代碼示例以下:
建立dao包,並建立StuDao接口和StuDaoImpl實現類,
StuDao接口spring

public interface StuDao {
    public void add();
    public void find();
    public void update();
    public void delete();
}

StuDaoImpl實現類數組

public class StuDaoImpl implements StuDao {
    @Override
    public void add() {
        System.out.println("添加學生");
    }

    @Override
    public void find() {
        System.out.println("查詢學生");
    }

    @Override
    public void update() {
        System.out.println("修改學生");
    }

    @Override
    public void delete() {
        System.out.println("刪除學生");
    }
}

建立aspect包,並建立切面類MyAspect,該類中能夠定義多個通知,即加強處理的方法,示例代碼以下:ide

public class MyAspect {
    public void check(){
        System.out.println("模擬權限控制");
    }
    public void except(){
        System.out.println("模擬異常處理");
    }
    public void log(){
        System.out.println("模擬日誌記錄");
    }
    public void monitor(){
        System.out.println("模擬性能檢測");
    }
}

建立proxy包,並建立代理類MyJdkProxy,在JDK動態代理中代理類必須實現java.lang.reflect.InvocationHandler接口,並編寫代理方法,在代理方法中須要經過Proxy實現動態代理。示例代碼以下:性能

package com.aop.proxy;

import com.aop.aspect.MyAspect;
import com.aop.dao.StuDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyJdkProxy implements InvocationHandler {

    //聲明目標類接口對象(真實對象)
    private StuDao stuDao;

    public MyJdkProxy(StuDao stuDao){
        this.stuDao = stuDao;
    }

    //建立代理的方法,創建代理對象和真實對象的代理關係,返回代理對象
    public Object createProxy(){
        //1.類加載器
        ClassLoader cld = MyJdkProxy.class.getClassLoader();
        //2.被代理對象實現的全部接口
        Class[] clazz = stuDao.getClass().getInterfaces();
        return Proxy.newProxyInstance(cld,clazz,this);
    }

    /**
     * 代理的邏輯方法,全部動態代理類的方法調用都交給該方法處理
     * @param proxy 被代理對象
     * @param method 要執行的方法
     * @param args 執行方法時須要的參數
     * @return 返回代理結果
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //建立一個切面
        MyAspect myAspect = new MyAspect();
        //前加強
        myAspect.check();
        myAspect.except();
        //在目標類上調用方法並傳入參數,至關於調用stuDao中的方法
        Object obj = method.invoke(stuDao,args);
        //後加強
        myAspect.log();
        myAspect.monitor();
        return obj;
    }
}

建立測試類測試

@Test
public void testStu(){
    //建立目標對象
    StuDao stuDao = new StuDaoImpl();
    //建立代理對象
    MyJdkProxy myJdkProxy = new MyJdkProxy(stuDao);
    //從代理對象中獲取加強後的目標對象
    //該對象是一個被代理的對象,它會進入代理的邏輯方法invoke中
    StuDao stuDaoProxy = (StuDao) myJdkProxy.createProxy();
    //執行方法
    stuDaoProxy.add();
    System.out.println("==================");
    stuDaoProxy.update();
    System.out.println("==================");
    stuDaoProxy.delete();
}

運行結果this

二、CGLIB的動態代理

JDK動態代理必須提供接口才能使用,對於沒有提供接口的類,只能採用CGLIB動態代理。CGLIB採用很是底層的字節碼技術,對指定的目標類生產一個子類,並對子類進行加強。在Spring Core 包中已經集成了CGLIB所須要的jar包,無需另外引入jar包。
示例代碼以下:
建立目標類TestDaospa

public class TestDao {
    public void save(){
        System.out.println("保存方法");
    }
    public void modify(){
        System.out.println("修改方法");
    }
    public void delete(){
        System.out.println("刪除方法");
    }
}

建立切面類MyAspect,並在該類中定義多個通知代理

public class MyAspect {
    public void check(){
        System.out.println("模擬權限控制");
    }
    public void except(){
        System.out.println("模擬異常處理");
    }
    public void log(){
        System.out.println("模擬日誌記錄");
    }
    public void monitor(){
        System.out.println("模擬性能檢測");
    }
}

建立代理類MyCglibProxy,並實現MethodInterceptor接口日誌

package com.aop.proxy;

import com.aop.aspect.MyAspect;
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 MyCglibProxy implements MethodInterceptor {

    /**
     * 建立代理的方法,生成CGLIB代理對象
     * @param target 目標對象,須要加強的對象
     * @return 返回目標對象的CGLIB代理對象
     */
    public Object createProxy(Object target){
        //建立一個動態類對象,即加強類對象
        Enhancer enhancer = new Enhancer();
        //設置其父類
        enhancer.setSuperclass(target.getClass());
        //肯定代理邏輯對象爲當前對象
        enhancer.setCallback(this);
        return enhancer.create();
    }

    /**
     * 該方法會在程序執行目標方法時調用
     * @param proxy 是CGLIB根據指定父類生成的代理對象
     * @param method 是攔截方法
     * @param args 攔截方法的參數數組
     * @param methodProxy 方法的代理對象,用於執行父類的方法
     * @return 返回代理結果
     * @throws Throwable
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //建立一個切面
        MyAspect myAspect = new MyAspect();
        //前置加強
        myAspect.check();
        //目標方法執行,返回執行結果
        Object obj = methodProxy.invokeSuper(proxy,args);
        //後置加強
        myAspect.log();
        return obj;
    }
}

建立測試類

@Test
public void test(){
    //建立目標對象
    TestDao testDao = new TestDao();
    //建立代理對象
    MyCglibProxy myCglibProxy = new MyCglibProxy();
    //獲取加強後的目標對象
    TestDao testDaoAdvice = (TestDao) myCglibProxy.createProxy(testDao);
    //執行方法
    testDaoAdvice.save();
    System.out.println("==================");
    testDaoAdvice.modify();
    System.out.println("==================");
    testDaoAdvice.delete();
}

運行結果

三、動態代理注意事項

(1)程序中應優先對接口建立代理,便於程序解耦維護;

(2)使用final關鍵字修飾的方法不能被代理,由於沒法覆蓋

  • JDK動態代理,是針對接口生成子類,接口中方法不能使用final修飾
  • CGLIB是針對目標類生成子類,所以類或方法不能使用final修飾

(3)Spring只支持方法鏈接點,不提供屬性鏈接點

相關文章
相關標籤/搜索