动态代理原理总结

本文为笔者近期在公司进行的技术分享,在此记录总结。

相比于静态代理方式需要开发者自己声明代理类,动态代理能够在运行中生成代理类,实现无侵入的代码拓展,简化了开发,提高代码拓展性。

先声明一个委托接口和委托类:

public interface Delegation {
        void doSomething();
}

public class DelegationImpl implements Delegation {

        @Override
        public void doSomething() {
            System.out.println("real Delegation exec");
        }
}

动态代理实现方式有以下几种,本文重点讲述前两种:

JDK 动态代理

JDK 1.3开始支持动态代理类创建,常用于 Spring AOP ,核心类 java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler

  1. 首先创建实际需要执行的代码逻辑,即实现 InvocationHandler 接口 invoke() 方法
public class JdkProxy implements InvocationHandler {

    private Object bean;

    public JdkProxy(Object bean) {
        this.bean = bean;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if (methodName.equals("doSomething")){
            System.out.println("before doSomething");
        }
        return method.invoke(bean, args);
    }
}
  1. 生成动态代理并执行
public static void main(String[] args) {
        JdkProxy proxy = new JdkProxy(new DelegationImpl());
    Delegation delegation = (Delegation) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Delegation.class}, proxy);
    delegation.doSomething();
}
  1. 正常情况下我们看不到动态代理的具体代码,这里我们可以用以下代码输出动态代理的字节码,然后反编译查看具体代码
byte[] classFile = ProxyGenerator.generateProxyClass("proxyJdk", new Class[]{Delegation.class});
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("D:/ProxyJdk.class");
            fos.write(classFile);
            fos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            if(fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
// Source code is decompiled from a .class file using FernFlower decompiler.
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import org.soga.yasuo.web.Delegation;

public final class proxyJdk extends Proxy implements Delegation {
   private static Method m1;
   private static Method m3;
   private static Method m2;
   private static Method m0;

   publicproxyJdk(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 doSomething() 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("org.soga.yasuo.web.Delegation").getMethod("doSomething");
         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());
      }
   }
}

从反编译的代码可以看出生成的代理类继承 Proxy 类,所以委托类必须实现一个接口(Java 仅支持单继承)。

InvocationHandlerpublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable 方法中,调用 method.invoke(proxy, args) 会出现死循环,所以基本都会传入委托类,反射调用委托类对应的方法。

Cglib 动态代理

Spring 在 5.X 之前默认的动态代理实现一直是jdk动态代理。但是从 5.X 开始,Spring 就开始默认使用 Cglib 来作为动态代理实现。并且 Springboot 从 2.X 开始也转向了 Cglib 动态代理实现。

是什么导致了 Spring 体系整体转投 Cglib 呢,jdk 动态代理又有什么缺点呢?

那么我们现在就要来说下 Cglib 的动态代理。

Cglib是一个开源项目,它的底层是字节码处理框架 ASM,Cglib 提供了比 jdk 更为强大的动态代理。主要相比 jdk 动态代理的优势有:

  • Jdk 动态代理只能基于接口,代理生成的对象只能赋值给接口变量,而Cglib就不存在这个问题,Cglib 是通过生成子类来实现的,代理对象既可以赋值给实现类,又可以赋值给接口。
  • Cglib 速度比 jdk 动态代理更快,性能更好。
  1. 创建 CglibProxy :
public class CglibProxy implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();

    private Object bean;

    public CglibProxy(Object bean) {
        this.bean = bean;
    }

    public Object getProxy(){
        //设置需要创建子类的类
        enhancer.setSuperclass(bean.getClass());
        enhancer.setCallback(this);
        //通过字节码技术动态创建子类实例
        return enhancer.create();
    }
    //实现MethodInterceptor接口方法
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        String methodName = method.getName();
        if (methodName.equals("doSomething")){
            System.out.println("before doSomething");
        }
        //调用原bean的方法
        return method.invoke(bean,args);
    }
}
  1. 执行代理
