java的动态代理机制详解

        在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的我们的功能,我们更需要学习的是其底层是怎么样的一个原理,而AOP的原理就是java的动态代理机制,所以本篇随笔就是对java的动态机制进行一个回顾。


一、JDK的动态代理


        在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。首先我们先来看看java的API帮助文档是怎么样对这两个类进行描述的:

InvocationHandler:

InvocationHandler is the interface implemented by the invocation handler of a proxy instance. 

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is 
encoded and dispatched to the invoke method of its invocation handler.

        每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

        我们看到这个方法一共接受三个参数,那么这三个参数分别代表什么呢?

proxy:  指代我们所代理的那个真实对象

method:  指代的是我们所要调用真实对象的某个方法的Method对象

args:  指代的是调用真实对象某个方法时接受的参数

        如果不是很明白,等下通过一个实例会对这几个参数进行更深的讲解。


        接下来我们来看看Proxy这个类:

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic 
proxy classes created by those methods. 

        Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h) 
 throws IllegalArgumentException
Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified 
invocation handler.

        这个方法的作用就是得到一个动态的代理对象,其接收三个参数,我们来看看这三个参数所代表的含义:

loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载

interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了

h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

        好了,在介绍完这两个接口(类)以后,我们来通过一个实例来看看我们的动态代理模式是什么样的:

        首先我们定义了一个Subject类型的接口,为其声明了两个方法:


public interface Subject {

    public void rent();

    public void hello(String str);
}

        接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,RealSubject类:

public class RealSubject implements Subject {

    @Override
    public void rent() {
        System.out.println("I want to rent my house");

    }

    @Override
    public void hello(String str) {
        System.out.println("hello: " + str);

    }

}

        下一步,我们就要定义一个动态代理类了,前面说个,每一个动态代理类都必须要实现 InvocationHandler 这个接口,因此我们这个动态代理类也不例外:

public class DynamicProxy implements InvocationHandler {
    // 这个就是我们要代理的真实对象
    private Object subject;

    // 构造方法,给我们要代理的真实对象赋初值
    public DynamicProxy(Object subject) {
        this.subject = subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在代理真实对象前我们可以添加一些自己的操作
        System.out.println("before");

        System.out.println("Method:" + method);

        // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        Object result = method.invoke(subject, args);

        // 在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("after");

        return result;
    }

}


        最后,来看看我们的Client类:

public class Client {

    public static void main(String[] args) {
        // 我们要代理的真实对象
        Subject realSubject = new RealSubject();

        // 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
        InvocationHandler handler = new DynamicProxy(realSubject);

        /*
         * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数 第一个参数
         * handler.getClass().getClassLoader()
         * ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
         * 第二个参数realSubject.getClass().
         * getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,
         * 表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了 第三个参数handler, 我们这里将这个代理对象关联到了上方的
         * InvocationHandler 这个对象上
         */
        Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass()
                .getClassLoader(), realSubject.getClass().getInterfaces(),
                handler);

        System.out.println(subject.getClass().getName());
        subject.rent();
        subject.hello("world");
    }

}

        我们先来看看控制台的输出:

com.sun.proxy.$Proxy0
before
Method:public abstract void test.proxy.Subject.rent()
I want to rent my house
after
before
Method:public abstract void test.proxy.Subject.hello(java.lang.String)
hello: world
after

        我们首先来看看 com.sun.proxy.$Proxy0 这东西,我们看到,这个东西是由 System.out.println(subject.getClass().getName()); 这条语句打印出来的,那么为什么我们返回的这个代理对象的类名是这样的呢?

        Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(), handler);

        可能我以为返回的这个代理对象会是Subject类型的对象或者是InvocationHandler的对象,结果却不是,首先我们解释一下为什么我们这里可以将其转化为Subject类型的对象?

        原因就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了。

        同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。

        接着我们来看看这两句:

subject.rent();
subject.hello("world");

        这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的 handler 中的invoke方法去执行,而我们的这个 handler 对象又接受了一个 RealSubject类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的invoke方法去执行

        我们看到,在真正通过代理对象来调用真实对象的方法的时候,我们可以在该方法前后添加自己的一些操作,同时我们看到我们的这个 method 对象是这样的:

