設計模式學習之代理模式

代理的實質就是代理類保存著實際目標類的引用對象。代理分為靜態(tài)代理和動態(tài)代理

靜態(tài)代理

定義一個目標類接口,并定義兩個方法:

public interface ITarget {
    void fun1();
    String fun2(String s);
}

定義一個真實目標類并且實現(xiàn)ITarget接口

public class TargetObject implements ITarget{
    @Override
    public void fun1() {
        System.out.println("執(zhí)行方法fun1...");
    }

    @Override
    public String fun2(String s) {
        System.out.println("執(zhí)行方法fun2..." + s);
        return s;
    }
}

定義一個代理類保存著ITarget的引用

public class TargetObjectProxy {
    private ITarget target;
    public TargetObjectProxy(ITarget target){
        this.target = target;
    }
    //目標方法執(zhí)行前
    public void before(){
        System.out.println("靜態(tài)代理方法執(zhí)行前...");
    }

    //目標方法執(zhí)行后
    public void after(){
        System.out.println("靜態(tài)代理方法執(zhí)行后...");
    }
    
    public void fun1() {
        before();
        target.fun1();
        after();
    }

    public String fun2(String s) {
        before();
        String result = target.fun2();
        after();
        return result;
    }
}

上面直接調用TargetObjectProxy的方法即可完成靜態(tài)代理,傳統(tǒng)的三層架構就是使用靜態(tài)代理實現(xiàn)的,Controller代理Service,Service代理Dao

但是當目標類新增方法,也要在代理類中添加相應的方法,不符合開閉原則

動態(tài)代理

動態(tài)代理實現(xiàn)有兩種,分別是JDK動態(tài)代理和CGlib動態(tài)代理

JDK動態(tài)代理

JDK動態(tài)代理要求代理類實現(xiàn)InvocationHandler接口,并且重寫invoke方法,使用反射生成代理類

使用

創(chuàng)建一個ObjectProxy類

public class ObjectProxy implements InvocationHandler {
    //目標類
    private Object target;

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

    //反射獲取目標類的代理實例
    public <T> T getInstance(){
        Class<?> clazz = target.getClass();
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }

    public void before(){
        System.out.println("動態(tài)代理執(zhí)行前...");
    }

    public void after(){
        System.out.println("動態(tài)代理執(zhí)行后...");
    }

    /**
     * @param proxy  代理對象 $Proxy0
     * @param method 當前執(zhí)行方法
     * @param args 方法參數
     * @return 方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object o = method.invoke(target, args);
        after();
        return o;
    }
}

測試

public class JDKProxyDemo {
    public static void main(String[] args) throws IOException {
        TargetObjectProxy proxy = new TargetObjectProxy(new TargetObject());
        ITarget target = proxy.getInstance();
        target.fun1();
        System.out.println(target.fun2("hello"));
    }
}

原理

JDK動態(tài)代理實際上是在代碼運行期間動態(tài)生成代理類,編譯加載到JVM中,這里主要關注Proxy.newProxyInstance方法

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
         * Look up or generate the designated proxy class.
         */
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
         * Invoke its constructor with the designated invocation handler.
         */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

上述代碼:

Class<?> cl = getProxyClass0(loader, intfs);

生成了代理類的字節(jié)碼對象,跟蹤發(fā)現(xiàn)在Proxy.ProxyClassFactory中有個apply方法,該方法中

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);

生成代理類的字節(jié)數組然后通過defineClass加載到JVM中

看一下generateProxyClass方法

public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
    //定義代理代碼生成器
    ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
    final byte[] var4 = var3.generateClassFile();
    if (saveGeneratedFiles) {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                try {
                    int var1 = var0.lastIndexOf(46);
                    Path var2;
                    if (var1 > 0) {
                        Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
                        Files.createDirectories(var3);
                        var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                    } else {
                        var2 = Paths.get(var0 + ".class");
                    }

                    Files.write(var2, var4, new OpenOption[0]);
                    return null;
                } catch (IOException var4x) {
                    throw new InternalError("I/O exception saving generated file: " + var4x);
                }
            }
        });
    }

    return var4;
}

最后使用反射生成代理類的對象

CGlib動態(tài)代理

使用

定義代理類實現(xiàn)MethodIntercept接口實現(xiàn)intercept方法

public class CGlibProxy implements MethodInterceptor {
    private Object target;

    public <T> T getInstance(Class<T> clazz) throws IllegalAccessException, InstantiationException {
        target = clazz.newInstance();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return (T) enhancer.create();
    }

    public void before(){
        System.out.println("CGlib動態(tài)代理方法執(zhí)行前...");
    }

    public void after(){
        System.out.println("CGlib動態(tài)代理方法執(zhí)行后...");
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object o1 = methodProxy.invokeSuper(o, objects);
        after();
        return o1;
    }
}

原理

CGlib動態(tài)代理實際上通過繼承目標類實現(xiàn)的,是通過ASM生成class字節(jié)碼

代理類調用方法時會被攔截器攔截,最終執(zhí)行methodProxy.invokeSuper方法

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
        init();
        FastClassInfo fci = fastClassInfo;
        return fci.f2.invoke(fci.i2, obj, args);
    }
    catch (InvocationTargetException e) {
        throw e.getTargetException();
    }
}

JDK動態(tài)代理和CGlib動態(tài)代理對比

1.JDK 動態(tài)代理是實現(xiàn)了被代理對象的接口,CGLib 是繼承了被代理對象。
2.JDK 和 CGLib 都是在運行期生成字節(jié)碼,JDK 是直接寫 Class 字節(jié)碼,CGLib 使用 ASM
框架寫 Class 字節(jié)碼,Cglib 代理實現(xiàn)更復雜,生成代理類比 JDK 效率低。
3.JDK 調用代理方法,是通過反射機制調用,CGLib 是通過 FastClass 機制直接調用方法,
CGLib 執(zhí)行效率更高。

CGlib生成代理類效率低,執(zhí)行效率高

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容