前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[设计模式] 代理模式

[设计模式] 代理模式

作者头像
呼延十
发布2020-11-04 10:32:30
3000
发布2020-11-04 10:32:30
举报
文章被收录于专栏:呼延呼延

前言

之前的设计模式文章, 都是因为读书而做笔记写的, 这次的代理模式, 是真正的需求驱动学习, 学习驱动文章了….

其实动态代理及cglib动态代理的大名, 很早就听说过了, 作为面试必备问题, 从校招时候就经常听到, 但是这个名字太唬人了, 且原来一直没有遇到应用场景, 因此也就懒着一直没有学习.

这次遇到了应用场景, 简单学习之后进行了应用的开发, 闲下来之后当然是要追根究底并且进行一番性能测试了.

实际场景

首先来简单的介绍一下这次应用到的场景:

首先我有一个实现了很多方法的类, 每一个方法都是一个对外提供的接口. 如下:

代码语言:javascript
复制
/**
 * @author pfliu
 * @date 2020/11/01.
 * @brief 服务主类
 */
public class MyService implements ServiceInterface {

    @Override
    public void doSomething1() {
        System.out.println("doSomething1");

    }

    @Override
    public int getSomeThing1(String name) {
        System.out.println("getSomeThing1" + "\t" + name);
        return 0;
    }
}

客户端调用代码如下:

代码语言:javascript
复制
/**
 * @author pfliu
 * @date 2020/11/01.
 * @brief 服务主类
 */
public class MyService implements ServiceInterface {

    private ThreadLocal<List<Object>> context = ThreadLocal.withInitial(ArrayList::new);

    @Override
    public void doSomething1() {
        context.get().clear();
        context.get().add(System.currentTimeMillis());
        System.out.println("doSomething1");

    }

    @Override
    public int getSomeThing1(String name) {
        context.get().clear();
        context.get().add(name);
        context.get().add(System.currentTimeMillis());
        System.out.println("getSomeThing1" + "\t" + name);
        return 0;
    }
}

此时我要接入一个监控系统, 需要在每一次请求处理之前, 向ThreadLocal中写入一些数据, 代码就变成了这样:

代码语言:javascript
复制
/**
 * @author pfliu
 * @date 2020/11/01.
 * @brief 服务主类
 */
public class MyService implements ServiceInterface {

    private ThreadLocal<List<Object>> context = ThreadLocal.withInitial(ArrayList::new);

    @Override
    public void doSomething1() {
        context.get().clear();
        context.get().add(System.currentTimeMillis());
        System.out.println("doSomething1");

    }

    @Override
    public int getSomeThing1(String name) {
        context.get().clear();
        context.get().add(name);
        context.get().add(System.currentTimeMillis());
        System.out.println("getSomeThing1" + "\t" + name);
        return 0;
    }
}

我在每次请求之前, 记录了当前请求的实际参数及请求时间戳. 这样子代码也太难看了吧. 两个方法都这么难看了, 我50多个方法的类, 写出来岂不是要气死同事.

此时就想到了用代理模式来进行代码的优化, 同时后续如果有更多的事情需要在每次请求前来做, 修改代码也会更加舒适一点.

静态代理

静态代理, 其实就是写一个新的代理类, 然后实现同样的接口, 持有我们目标类的引用, 每次请求到来之后, 做完想要的工作, 再请求真正的目标类.

简单的改造之后, 变成了如下的样子:

代码语言:javascript
复制
/**
 * @author pfliu
 * @date 2020/11/01.
 * @brief
 */
public class StaticProxy implements ServiceInterface {

    private final MyService myService;
    private final ThreadLocal<List<Object>> context;

    public StaticProxy(MyService myService, ThreadLocal<List<Object>> context) {
        this.myService = myService;
        this.context = context;
    }


    @Override
    public void doSomething1() {
        this.context.get().clear();
        this.context.get().add(System.currentTimeMillis());
        this.myService.doSomething1();
    }

    @Override
    public int getSomeThing1(String name) {
        this.context.get().clear();
        this.context.get().add(name);
        this.context.get().add(System.currentTimeMillis());
        return this.myService.getSomeThing1(name);
    }
}

此时客户端就不用持有MyService的实例了, 持有StaticProxy即可.

代码语言:javascript
复制
    public static void main(String[] args) {
        ServiceInterface server = new MyService();
        StaticProxy proxy = new StaticProxy(server, new ThreadLocal<List<Object>>());
        proxy.doSomething1();
        proxy.getSomeThing1("huyanshi");
    }

这…..根本就是换汤不换药啊, 原来我要写一个恶心的类, 现在要写一个恶心的类+一个正常的服务类, 区别也不是很大啊, 当我需要修改写入threadlocal的内容时, 几十个接口仍然是需要一个一个改过去啊.

优缺点

照例分析下静态代理的优劣势:

优点:

  • 实现简单, 直接重新写写个类.
  • 性能没啥损失, 源码编译的,多走一层调用而已.

缺点:

  • 代码太过于冗余.
  • 不易扩展, 当接口新增一个方法, 我们要修改目标类本身和代理类.