public static void main(String[] args) {
    CglibProxy proxy = new CglibProxy(new DelegationImpl());
    Delegation delegation = (Delegation) proxy.getProxy();
    delegation.doSomething();
  • Cglib 基于继承实现,会动态生成一个委托类的子类,所以对于 static 方法、private 方法、final 方法和 final 类则无法代理(方法无法被复写、类无法被继承)。
  • 相比于 jdk,Cglib 既可以代理接口,又可以代理普通类。
  • Cglib 会默认代理Object中 equals, toString, hashCode, clone等方法。比JDK代理多了clone。
  • Cglib 做了方法访问优化,使用建立方法索引的方式避免了传统JDK动态代理需要通过Method方法反射调用,性能更优

Javassist

Javassist 是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。相对于bcel, asm等这些工具,开发者不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。性能比ASM稍差些,但是简单。

在日常使用中,javassit 通常被用来动态修改字节码。它也能用来实现动态代理的功能。Dubbo 中使用 Javassist 生成动态类。

public class JavassitProxy {

    private Object bean;

    public JavassitProxy(Object bean) {
        this.bean = bean;
    }

    public Object getProxy() throws IllegalAccessException, InstantiationException {
        ProxyFactory f = new ProxyFactory();
        f.setSuperclass(bean.getClass());
        f.setFilter(m -> ListUtil.toList("doSomething").contains(m.getName()));

        Class c = f.createClass();
        MethodHandler mi = (self, method, proceed, args) -> {
            String methodName = method.getName();
            if (methodName.equals("doSomething")){
                System.out.println("before doSomething");
            }
            return method.invoke(bean, args);
        };
        Object proxy = c.newInstance();
        ((Proxy)proxy).setHandler(mi);
        return proxy;
    }
}
public static void main(String[] args) {
    JavassitProxy proxy = new JavassitProxy(new DelegationImpl());
    Delegation delegation = (Delegation) proxy.getProxy();
    delegation.doSomething();

同样是基于子类的方式代理委托类,不过使用时可以用 filter 过滤需要代理的方法。

ByteBuddy

ByteBuddy也是一个大名鼎鼎的开源库,和Cglib一样,也是基于ASM实现。还有一个名气更大的库叫Mockito,相信不少人用过这玩意写过测试用例,其核心就是基于ByteBuddy来实现的,可以动态生成mock类,非常方便。另外ByteBuddy另外一个大的应用就是java agent,其主要作用就是在class被加载之前对其拦截,插入自己的代码。

ByteBuddy非常强大,是一个神器。可以应用在很多场景。但是这里,只介绍用 ByteBuddy 来做动态代理,关于其他使用方式,鉴于目前还没有学习,暂不深入。

public class ByteBuddyProxy {

    private Object bean;

    public ByteBuddyProxy(Object bean) {
        this.bean = bean;
    }

    public Object getProxy() throws Exception{
        Object object = new ByteBuddy().subclass(bean.getClass())
                .method(ElementMatchers.namedOneOf("doSomething"))
                .intercept(InvocationHandlerAdapter.of(new AopInvocationHandler(bean)))
                .make()
                .load(ByteBuddyProxy.class.getClassLoader())
                .getLoaded()
                .newInstance();
        return object;
    }

    public class AopInvocationHandler implements InvocationHandler {

        private Object bean;

        public AopInvocationHandler(Object bean) {
            this.bean = bean;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();
            if (methodName.equals("doSomething")){
                System.out.println("before doSomething");
            }
            return method.invoke(bean, args);
        }
    }

        // 执行
        ByteBuddyProxy proxy = new ByteBuddyProxy (new DelegationImpl());
        Delegation delegation = (Delegation) proxy.getProxy();
    delegation.doSomething();
}

同样是基于生成子类实现。

总结

动态代理技术对于一个经常写开源或是中间件的人来说,是一个神器。这种特性提供了一种新的解决方式。从而使得代码更加优雅而简单。作为Java开发者都应该有所了解。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

Scroll to Top