23种设计模式7_代理模式之二动态代理

23种设计模式7_代理模式之二动态代理

1 基本介绍

代理模式:为其他对象提供一种代理以控制对这个对象的访问

代理模式也叫委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上都是在更加特殊的场合采用了代理模式。在日常开发应用种,代理模式能够提供非常好的访问控制。

代理可分为两种:一种是静态代理,由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的**.class**文件就已经存在了;另一种是动态代理:在程序运行时,运用反射机制动态创建而成。

静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。
静态代理参考:23种设计模式7_代理模式之一静态代理

静态代理事先知道自己要代理的是什么,而动态代理不知道要代理什么,只有在运行时才知道。

动态代理的两种实现方式:

JDK动态代理*** 利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler*来处理

CGlib动态代理*** 利用ASM开源包(开源的Java字节码编辑库,操作字节码),将代理对象类的class文件*加载进来,通过修改其字节码生成子类来处理。

2 JDK动态代理

继续通过游戏代练这个案例来演示动态代理,UML类图如下:
在这里插入图片描述

被代理接口:

public interface IGamePlayer {
    // 登录
    void login(String playName, String password);
    // 打怪
    void killMonster();
    // 升级
    void levelUp();
}

被代理类:

public class GamePlayer implements IGamePlayer{
    // 游戏玩家名称
    private String playerName;

    public GamePlayer(String playerName) {
        this.playerName = playerName;
    }

    @Override
    public void login(String playName, String password) {
        System.out.println(this.playerName + "登录游戏成功!");
    }

    @Override
    public void killMonster() {
        System.out.println(this.playerName + "正在打怪!");
    }

    @Override
    public void levelUp() {
        System.out.println("恭喜" + this.playerName + "又升了一级!");
    }
}

动态代理类GamePlayerIH实现InvocationHandler

public class GamePlayerIH implements InvocationHandler {
    // 被代理的实例
    Object obj = null;

    // 我要代理谁
    public GamePlayerIH(Object _obj) {
        this.obj = _obj;
    }

    // 调用被代理的方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(this.obj, args);
    }
}

客户端

public class Client {
    public static void main(String[] args) {
        IGamePlayer player = new GamePlayer("zhangsan");
        // 定义一个Handler
        InvocationHandler handler = new GamePlayerIH(player);
        // 获取类加载器
        ClassLoader cl = player.getClass().getClassLoader();
        // 动态产生一个代理者
        IGamePlayer playerProxy = (IGamePlayer) Proxy.newProxyInstance(cl, new Class[]{IGamePlayer.class}, handler);
        playerProxy.login("zhangsan","123456");
        playerProxy.killMonster();
        playerProxy.levelUp();
    }
}

演示结果:
在这里插入图片描述

Spring的核心之一AOP就是通过这种方式实现的,这种方式对我们的设计、编码有非常大的影响,对于日志、事务、权限等都可以在系统设计阶段不用考虑,而在设计后通过AOP的方式切过去。

主题还用IGamePlayer接口和GamePlayer实现类;动态代理的Handler类还是GamePlayerIH,不过可以在invoke方法种添加详细的业务逻辑代码,下面对动态代理类进行优化:

前置处理:

public interface IAdvice {
    void exec();
}
public class BeforeAdvice implements IAdvice{
    @Override
    public void exec() {
        System.out.println("我是前置通知,我被执行了!!!!");
    }
}

动态代理类:

public class DynamicProxy {
    public static <T> T newProxyInstance(
            ClassLoader loader,
            Class<?>[] interfaces,
            InvocationHandler handler) {
        // 寻找JoinPoint连接点,AOP框架使用元数据定义
        if (true) {
            // 执行前置通知
            new BeforeAdvice().exec();
        }
        return (T) Proxy.newProxyInstance(loader, interfaces, handler);
    }

    // 可以添加具体业务的代理,如IGamePlayer
    public static <T> T newProxyInstance(IGamePlayer gamePlayer) {
        // 获得ClassLoader
        ClassLoader loader = gamePlayer.getClass().getClassLoader();
        // 获得接口数组
        Class<?>[] interfaces = gamePlayer.getClass().getInterfaces();
        // 获取handler
        GamePlayerIH handler = new GamePlayerIH(gamePlayer);
        return newProxyInstance(loader, interfaces, handler);
    }
}

客户端:

public class Client {
    public static void main(String[] args) {
        IGamePlayer player = new GamePlayer("zhangsan");
        // 动态产生一个代理者
        IGamePlayer playerProxy = DynamicProxy.newProxyInstance(player);
        playerProxy.login("zhangsan","123456");
        playerProxy.killMonster();
        playerProxy.levelUp();
    }
}

演示结果:
在这里插入图片描述