Method:public abstract void test.proxy.Subject.rent()
Method:public abstract void test.proxy.Subject.hello(java.lang.String)

        正好就是我们的Subject接口中的两个方法,这也就证明了当我通过代理对象来调用方法的时候,起实际就是委托由其关联到的 handler 对象的invoke方法中来调用,并不是自己来真实调用,而是通过代理的方式来调用的。

        这就是我们的java动态代理机制


动态代理内部实现


        首先来看看类Proxy的代码实现

// 映射表:用于维护类装载器对象到其对应的代理类缓存
private static Map loaderToCache = new WeakHashMap(); 

// 标记:用于标记一个动态代理类正在被创建中
private static Object pendingGenerationMarker = new Object(); 

// 同步表:记录已经被创建的动态代理类类型,主要被方法 isProxyClass 进行相关的判断
private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap()); 

// 关联的调用处理器引用
protected InvocationHandler h;

// 由于 Proxy 内部从不直接调用构造函数,所以 private 类型意味着禁止任何调用
private Proxy() {} 

// 由于 Proxy 内部从不直接调用构造函数,所以 protected 意味着只有子类可以调用
protected Proxy(InvocationHandler h) {this.h = h;} 

public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h) throws 
IllegalArgumentException { 
    // 检查 h 不为空,否则抛异常
    if (h == null) { 
        throw new NullPointerException(); 
    } 

    // 获得与指定类装载器和一组接口相关的代理类类型对象
    Class cl = getProxyClass(loader, interfaces); 

    // 通过反射获取构造函数对象并生成代理类实例
    try { 
        Constructor cons = cl.getConstructor(constructorParams); 
        return (Object) cons.newInstance(new Object[] { h }); 
    } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); 
    } catch (IllegalAccessException e) { throw new InternalError(e.toString()); 
    } catch (InstantiationException e) { throw new InternalError(e.toString()); 
    } catch (InvocationTargetException e) { throw new InternalError(e.toString()); 
    } 
}

        类Proxy的getProxyClass方法调用ProxyGenerator的 generateProxyClass方法产生RealSubjec.class的二进制数据:

public static byte[] generateProxyClass(final String name, Class[] interfaces)

        ProxyGenerator内部是如何生成class二进制数据,可以参考源代码:

private byte[] generateClassFile() {   
  /*  
   * Record that proxy methods are needed for the hashCode, equals,  
   * and toString methods of java.lang.Object.  This is done before  
   * the methods from the proxy interfaces so that the methods from  
   * java.lang.Object take precedence over duplicate methods in the  
   * proxy interfaces.  
   */  
  addProxyMethod(hashCodeMethod, Object.class);   
  addProxyMethod(equalsMethod, Object.class);   
  addProxyMethod(toStringMethod, Object.class);   
  /*  
   * Now record all of the methods from the proxy interfaces, giving  
   * earlier interfaces precedence over later ones with duplicate  
   * methods.  
   */  
  for (int i = 0; i < interfaces.length; i++) {   
      Method[] methods = interfaces[i].getMethods();   
      for (int j = 0; j < methods.length; j++) {   
    addProxyMethod(methods[j], interfaces[i]);   
      }   
  }   
  /*  
   * For each set of proxy methods with the same signature,  
   * verify that the methods' return types are compatible.  
   */  
  for (List<ProxyMethod> sigmethods : proxyMethods.values()) {   
      checkReturnTypes(sigmethods);   
  }   
  /* ============================================================  
   * Step 2: Assemble FieldInfo and MethodInfo structs for all of  
   * fields and methods in the class we are generating.  
   */  
  try {   
      methods.add(generateConstructor());   
      for (List<ProxyMethod> sigmethods : proxyMethods.values()) {   
    for (ProxyMethod pm : sigmethods) {   
        // add static field for method's Method object   
        fields.add(new FieldInfo(pm.methodFieldName,   
      "Ljava/lang/reflect/Method;",   
       ACC_PRIVATE | ACC_STATIC));   
        // generate code for proxy method and add it   
        methods.add(pm.generateMethod());   
    }   
      }   
      methods.add(generateStaticInitializer());   
  } catch (IOException e) {   
      throw new InternalError("unexpected I/O Exception");   
  }   
  /* ============================================================  
   * Step 3: Write the final class file.  
   */  
  /*  
   * Make sure that constant pool indexes are reserved for the  
   * following items before starting to write the final class file.  
   */  
  cp.getClass(dotToSlash(className));   
  cp.getClass(superclassName);   
  for (int i = 0; i < interfaces.length; i++) {   
      cp.getClass(dotToSlash(interfaces[i].getName()));   
  }   
  /*  
   * Disallow new constant pool additions beyond this point, since  
   * we are about to write the final constant pool table.  
   */  
  cp.setReadOnly();   
  ByteArrayOutputStream bout = new ByteArrayOutputStream();   
  DataOutputStream dout = new DataOutputStream(bout);   
  try {   
      /*  
       * Write all the items of the "ClassFile" structure.  
       * See JVMS section 4.1.  
       */  
          // u4 magic;   
      dout.writeInt(0xCAFEBABE);   
          // u2 minor_version;   
      dout.writeShort(CLASSFILE_MINOR_VERSION);   
          // u2 major_version;   
      dout.writeShort(CLASSFILE_MAJOR_VERSION);   
      cp.write(dout);   // (write constant pool)   
          // u2 access_flags;   
      dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);   
          // u2 this_class;   
      dout.writeShort(cp.getClass(dotToSlash(className)));   
          // u2 super_class;   
      dout.writeShort(cp.getClass(superclassName));   
          // u2 interfaces_count;   
      dout.writeShort(interfaces.length);   
          // u2 interfaces[interfaces_count];   
      for (int i = 0; i < interfaces.length; i++) {   
    dout.writeShort(cp.getClass(   
        dotToSlash(interfaces[i].getName())));   
      }   
          // u2 fields_count;   
      dout.writeShort(fields.size());   
          // field_info fields[fields_count];   
      for (FieldInfo f : fields) {   
    f.write(dout);   
      }   
          // u2 methods_count;   
      dout.writeShort(methods.size());   
          // method_info methods[methods_count];   
      for (MethodInfo m : methods) {   
    m.write(dout);   
      }   
             // u2 attributes_count;   
      dout.writeShort(0); // (no ClassFile attributes for proxy classes)   
  } catch (IOException e) {   
      throw new InternalError("unexpected I/O Exception");   
  }   
  return bout.toByteArray();


美中不足


        诚然,Proxy已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。有很多条理由,人们可以否定对 class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。接口和类的划分,本就不是很明显,只是到了Java中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。但是,不完美并不等于不伟大,伟大是一种本质,Java动态代理就是佐例。


二、CGLIB动态代理


        JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

        简单的实现举例:

        这是一个需要被代理的类,也就是父类,通过字节码技术创建这个类的子类,实现动态代理。

public class SayHello {
    public void say() {
        System.out.println("hello everyone");
    }
}

        该类实现了创建子类的方法与代理的方法。getProxy(SuperClass.class)方法通过入参即父类的字节码,通过扩展父类的class来创建代理对象。intercept()方法拦截所有目标类方法的调用。

public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable


obj: 表示目标类的实例

method: 为目标类方法的反射对象

args: 为方法的动态入参

proxy: 为代理类实例

proxy.invokeSuper(obj, args)通过代理类调用父类中的方法。

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();

    public Object getProxy(@SuppressWarnings("rawtypes") Class clazz) {
        // 设置需要创建子类的类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        // 通过字节码技术动态创建子类实例
        return enhancer.create();
    }

    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("before");
        // 通过代理类调用父类中的方法
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("after");

        return result;
    }

}


        测试类:


public class DoCGLib {
    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        
        // 通过生成子类的方式创建代理类
        SayHello proxyImp = (SayHello) proxy.getProxy(SayHello.class);
        proxyImp.say();
    }
}

        结果:

before
hello everyone
after

        CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。  




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值