美文网首页
代理模式(Proxy Pattern)

代理模式(Proxy Pattern)

作者: 慕尼黑凌晨四点 | 来源:发表于2020-12-21 15:52 被阅读0次

简介

代理,原本要自己做的事情,拿去给别人做了。比如说A类的invokeA()方法中调用B.invokeB()方法。

防止直接访问目标对象给系统带来的不必要复杂性。

image-20201221100808172.png

静态代理

上面这种类图是静态代理,不看代码了。

动态代理

先随便写一个需要代理的类;

interface Hero { void Q(); }
public class YaSuo implements Hero{
    @Override
    public void Q() {
        System.out.println("hasak");
    }
}

JDK动态代理

需要用到Java反射机制。用java.lang.reflect.Proxy进行动态代理:

YaSuo yaSuo = new YaSuo();
Hero proxyYs = (Hero) Proxy.newProxyInstance(yaSuo.getClass().getClassLoader(),yaSuo.getClass().getInterfaces(), new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //do something before
        return method.invoke(yaSuo);
        //do sonmething after
    }
});
proxyYs.Q();

生成的proxyYs就是我们想要的代理类了,它继承了原被代理类的所有接口 外加一个Proxy类,实现接口所有方法,并在内部代理给原对象。

想看生成的代理类 代码 ,可以在VM -options中添加:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true 。生成的类文件如下:

final class $Proxy0 extends Proxy implements Hero {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(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 Q() throws  {
        try {
            super.h.invoke(this, m3, (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 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);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("proxy.Hero").getMethod("Q");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

CGLIB动态代理

基于ASM机制实现,通过生成业务类的子类作为代理类。需要引入cglib。

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>
implementation 'cglib:cglib:3.3.0'

使用:

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(YaSuo.class);
enhancer.setCallback(new MethodInterceptor(){
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        methodProxy.invokeSuper(o,objects);
        return objects;
    }
});

YaSuo proxy = (YaSuo) enhancer.create();
proxy.Q();

JDK Proxy 的优势:

  • 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
  • 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
  • 代码实现简单。

基于类似 cglib 框架的优势:

  • 无需实现接口,达到代理类无侵入
  • 只操作我们关心的类,而不必为其他相关类增加工作量。
  • 高性能

以下部分来自于:作者:小旋锋
链接:https://juejin.cn/post/6844903744954433544
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

面试题

描述动态代理的几种实现方式?分别说出相应的优缺点

代理可以分为 "静态代理" 和 "动态代理",动态代理又分为 "JDK动态代理" 和 "CGLIB动态代理" 实现。

静态代理:代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理对象而真正调用的是 Real Object

  • 优点:可以很好的保护实际对象的业务逻辑对外暴露,从而提高安全性。
  • 缺点:不同的接口要有不同的代理类实现,会很冗余

JDK 动态代理

  • 为了解决静态代理中,生成大量的代理类造成的冗余;
  • JDK 动态代理只需要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现,
  • jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象
  • jdk动态代理之所以只能代理接口是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口
  • 优点:解决了静态代理中冗余的代理实现类问题。
  • 缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。

CGLIB 代理

  • 由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK方式解决不了;
  • CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。
  • 实现方式实现 MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现。
  • 但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。
  • 同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。
  • 优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。
  • 缺点:技术实现相对难理解些。

相关文章

网友评论

      本文标题:代理模式(Proxy Pattern)

      本文链接:https://www.haomeiwen.com/subject/hjgrnktx.html