3 CGlib动态代理

3.1 简单介绍

CGLIB是一个优秀的动态代理框架,它的底层使用ASM在内存中动态的生成被代理类的子类,使用CGLIB即使代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用,它的运行速度要远远快于JDK的Proxy动态代理。

3.2 CGLIB的核心类

net.sf.cglib.proxy.Enhancer – 主要的增强类
net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类,它是Callback接口的子接口,需要用户实现
net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用,如使用:Object o = methodProxy.invokeSuper(proxy, args);//虽然第一个参数是被代理对象,也不会出现死循环的问题。

3.3 Enhancer

Enhancer 是增强器,它对你想代理的类进行扩展

它的两个主要方法:

setSuperclass 设置需要代理/增强的类(相当于父类)
setCallback 设置回调的拦截器,正常是MethodInterceptor的实现类

3.4 MethodInterceptor

MethodInterceptor 方法拦截器,在调用目标方法的时候会调用它的实现类进行拦截。它最重要方法为intercept,进行拦截并执行。

intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)

第一个参数是增强类;

第二个参数是拦截的方法;

第三个参数是拦截的方法的参数;

第四个参数是Method的代理类。

3.5 两种方式

cglib有两种可选方式,继承和引用。第一种是基于继承实现的动态代理,所以可以直接通过super调用target方法,但是这种方式在spring中是不支持的,因为这样的话,这个target对象就不能被spring所管理,所以cglib还是采用类似jdk的方式,通过持有target对象来达到拦截方法的效果。两种方式其一是通过使用java.lang.reflect.Method 对象的一般反射调用;另一种就是使用net.sf.cglib.proxy.MethodProxy 对象调用。通常使用第二种,因为它更快。

3.6 代码演示

创建一个普通类:

public class Animal {
    public void run() {
        System.out.println("动物在奔跑!!!!");
    }
}

创建cglib代理类,实现MethodInterceptor接口

public class CglibProxy implements MethodInterceptor {
    // 增强器
    private Enhancer enhancer = new Enhancer();

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("-------------before-----------------");
        System.out.println("method.getName():" + method.getName());
        System.out.println("methodProxy.getSuperName():" + methodProxy.getSuperName());
        Object obj = methodProxy.invokeSuper(o, objects);
        System.out.println("-------------after-----------------");
        return obj;
    }

    public Object getProxy(Object obj) {
        enhancer.setSuperclass(obj.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }
}

main

public class Client {
    public static void main(String[] args) {
        Animal animalProxy = (Animal) new CglibProxy().getProxy(new Animal());
        animalProxy.run();
    }
}

运行发现报错:

在这里插入图片描述

报错内容是缺少类,发现我引入的包是cglib-3.1.jar,后面又导入了asm-all-4.1.jar,就能够正常运行了。

在这里插入图片描述
cglib资源包(包含cglib-3.1.jar和asm-all-4.1.jar)

3.7 拓展

被cglib代理的类只要是普通的类就行,但是要确保类和方法上没有被**final关键字修饰。用final修饰的类会直接抛出异常,但是修饰方法就不会抛出异常,但是此方法不会被代理,但是不影响代理其他没有被final**修改的方法。

3.7.1 死循环演示

CglibProxy进行修改:

public class CglibProxy implements MethodInterceptor {
    // 创建增强器
    private Enhancer enhancer = new Enhancer();
    // 被代理对象,如果使用invoke会使用到
    private Object target;

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("-------------before-----------------");
        System.out.println("method.getName():" + method.getName());
        System.out.println("methodProxy.getSuperName():" + methodProxy.getSuperName());
        Object obj = methodProxy.invoke(o, objects);
        //Object obj = methodProxy.invoke(this.target, objects);
        //Object obj = methodProxy.invokeSuper(o, objects);
        System.out.println("-------------after-----------------");
        return obj;
    }

    public Object getProxy(Object obj) {
        this.target = obj;
        enhancer.setSuperclass(obj.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }
}

演示结果

在这里插入图片描述

可以看到一直在打印,导致**StackOverflowError**。注释掉的两个使用方式都是能够正常使用的。

3.7.2 原理分析

首先,我们需要获取到代理类的字节文件,然后对其反编译。在main方法种添加下面这句话:

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\classes");

再运行一遍你会发现有生成的代理class文件有三个,我们主要看**Animal$$EnhancerByCGLIB$$e01ba0a1**代理类!

在这里插入图片描述

反编译后(代码很长,我截取需要演示的部分,其他省略号):