动态代理

JDK自带了代理模式, 提供了一个动态代理给我们, 让我们来试试.

代理类代码如下:

代码语言:javascript
复制
public class DynamicProxy {

    private final Object target;
    private final ThreadLocal<List<Object>> context;

    public DynamicProxy(Object target, ThreadLocal<List<Object>> context) {
        this.target = target;
        this.context = context;
    }

    public Object getInstance() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                (proxy, method, args) -> {
                    context.get().clear();
                    context.get().add(args);
                    context.get().add(System.currentTimeMillis());
                    return method.invoke(target, args);
                });
    }
}

客户端调用如下:

代码语言:javascript
复制
    // 动态代理
    public static void main(String[] args) {
        ServiceInterface server = new MyService();

        ThreadLocal<List<Object>> context = ThreadLocal.withInitial(ArrayList::new);
        DynamicProxy proxy = new DynamicProxy(server, context);
        ServiceInterface instance = (ServiceInterface) proxy.getInstance();
        instance.doSomething1();
        instance.getSomeThing1("huyanshi");
    }

这种实现方式看起来简单多了,只需要在代理类中写一次我们要做的内容,之后无论目标类扩展多少方法,我们都是可以支持的,比较符合我们的设想。

JDK动态代理是怎么做到的呢?让我们跟着源码看一下。

首先是java.lang.reflect.Proxy#newProxyInstance的代码:

代码语言:javascript
复制
    @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);
        }

        // 拿到生成的代理类Class
        Class<?> cl = getProxyClass0(loader, intfs);

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

            // 根据Class拿到对应的构造方法
            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);
        }
    }

那么重点就来到了如何获取一个代理类,也就是java.lang.reflect.Proxy#getProxyClass0的代码:

代码语言:javascript
复制
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

这个方法我们可以看出是从cache中获取了一个类,那么我们既然是研究如何获取,就不关心cache的填充,命中等等逻辑了,直接看最初往cache里面添加类的时候,是如何创建除对应的代理类的。

寻找之后,就会发现来到了java.lang.reflect.Proxy.ProxyClassFactory这个类,从名字可以看出他是负责创建代理类的工厂,代码如下:

关键地方已经注释了:

代码语言:javascript
复制
    private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        // prefix for all proxy class names
        private static final String proxyClassNamePrefix = "$Proxy";

        // next number to use for generation of unique proxy class names
        private static final AtomicLong nextUniqueNumber = new AtomicLong();

        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class<?> intf : interfaces) {
                /*
                 * Verify that the class loader resolves the name of this
                 * interface to the same Class object.
                 */
                Class<?> interfaceClass = null;
                try {
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                /*
                 * Verify that the Class object actually represents an
                 * interface.
                 */
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                /*
                 * Verify that this interface is not a duplicate.
                 */
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }

            String proxyPkg = null;     // package to define proxy class in
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

            /*
             * Record the package of a non-public proxy interface so that the
             * proxy class will be defined in the same package.  Verify that
             * all non-public proxy interfaces are in the same package.
             */
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

           // 调用ProxyGenerator来生成一个代理类的字节数组
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
        }
    }

那么我们能反编译生成的类,看看长什么样子吗?当然是可以的,我们用上面的示例类来试一下:

代码语言:javascript
复制
    public static void main(String[] args) {
        // 设置为保存
        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        String name = "ProxyHello";
        byte[] data = ProxyGenerator.generateProxyClass(name, new Class[] { MyService.class });
        try {
            // 输出路径
            FileOutputStream out = new FileOutputStream("your_path" + name + ".class");
            out.write(data);
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

之后使用反编译工具(INTELLIJ就可以) 来看看生成的类长什么样子。

代码语言:javascript
复制
public final class ProxyHello extends Proxy implements MyService {
    private static Method m1;
    private static Method m9;
    private static Method m2;
    private static Method m3;
    private static Method m4;
    private static Method m6;
    private static Method m5;
    private static Method m8;
    private static Method m10;
    private static Method m0;
    private static Method m7;

    public ProxyHello(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void notify() throws  {
        try {
            super.h.invoke(this, m9, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void doSomething1() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int getSomeThing1(String var1) throws  {
        try {
            return (Integer)super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void wait(long var1) throws InterruptedException {
        try {
            super.h.invoke(this, m6, new Object[]{var1});
        } catch (RuntimeException | InterruptedException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final void wait(long var1, int var3) throws InterruptedException {
        try {
            super.h.invoke(this, m5, new Object[]{var1, var3});
        } catch (RuntimeException | InterruptedException | Error var5) {
            throw var5;
        } catch (Throwable var6) {
            throw new UndeclaredThrowableException(var6);
        }
    }

    public final Class getClass() throws  {
        try {
            return (Class)super.h.invoke(this, m8, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void notifyAll() throws  {
        try {
            super.h.invoke(this, m10, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void wait() throws InterruptedException {
        try {
            super.h.invoke(this, m7, (Object[])null);
        } catch (RuntimeException | InterruptedException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m9 = Class.forName("design_patterns.proxy.MyService").getMethod("notify");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("design_patterns.proxy.MyService").getMethod("doSomething1");
            m4 = Class.forName("design_patterns.proxy.MyService").getMethod("getSomeThing1", Class.forName("java.lang.String"));
            m6 = Class.forName("design_patterns.proxy.MyService").getMethod("wait", Long.TYPE);
            m5 = Class.forName("design_patterns.proxy.MyService").getMethod("wait", Long.TYPE, Integer.TYPE);
            m8 = Class.forName("design_patterns.proxy.MyService").getMethod("getClass");
            m10 = Class.forName("design_patterns.proxy.MyService").getMethod("notifyAll");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m7 = Class.forName("design_patterns.proxy.MyService").getMethod("wait");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

为了避免麻烦,我直接将生成类反编译后的所有code粘贴上来了。

有以下几点需要注意:

  • 生成的代理类,继承了Proxy类且实现了目标类实现的接口。
  • 自动生成了equals,notify等方法的代理代码,因此使用动态代理生成的代理类,也可以代理这几个Object的方法.
  • 直接生成了目标类的几个方法的代码。

比如:

代码语言:javascript
复制
    public final void doSomething1() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

当客户端调用代理类的doSomething1时,直接调用了我们传入的`java.lang.reflect.InvocationHandler的invode方法,通过反射执行对应方法。

到这里,JDK动态代理的原理就分析完了,我们可以看出他有一个限制,那就是如果目标类没有实现接口,而是继承了某些abstract类, 或者干脆是完全独立的类,JDK动态代理就束手无策了。

优缺点

优点:

  • 代码简洁
  • 易扩展

缺点:

  • 反射可能带来的性能损失
  • 目标类必须实现了对应的接口

cglib 实现动态代理

cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。

cglib特点

  • JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。 如果想代理没有实现接口的类,就可以使用CGLIB实现。
  • CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。 它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。
  • CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。 不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。

cglib与动态代理最大的区别就是

  • 使用动态代理的对象必须实现一个或多个接口
  • 使用cglib代理的对象则无需实现接口,达到代理类无侵入。

同样的,我们先写一个简单的示例:

代码语言:javascript
复制
public class CglibProxy implements MethodInterceptor {

    private final Object target;
    private final ThreadLocal<List<Object>> contextTL;


    public CglibProxy(Object target, ThreadLocal<List<Object>> contextTL) {
        this.target = target;
        this.contextTL = contextTL;
    }

    public Object getProxyInstance() {
        Enhancer en = new Enhancer();
        en.setSuperclass(target.getClass());
        en.setCallback(this);
        return en.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        this.contextTL.get().clear();
        this.contextTL.get().addAll(Arrays.asList(args));
        this.contextTL.get().add(System.currentTimeMillis());
        return method.invoke(target, args);
    }
}

代码和JDK动态代理差不多简洁,但是实现方式从传入一个java.lang.reflect.InvocationHandler变成实现MethodInterceptor接口,其实都差不多,主要是为了嵌入自己的逻辑。

但是实现原理区别是很大的。

跟随代码一路点进去,会发现cglib也是生成了一个代理类,加载之后使用,其间的代码这里不再赘述,直接反编译生成的代码。

2020-11-02-17-21-20
2020-11-02-17-21-20

从类定义中,可以看出,生成的类是继承了目标类 且 实现了Factory接口, 与jdk的继承了Proxy类且实现了目标类实现的接口是有显著的区别的。

其中关键的业务方法为:

2020-11-02-17-24-18
2020-11-02-17-24-18

由于继承了目标类,因此可以重写父类的方法,在重写方法内部,首先调用intercept来执行我们嵌入的代码,之后调用父类的对应方法,如:super.getSomeThing1.

优缺点:

优点:

  • 代码简洁
  • 扩展方便
  • 不需要强制要求目标类实现接口

缺点:

  • 如果目标类为final类,无法继承,那么cglib无法为其生成代理类

其实这里本应该有个性能测试,但是由于设计实验太难了,尝试了几次之后获得的结果不稳定,因此我放弃了。

如果需要性能测试可以google其他大佬的数据.

完。

联系我

最后,欢迎关注我的个人公众号【 呼延十 】,会不定期更新很多后端工程师的学习笔记。 也欢迎直接公众号私信或者邮箱联系我,一定知无不言,言无不尽。

以上皆为个人所思所得,如有错误欢迎评论区指正。

欢迎转载,烦请署名并保留原文链接。

联系邮箱:huyanshi2580@gmail.com

更多学习笔记见个人博客或关注微信公众号 <呼延十 >——>呼延十

var gitment = new Gitment({ id: '[设计模式] 代理模式', // 可选。默认为 location.href owner: 'hublanker', repo: 'blog', oauth: { client_id: '2297651c181f632a31db', client_secret: 'a62f60d8da404586acc965a2ba6a6da9f053703b', }, }) gitment.render('container')



本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 实际场景
  • 静态代理
    • 优缺点
    • 动态代理
      • 优缺点
      • cglib 实现动态代理
        • 优缺点:
        • 联系我
        相关产品与服务
        云数据库 Redis
        腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档