Java 动态代理

    xiaoxiao2021-03-25  296

    本文主要介绍Java SDK提供的动态代理和CGLib动态代理。

    1、Java 动态代理的简单应用

    //接口类 public interface Subject { public void doSomething(); } //被代理类 public class RealSubject implements Subject { public void doSomething() { System.out.println( "call doSomething()" ); } } //定义代理hankler public class ProxyHandler implements InvocationHandler { private Object proxied; public ProxyHandler( Object proxied ) { this.proxied = proxied; } public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { //在转调具体目标对象之前,可以执行一些功能处理 //转调具体目标对象的方法 return method.invoke( proxied, args); //在转调具体目标对象之后,可以执行一些功能处理 } } //调用过程 public static void main( String args[] )      {        RealSubject real = new RealSubject();        Subject proxySubject = (Subject)Proxy.newProxyInstance( Subject.class.getClassLoader(),      new Class[]{Subject.class}, //或real.getClass().getInterfaces()      new ProxyHandler(real));              proxySubject.doSomething(); }   newProxyInstance (ClassLoader loader, Class<?>[]interfaces,InvocationHandler h)三个参数含义: loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载。               //或Thread.currentThread().getContextClassLoader()               //或real.getClass().getClassLoader() interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了。(该参数决定了newProxyInstance()返回类型) h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。

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

    2、Java 动态代理内部实现

    类Proxy中的主要静态变量:

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

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

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

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

    类Proxy中的主要静态方法:

    // 方法 1: 该方法用于获取指定代理对象所关联的调用处理器 static InvocationHandler getInvocationHandler(Object proxy) ;

    // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象 static Class getProxyClass(ClassLoader loader, Class[] interfaces) ;

    // 方法 3:该方法用于判断指定类对象是否是一个动态代理类 static boolean isProxyClass(Class cl) ;

    // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h);

    newProxyInstance()方法主要操作:

    public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h) throws IllegalArgumentException { // 检查 h 不为空,否则抛异常 if (h == null) { throw new NullPointerException(); } // 获得与指定类装载器和一组接口相关的代理类类型对象 Class cl = getProxyClass(loader, interfaces); // 通过反射获取构造函数对象并生成代理类实例 // 调用代理对象的构造方法(也就是$Proxy0(InvocationHandler h))  Constructor cons = cl.getConstructor(constructorParams); return (Object) cons.newInstance(new Object[] { h }); }  

    类Proxy的getProxyClass方法调用ProxyGenerator类的generateProxyClass方法产生ProxySubject.class的二进制数据。 动态代理生成的类,反编译得到:

    public final class $Proxy11 extends Proxy        implements Subject    {        private static Method m1;        private static Method m0;        private static Method m3;        private static Method m2;        public ProxySubject(InvocationHandler invocationhandler)        {            super(invocationhandler);        }        public final boolean equals(Object obj)        {            try           {                return ((Boolean)super.h.invoke(this, m1, new Object[] {                    obj                })).booleanValue();            }            catch(Error _ex) { }            catch(Throwable throwable)            {                throw new UndeclaredThrowableException(throwable);            }        }        public final int hashCode()        {            try           {                return ((Integer)super.h.invoke(this, m0, null)).intValue();            }            catch(Error _ex) { }            catch(Throwable throwable)            {                throw new UndeclaredThrowableException(throwable);            }        }        public final void doSomething()        {            try           {                super.h.invoke(this, m3, null);                return;            }            catch(Error _ex) { }            catch(Throwable throwable)            {                throw new UndeclaredThrowableException(throwable);            }        }        public final String toString()        {            try           {                return (String)super.h.invoke(this, m2, null);            }            catch(Error _ex) { }            catch(Throwable throwable)            {                throw new UndeclaredThrowableException(throwable);            }        }        static         {            try           {                m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {                    Class.forName("java.lang.Object")                });                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);                m3 = Class.forName("Subject").getMethod("doSomething", new Class[0]);                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);            }            catch(NoSuchMethodException nosuchmethodexception)            {                throw new NoSuchMethodError(nosuchmethodexception.getMessage());            }            catch(ClassNotFoundException classnotfoundexception)            {                throw new NoClassDefFoundError(classnotfoundexception.getMessage());            }        }    }  

    生成动态代理类的特点:

    1、继承自 java.lang.reflect.Proxy,实现了相应的接口。 2、类中的所有方法都是final 的。 3、所有的方法功能的实现都统一调用了InvocationHandler的invoke()方法。 4、代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是hashCode()、equals()、toString(),可能的原因有:一是因为这些方法为 public 且非 final 类型,能够被代理类覆盖;二是因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被分派到委托类执行。 getProxyClass()方法具体实现过程: public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces)                                                     throws IllegalArgumentException           {           // 如果目标类实现的接口数大于65535个则抛出异常         if (interfaces.length > 65535) {               throw new IllegalArgumentException("interface limit exceeded");           }                  // 声明代理对象所代表的Class对象         Class proxyClass = null;                  String[] interfaceNames = new String[interfaces.length];                  Set interfaceSet = new HashSet();   // for detecting duplicates                  // 遍历目标类所实现的接口           for (int i = 0; i < interfaces.length; i++) {                              // 拿到目标类实现的接口的名称               String interfaceName = interfaces[i].getName();               Class interfaceClass = null;               try {               // 加载目标类实现的接口到内存中               interfaceClass = Class.forName(interfaceName, false, loader);               } catch (ClassNotFoundException e) {               }               if (interfaceClass != interfaces[i]) {               throw new IllegalArgumentException(                   interfaces[i] + " is not visible from class loader");               }                      // 中间省略了一些无关紧要的代码 .......                              // 把目标类实现的接口代表的Class对象放到Set中               interfaceSet.add(interfaceClass);                      interfaceNames[i] = interfaceName;           }                  // 把目标类实现的接口名称作为缓存(Map)中的key           Object key = Arrays.asList(interfaceNames);                  Map cache;                      synchronized (loaderToCache) {               // 从缓存中获取cache               cache = (Map) loaderToCache.get(loader);               if (cache == null) {               // 如果获取不到,则新建地个HashMap实例               cache = new HashMap();               // 把HashMap实例和当前加载器放到缓存中               loaderToCache.put(loader, cache);               }                  }                  synchronized (cache) {                      do {               // 根据接口的名称从缓存中获取对象               Object value = cache.get(key);               if (value instanceof Reference) {                   proxyClass = (Class) ((Reference) value).get();               }               if (proxyClass != null) {                   // 如果代理对象的Class实例已经存在,则直接返回                   return proxyClass;               } else if (value == pendingGenerationMarker) {                   try {                   cache.wait();                   } catch (InterruptedException e) {                   }                   continue;               } else {                   cache.put(key, pendingGenerationMarker);                   break;               }               } while (true);           }                  try {               // 中间省略了一些代码 .......                              // 这里就是动态生成代理对象的最关键的地方               byte[] proxyClassFile = ProxyGenerator.generateProxyClass(                   proxyName, interfaces);               try {                   // 根据代理类的字节码生成代理类的实例                   proxyClass = defineClass0(loader, proxyName,                   proxyClassFile, 0, proxyClassFile.length);  //defineClass0             } catch (ClassFormatError e) {                   throw new IllegalArgumentException(e.toString());               }               }               // add to set of all generated proxy classes, for isProxyClass               proxyClasses.put(proxyClass, null);                  }            // 中间省略了一些代码 .......                      return proxyClass;           }  ProxyGenerator类的静态方法generateProxyClass()具体实现 public static byte[] generateProxyClass(final String name, Class[] interfaces) { ProxyGenerator gen = new ProxyGenerator(name, interfaces); // 这里动态生成代理类的字节码,由于比较复杂就不进去看了 final byte[] classFile = gen.generateClassFile(); // 如果saveGeneratedFiles的值为true,则会把所生成的代理类的字节码保存到硬盘上 if (saveGeneratedFiles) { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Void>() { public Void run() { try { FileOutputStream file = new FileOutputStream(dotToSlash(name) + ".class"); file.write(classFile); file.close(); return null; } catch (IOException e) { throw new InternalError( "I/O exception saving generated file: " + e); } } }); } // 返回代理类的字节码 return classFile; }

    3、Java 动态生成代理类的注意事项

    1)包:如果所代理的接口都是public的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非public的接口(因为接口不能被定义为protect或private,所以除public之外就是默认的package访问级别),那么它将被定义在该接口所在包(假设代理了com.ibm.dev包中的某非public接口A,那么新生成的代理类所在的包就是com.ibm.dev),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问。注意不能有重复的接口,以避免动态代理类代码生成时的编译错误。其次,这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败。再次,需被代理的所有非public的接口必须在同一个包中,否则代理类生成也会失败。最后,接口的数目不能超过65535,这是 JVM 设定的限制。

    2)类修饰符:代理类具有final和public修饰符,意味着它可以被所有的类访问,但不能被再度继承

    3)类名:格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。

    4)当代理的一组接口有重复声明的方法且该方法被调用时,代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器,而无论代理类实例是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用,因为在代理类内部无法区分其当前的被引用类型

    5)继承原则:即子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。但实际上往往受限制,除非父接口中的方法支持抛 Throwable 异常。那么如果在 invoke 方法中的确产生了接口方法声明中不支持的异常,那将如何呢?放心,Java 动态代理类已经为我们设计好了解决方法:它将会抛出UndeclaredThrowableException 异常。这个异常是一个 RuntimeException 类型,所以不会引起编译错误。通过该异常的 getCause 方法,还可以获得原来那个不受支持的异常对象,以便于错误诊断。

    4、Java 动态代理的缺陷

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

    5、CGLib 动态代理

    Spring主要有两大思想,一个是IoC,另一个就是AOP。对于IoC,是依赖注入;Spring的核心AOP(Aspect Oriented Programming),其原理就是动态代理机制。AOP的拦截功能是由java中的动态代理来实现的。本质就是在目标类的基础上增加切面逻辑,生成增强的目标类(该切面逻辑或者在目标类函数执行之前,或者目标类函数执行之后,或者在目标类函数抛出异常时候执行。不同的切入时机对应不同的Interceptor的种类,如BeforeAdviseInterceptor,AfterAdviseInterceptor以及ThrowsAdviseInterceptor等)。

    AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理cglib动态代理。两种方法同时存在,各有优劣。jdk动态代理是由Java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势

    AOP作用:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码,实现解耦。

    CGLib(Code Generation Library)采用了非常底层的字节码技术,底层采用asm字节码生成框架生成代理类的字节码,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。(动态生成字节码文件)

    CGLib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理

    CGLib的核心类和方法:

    Enhancer:主要的增强类。 MethodInterceptor:主要的方法拦截类,它是Callback接口的子接口,需要用户实现。 MethodProxy:JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用。 methodProxy.invokeSuper(proxy, args):虽然第一个参数是被代理对象,也不会出现死循环的问题。 MethodInterceptor接口是最通用的回调(callback)类型,它经常被基于代理的AOP用来实现拦截(intercept)方法的调用。这个接口只定义了一个方法:intercept(Object object, java.lang.reflect.Method method,Object[] args, MethodProxy proxy)。

    CGLib 创建某个类A的动态代理类的模式是:

    1、找A上的所有非final 的public类型的方法定义。 2、将这些方法的定义转换成字节码。 3、将组成的字节码转换成相应的代理的class对象。 4、实现MethodInterceptor接口,用来处理对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的)。

    CGLib简单例子:

    需要被代理的类: public class SayHello { public void say(){ System.out.println("hello everyone"); } } 该类实现了创建子类的方法与代理的方法。getProxy(SuperClass.class)方法通过入参即父类的字节码,通过扩展父类的class来创建代理对象。 intercept()方法拦截所有目标类方法的调用,obj表示目标类的实例,method为目标类方法的反射对象,args为方法的动态入参,proxy为代理类实例。 proxy.invokeSuper(obj, args)通过代理类调用父类中的方法。 public class CglibProxy implements MethodInterceptor{ private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz){ //设置需要创建子类的类 enhancer.setSuperclass(clazz); enhancer.setCallback(this); //通过字节码技术动态创建子类实例 return enhancer.create(); } //实现MethodInterceptor接口方法 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("前置代理"); //通过代理类调用父类中的方法 Object result = proxy.invokeSuper(obj, args); System.out.println("后置代理"); return result; } } 具体实现类: public class DoCGLib { public static void main(String[] args) { CglibProxy proxy = new CglibProxy(); //通过生成子类的方式创建代理类 SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.class); proxyImp.say(); } } jdk和cglib动态代理实现的区别:

    1、jdk动态代理生成的代理类和委托类实现了相同的接口 。 2、cglib动态代理中生成的字节码更加复杂,生成的代理类是委托类的子类,且不能处理被final关键字修饰的方法。 3、jdk采用反射机制调用委托类的方法,cglib采用类似索引的方式直接调用委托类方法。

    Java动态代理参考: http://rejoy.iteye.com/blog/1627405 http://www.cnblogs.com/flyoung2008/archive/2013/08/11/3251148.html https://www.ibm.com/developerworks/cn/java/j-lo-proxy1/index.html Cglib动态代理参考: http://www.cnblogs.com/chinajava/p/5880887.html
    转载请注明原文地址: https://ju.6miu.com/read-140.html

    最新回复(0)