JAVASE-15-動態代理

Table of Contents

1:基於JDK的動態代理

1.1:創建接口

1.2:創建實現類

1.3:創建InvocationHandler方法執行器 和生成代理類

1.4:編寫代理類實際的調用。

1.5:或者使用內部類來標識InvocationHandler處理器;

2:基於CGLIB的動態代理

2.1:引入jar包

2.2:被代理類

2.3:代理了實現MethodInterceptor接口

2.4:測試:

2.5:CGLIB原理


目前java動態代理的實現分爲兩種

1.基於JDK的動態代理

2.基於CGILB的動態代理

在業務中使用動態代理,一般是爲了給需要實現的方法添加預處理或者添加後續操作,但是不干預實現類的正常業務,把一些基本業務和主要的業務邏輯分離。我們一般所熟知的Spring的AOP原理就是基於動態代理實現的。
 

1:基於JDK的動態代理

基於JDK的動態代理就需要知道兩個類:1.InvocationHandler(接口)、2.Proxy(類)

還要知道JDK是基於接口的動態代理,也就是說我們動態代理的被代理類,必須實現接口

1.1:創建接口

public interface UserInter {
    void add();
}

1.2:創建實現類

public class UserImpl implements UserInter {
    @Override
    public void add() {
        System.out.println("add.....");
    }
}

1.3:創建InvocationHandler方法執行器 和生成代理類

package com.wkl.impl.Proxy;

import com.wkl.impl.UserImpl;

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

/**
 * Description:方法執行器,幫我們執行方法
 * Date:       2020/6/26 - 下午 10:16
 * author:     wangkanglu
 * version:    V1.0
 */
public class MyInvocationHandler implements InvocationHandler {
    private UserImpl userImpl;

    public void setUserImpl(UserImpl userImpl) {
        this.userImpl = userImpl;
    }

    /**
     * Object proxy:代理對象;給jdk使用,任何時候都不要動這個對象
     * Method method:當前將要執行的目標對象的方法
     * Object[] args:這個方法調用時外界傳入的參數值
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //參數爲被代理的類的實例,和傳入的參數
        System.out.println("準備執行方法:"+method.getName());
        Object invoke = method.invoke(userImpl, args);
        System.out.println("執行方法:"+method.getName()+"完畢!");
        return invoke;
    }

    
}

1.4:編寫代理類實際的調用。

 UserInter userInter = new UserImpl();
        //獲取處理器實例
        MyInvocationHandler userProxy = new MyInvocationHandler();
        userProxy.setUserInter(userInter);

        UserInter o = (UserInter) Proxy.newProxyInstance(userInter.getClass().getClassLoader(), userInter.getClass().getInterfaces(), userProxy);
        o.add();
準備執行方法:add
add.....
執行方法:add完畢!

1.5:或者使用內部類來標識InvocationHandler處理器;

package com.wkl.impl.Proxy;

import com.wkl.impl.UserImpl;
import com.wkl.inter.UserInter;

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

/**
 * Description:
 * Date:       2020/6/26 - 下午 10:15
 * author:     wangkanglu
 * version:    V1.0
 */
public class UserProxy {