public class Animal$$EnhancerByCGLIB$$e01ba0a1 extends Animal implements Factory {
    private boolean CGLIB$BOUND;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static final Method CGLIB$run$0$Method;
    private static final MethodProxy CGLIB$run$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$finalize$1$Method;
    private static final MethodProxy CGLIB$finalize$1$Proxy;
    private static final Method CGLIB$equals$2$Method;
    private static final MethodProxy CGLIB$equals$2$Proxy;
    private static final Method CGLIB$toString$3$Method;
    private static final MethodProxy CGLIB$toString$3$Proxy;
    private static final Method CGLIB$hashCode$4$Method;
    private static final MethodProxy CGLIB$hashCode$4$Proxy;
    private static final Method CGLIB$clone$5$Method;
    private static final MethodProxy CGLIB$clone$5$Proxy;

    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("cn.test.Animal$$EnhancerByCGLIB$$e01ba0a1");
        Class var1;
        CGLIB$run$0$Method = ReflectUtils.findMethods(new String[]{"run", "()V"}, (var1 = Class.forName("cn.test.Animal")).getDeclaredMethods())[0];
        CGLIB$run$0$Proxy = MethodProxy.create(var1, var0, "()V", "run", "CGLIB$run$0");
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$finalize$1$Method = var10000[0];
        CGLIB$finalize$1$Proxy = MethodProxy.create(var1, var0, "()V", "finalize", "CGLIB$finalize$1");
        CGLIB$equals$2$Method = var10000[1];
        CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
        CGLIB$toString$3$Method = var10000[2];
        CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
        CGLIB$hashCode$4$Method = var10000[3];
        CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
        CGLIB$clone$5$Method = var10000[4];
        CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
    }

    final void CGLIB$run$0() {
        super.run();
    }

    public final void run() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$run$0$Method, CGLIB$emptyArgs, CGLIB$run$0$Proxy);
        } else {
            super.run();
        }
    }
    
    // ......
}

从反编译后的源码来看,可以看到被代理类的方法会生成两个代理方法,一个是跟我Animal种run方法同名的run方法,另一个是CGLIB$run$0()

1.CGLIB$run$0()是调用被代理的方法,可以看到别的啥都没干。

2.同名的**run()** 首先判断有没有设置callback,我们在代码中设置为CglibProxy,所以就会调用CglibProxy的**intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)**方法

下面再看一下**MethodProxy**的源码,invoke和invokeSuper的具体执行流程。

public Object invoke(Object obj, Object[] args) throws Throwable {
    try {
        this.init();
        MethodProxy.FastClassInfo fci = this.fastClassInfo;
        return fci.f1.invoke(fci.i1, obj, args);
    } catch (InvocationTargetException var4) {
        throw var4.getTargetException();
    } catch (IllegalArgumentException var5) {
        if (this.fastClassInfo.i1 < 0) {
            throw new IllegalArgumentException("Protected method: " + this.sig1);
        } else {
            throw var5;
        }
    }
}

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

invoke和invokeSuper第一步都会执行**init()**这个方法

private void init() {
    if (this.fastClassInfo == null) {
        synchronized(this.initLock) {
            if (this.fastClassInfo == null) {
                MethodProxy.CreateInfo ci = this.createInfo;
                MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
                fci.f1 = helper(ci, ci.c1);
                fci.f2 = helper(ci, ci.c2);
                fci.i1 = fci.f1.getIndex(this.sig1);
                fci.i2 = fci.f2.getIndex(this.sig2);
                this.fastClassInfo = fci;
                this.createInfo = null;
            }
        }
    }
}

这个init方法就是加载**MethodProxy.FastClassInfo**。

c1 指的是我们的代理类,即**Animal**

c2 代理类,即**Animal$$EnhancerByCGLIB$$e01ba0a1.class**

f1 代理类,即**Animal$$FastClassByCGLIB$$b2e76df5.class**

f2 代理类,即**Animal$$EnhancerByCGLIB$$e01ba0a1$$FastClassByCGLIB$$9a34f4cc.class**

上述三个class文件即**E:\\classes**这个路径下(当然还有你的包路径)生成出来的三个class文件

i1 即**run()**

i2 即**CGLIB$run$0()**

为什么会生成f1和f2这两个代理类,这里就涉及到invoke和invokeSuper两个方法的区别,invoke就是f1.invoke(),invokeSuper就是f2.invoke()。

这也就解释了为什么会发生死循环了,我们用的是invoke()方法,但是传入的是一个代理对象,从上面的代理类的代码可以看到还是会回到invoke()这个方法,所以就造成了死循环。

3.7.3 总结

cglib代理比jdk代理快很多,但是被代理的类和方法不能被final修改,而且有invoke和invokeSuper这两个方法,使用的时候需要谨慎。

× Object obj = methodProxy.invoke(o, objects);
Object obj = methodProxy.invoke(this.target, objects);
Object obj = methodProxy.invokeSuper(o, objects);

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奔跑吧,高同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值