    public UserInter getProxy(UserInter userInter){

        InvocationHandler i = new InvocationHandler() {
            /**
             * Object proxy:代理對象;給jdk使用,任何時候都不要動這個對象
             * Method method:當前將要執行的目標對象的方法
             * Object[] args:這個方法調用時外界傳入的參數值
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("準備執行方法:"+method.getName());
                Object invoke = method.invoke(userInter, args);
                System.out.println("執行方法:"+method.getName()+"完畢!");
                return invoke;
            }
        };

        UserInter o = (UserInter) Proxy.newProxyInstance(userInter.getClass().getClassLoader(), userInter.getClass().getInterfaces(), i);
        return o;
    }

    public static void main(String[] args) {
        UserInter userInter = new UserImpl();
        //獲取處理器實例
        UserProxy userProxy = new UserProxy();
        UserInter proxy = userProxy.getProxy(userInter);
        proxy.add();
    }

}

注:處理器接口傳入的是接口的實現;

 

2:基於CGLIB的動態代理

本文主要講的是CGLIB的動態代理,因爲基於JDK的動態代理一定要繼承一個接口,而絕大部分情況是基於POJO類的動態代理,那麼CGLIB就是一個很好的選擇,在Hibernate框架中PO的字節碼生產工作就是靠CGLIB來完成的。還是先看代碼。

2.1:引入jar包

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

2.2:被代理類

public class UserImpl {
    public void add() {
        System.out.println("add.....");
    }
}

 

2.3:代理了實現MethodInterceptor接口

package com.wkl.impl.Proxy;

import com.wkl.impl.UserImpl;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * Description:
 * Date:       2020/6/26 - 下午 10:55
 * author:     wangkanglu
 * version:    V1.0
 */
public class UserCGLIB implements MethodInterceptor {
    private UserImpl userimpl;

    public void setUserimpl(UserImpl userimpl) {
        this.userimpl = userimpl;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//參數爲被代理的類的實例,和傳入的參數
        System.out.println("準備執行方法:"+method.getName());
 用。
        ////method無法得到父類的方法,所以使用methoproxy
        Object invoke = methodProxy.invoke(userimpl, objects);
        System.out.println("執行方法:"+method.getName()+"完畢!");
        return invoke;
    }
}

2.4:測試:

 public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserImpl.class);
        UserCGLIB userCGLIB = new UserCGLIB();
        UserImpl user = new UserImpl();
        userCGLIB.setUserimpl(user);
        enhancer.setCallback(userCGLIB);
        UserImpl o = (UserImpl) enhancer.create();
        o.add();
    }
準備執行方法:add
add.....
執行方法:add完畢!

 

2.5:CGLIB原理

從代碼可以看出,它和jdk動態代理有所不同,對外表現上看CreatProxyedObj,它只需要一個類型clazz就可以產生一個代理對象, 所以說是“類的代理”,且創造的對象通過打印類型發現也是一個新的類型。不同於jdk動態代理,jdk動態代理要求對象必須實現接口(三個參數的第二個參數),cglib對此沒有要求。

cglib的原理是這樣,它生成一個繼承B的類型C(代理類),這個代理類持有一個MethodInterceptor,我們setCallback時傳入的。 C重寫所有B中的方法(方法名一致),然後在C中,構建名叫“CGLIB”+“$父類方法名$”的方法(下面叫cglib方法,所有非private的方法都會被構建),方法體裏只有一句話super.方法名(),可以簡單的認爲保持了對父類方法的一個引用,方便調用。
 

這樣的話,C中就有了重寫方法、cglib方法、父類方法(不可見),還有一個統一的攔截方法(增強方法intercept)。其中重寫方法和cglib方法肯定是有映射關係的。

C的重寫方法是外界調用的入口(LSP原則),它調用MethodInterceptor的intercept方法,調用時會傳遞四個參數,第一個參數傳遞的是this,代表代理類本身,第二個參數標示攔截的方法,第三個參數是入參,第四個參數是cglib方法,intercept方法完成增強後,我們調用cglib方法間接調用父類方法完成整個方法鏈的調用。
 

爲什麼我們使用methodproxy,不適用method

因爲如果我們通過反射 arg1.invoke(arg0, ...)這種方式是無法調用到父類的方法的,子類有方法重寫,隱藏了父類的方法,父類的方法已經不可見,如果硬調arg1.invoke(arg0, ...)很明顯會死循環。

所以調用的是cglib開頭的方法,但是,我們使用arg3也不是簡單的invoke,而是用的invokeSuper方法,這是因爲cglib採用了fastclass機制,不僅巧妙的避開了調不到父類方法的問題,還加速了方法的調用。

fastclass基本原理是,給每個方法編號,通過編號找到方法執行避免了通過反射調

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章