jdk的动态代理及为什么需要接口

参考文章:https://blog.csdn.net/zxysshgood/article/details/78684229

为什么jdk动态代理必须基于接口 
原因如下: 
1、生成的代理类继承了Proxy,由于java是单继承,所以只能实现接口,通过接口实现 

2、从代理模式的设计来说,充分利用了java的多态特性,也符合基于接口编码的规范 

CGLIB代理

CGLIB底层:使用字节码处理框架ASM,来转换字节码并生成新的类。 

CGLIB(CODE GENERLIZE LIBRARY)代理是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的所有方法,所以该类或方法不能声明称final的。

如果目标对象实现了接口,可以强制使用CGLIB实现代理。

如果目标对象没有实现接口,则默认会采用CGLIB代理; 


动态代理有关,无非是使用JDK动态代理,和cglib动态代理。一直不待明白的是为什么,jdk的动态代理需要接口才能实现,这也是其短板和令人诟病的地方。很多的博文说的很复杂,代码一大堆,没有太明白。手打了一下,参考了一些优秀的博文,在这里给自己做个总结。

首先,动态代理是个挺有用的东西,常见的就是javaAOP的有关,主要的作用是是在一个方法使用前后,能进行别的处理。比如吧,aop所说的,面向切面编程,日志有关,检测有关。都可以通过AOP或者说动态代理来实现。

先来看下最简单的代码下实现动态代理的情况,然后看下源码和我们的主题,为什么需要接口

为什么需要接口,先上结论

1.在需要继承proxy类获得有关方法和InvocationHandler构造方法传参的同时,java不能同时继承两个类,我们需要和想要代理的类建立联系,只能实现一个接口

2.需要反射获得代理类的有关参数,必须要通过某个类,反射获取有关方法,如本次测试用的 :printSomeThing

3.成功返回的是object类型,要获取原类,只能继承/实现,或者就是那个代理类

4.对具体实现的方法内部并不关心,这个交给InvocationHandler.invoke那个方法里去处理就好了,我只想根据你给我的接口反射出对我有用的东西。

5.考虑到设计模式,以及proxy编者编写代码的逻辑使然


过程:

1.实现简单的JDK动态代理

1.1为什么需要这个接口的---这个接口==

[java]  view plain  copy
  1. package Activeproxy.jdk;  
  2.   
  3. public interface tagService {  
  4.   
  5.     public void printSomeThing();  
  6. }  

1.2编写他的简单实现类

[java]  view plain  copy
  1. package Activeproxy.jdk;  
  2.   
  3. public class tagServiceImpl implements tagService {  
  4.   
  5.     public final void printSomeThing() {  
  6.         System.err.println("this is printSomeThing Core ServiceImpl");  
  7.     }  
  8.   
  9.   
  10. }  
1.3编写调用处理程序InvocationHandler,实现该方法,其实在后面调用的时候,具体方法的调用,会进入这里面按下面invoke里面的顺序执行。 三个参数,分别代表,传入的代理实现类,此次调用的方法名称,有关参数 。这是平时使用的时候的动态代理的核心方法,看起来也很简答
[java]  view plain  copy
  1. package Activeproxy.jdk;  
  2.   
  3. import java.lang.reflect.InvocationHandler;  
  4. import java.lang.reflect.Method;  
  5.   
  6. public class jdkInvocation implements InvocationHandler {  
  7.   
  8.     private Object object;  
  9.   
  10.     public void setTagServiceObject(Object object) {  
  11.         this.object = object;  
  12.     }  
  13.   
  14.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  15.         System.out.println("TagService代理前");  
  16.         Object returnObject = method.invoke(this.object, args);  
  17.         System.out.println("TagService代理后");  
  18.         return returnObject;  
  19.     }  
  20.   
  21. }  
1.4编写调用类
[java]  view plain  copy
  1. package Activeproxy.jdk;  
  2.   
  3. import java.lang.reflect.Proxy;  
  4.   
  5. public class start {  
  6.   
  7.     public static void main(String[] args) {  
  8.         jdkInvocation invocation = new jdkInvocation();  
  9.         invocation.setTagServiceObject(new tagServiceImpl());  
  10.         tagService service = (tagService) Proxy  
  11.                 .newProxyInstance(start.class.getClassLoader(), new Class[] { tagService.class }, invocation);  
  12.         service.printSomeThing();  
  13.   
  14.     }  
  15. }  

启动后,控制台会输出


就简单的动态代理来说在这里就结束了,但是并没有解决的我的疑问,为什么jdk动态代理需要接口

主要的秘密在newProxyInstance这个方法里

2.1在jdk 1.8里这个方法的代码是这样的

[java]  view plain  copy
  1. @CallerSensitive  
  2.     public static Object newProxyInstance(ClassLoader loader,  
  3.                                           Class<?>[] interfaces,  
  4.                                           InvocationHandler h)  
  5.         throws IllegalArgumentException  
  6.     {  
  7.         Objects.requireNonNull(h);  
  8.   
  9.         final Class<?>[] intfs = interfaces.clone();  
  10.         final SecurityManager sm = System.getSecurityManager();  
  11.         if (sm != null) {  
  12.             checkProxyAccess(Reflection.getCallerClass(), loader, intfs);  
  13.         }  
  14.   
  15.         /* 
  16.          * Look up or generate the designated proxy class. 
  17.          */  
  18.         Class<?> cl = getProxyClass0(loader, intfs);  
  19.   
  20.         /* 
  21.          * Invoke its constructor with the designated invocation handler. 
  22.          */  
  23.         try {  
  24.             if (sm != null) {  
  25.                 checkNewProxyPermission(Reflection.getCallerClass(), cl);  
  26.             }  
  27.   
  28.             final Constructor<?> cons = cl.getConstructor(constructorParams);  
  29.             final InvocationHandler ih = h;  
  30.             if (!Modifier.isPublic(cl.getModifiers())) {  
  31.                 AccessController.doPrivileged(new PrivilegedAction<Void>() {  
  32.                     public Void run() {  
  33.                         cons.setAccessible(true);  
  34.                         return null;  
  35.                     }  
  36.                 });  
  37.             }  
  38.             return cons.newInstance(new Object[]{h});  
  39.         } catch (IllegalAccessException|InstantiationException e) {  
  40.             throw new InternalError(e.toString(), e);  
  41.         } catch (InvocationTargetException e) {  
  42.             Throwable t = e.getCause();  
  43.             if (t instanceof RuntimeException) {  
  44.                 throw (RuntimeException) t;  
  45.             } else {  
  46.                 throw new InternalError(t.toString(), t);  
  47.             }  
  48.         } catch (NoSuchMethodException e) {  
  49.             throw new InternalError(e.toString(), e);  
  50.         }  
  51.     }  

讲解一下:

checkProxyAccess是验证一些参数

Class<?> cl = getProxyClass0(loader, intfs);是查找或生成指定的代理类(重点)

final Constructor<?> cons = cl.getConstructor(constructorParams);这行代码是获取,生成代理类的构造函数是 InvocationHandler参数的方法

return cons.newInstance(new Object[]{h});这行代码的意思是将h,就是实现InvocationHandler的jdkInvocation注入到cons中。然后newInstance生成一个已经组装过参数的代理类。

现在这个参数名是cl的代理类,经过,获得cl,注入InvocationHandler的实现类。通过他newInstance的类,我们可以猜测一下,应该是有一个构造方法可以注入InvocationHandler的实现类。而且,还能被(tagService)这样强转,那么应该是继承了或者实现了tagService。

so,到这一步,理论上来说我们可以知道:一个代理类,继承或者实现了我们专门创的接口,且内部有构造方法接受InvocationHandler的实现类。两个类关联起来了。而且,如果调用实例化成功的话,我们已经可以通过接口访问内部的方法了。

那么重点来了,这个方法里的重点就是getProxyClass0(loader, intfs);这个方法里面是什么样的,做了哪些操作,返回的是什么代理类

3.1 getProxyClass0   通过该类源码,发现其实他是从  proxyClassCache里获取的。这个是已经获取完毕后放在所谓,有关代理的静态缓存中。怎么处理的秘密在ProxyClassFactory中

[java]  view plain  copy
  1. private static Class<?> getProxyClass0(ClassLoader loader,  
  2.                                           Class<?>... interfaces) {  
  3.        if (interfaces.length > 65535) {  
  4.            throw new IllegalArgumentException("interface limit exceeded");  
  5.        }  
  6.   
  7.        // If the proxy class defined by the given loader implementing  
  8.        // the given interfaces exists, this will simply return the cached copy;  
  9.        // otherwise, it will create the proxy class via the ProxyClassFactory  
  10.        return proxyClassCache.get(loader, interfaces);  
  11.    }  
3.2.ProxyClassFactory
[java]  view plain  copy
  1. private static final class ProxyClassFactory  
  2.     implements BiFunction<ClassLoader, Class<?>[], Class<?>>  
  3. {  
  4.     // prefix for all proxy class names  
  5.     private static final String proxyClassNamePrefix = "$Proxy";  
  6.   
  7.     // next number to use for generation of unique proxy class names  
  8.     private static final AtomicLong nextUniqueNumber = new AtomicLong();  
  9.   
  10.     @Override  
  11.     public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {  
  12.   
  13.         Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);  
  14.         for (Class<?> intf : interfaces) {  
  15.             /* 
  16.              * Verify that the class loader resolves the name of this 
  17.              * interface to the same Class object. 
  18.              */  
  19.             Class<?> interfaceClass = null;  
  20.             try {  
  21.                 interfaceClass = Class.forName(intf.getName(), false, loader);  
  22.             } catch (ClassNotFoundException e) {  
  23.             }  
  24.             if (interfaceClass != intf) {  
  25.                 throw new IllegalArgumentException(  
  26.                     intf + " is not visible from class loader");  
  27.             }  
  28.             /* 
  29.              * Verify that the Class object actually represents an 
  30.              * interface. 
  31.              */  
  32.             if (!interfaceClass.isInterface()) {  
  33.                 throw new IllegalArgumentException(  
  34.                     interfaceClass.getName() + " is not an interface");  
  35.             }  
  36.             /* 
  37.              * Verify that this interface is not a duplicate. 
  38.              */  
  39.             if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {  
  40.                 throw new IllegalArgumentException(  
  41.                     "repeated interface: " + interfaceClass.getName());  
  42.             }  
  43.         }  
  44.   
  45.         String proxyPkg = null;     // package to define proxy class in  
  46.         int accessFlags = Modifier.PUBLIC | Modifier.FINAL;  
  47.   
  48.         /* 
  49.          * Record the package of a non-public proxy interface so that the 
  50.          * proxy class will be defined in the same package.  Verify that 
  51.          * all non-public proxy interfaces are in the same package. 
  52.          */  
  53.         for (Class<?> intf : interfaces) {  
  54.             int flags = intf.getModifiers();  
  55.             if (!Modifier.isPublic(flags)) {  
  56.                 accessFlags = Modifier.FINAL;  
  57.                 String name = intf.getName();  
  58.                 int n = name.lastIndexOf('.');  
  59.                 String pkg = ((n == -1) ? "" : name.substring(0, n + 1));  
  60.                 if (proxyPkg == null) {  
  61.                     proxyPkg = pkg;  
  62.                 } else if (!pkg.equals(proxyPkg)) {  
  63.                     throw new IllegalArgumentException(  
  64.                         "non-public interfaces from different packages");  
  65.                 }  
  66.             }  
  67.         }  
  68.   
  69.         if (proxyPkg == null) {  
  70.             // if no non-public proxy interfaces, use com.sun.proxy package  
  71.             proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";  
  72.         }  
  73.   
  74.         /* 
  75.          * Choose a name for the proxy class to generate. 
  76.          */  
  77.         long num = nextUniqueNumber.getAndIncrement();  
  78.         String proxyName = proxyPkg + proxyClassNamePrefix + num;  
  79.   
  80.         /* 
  81.          * Generate the specified proxy class. 
  82.          */  
  83.         byte[] proxyClassFile = ProxyGenerator.generateProxyClass(  
  84.             proxyName, interfaces, accessFlags);  
  85.         try {  
  86.             return defineClass0(loader, proxyName,  
  87.                                 proxyClassFile, 0, proxyClassFile.length);  
  88.         } catch (ClassFormatError e) {  
  89.             /* 
  90.              * A ClassFormatError here means that (barring bugs in the 
  91.              * proxy class generation code) there was some other 
  92.              * invalid aspect of the arguments supplied to the proxy 
  93.              * class creation (such as virtual machine limitations 
  94.              * exceeded). 
  95.              */  
  96.             throw new IllegalArgumentException(e.toString());  
  97.         }  
  98.     }  
  99. }  
这么长的一大串代码其实也没干太多事,无非也就是验证参数,验证接口,反射获得接口。重点是

 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags);

这个方法就是要找到的原因。他隐藏起来了最关键的,怎么组装了一个byte类型代理类。在后面传了回去

通过反编译,我们得知,返回的结果是,

[java]  view plain  copy
  1. package com.sun.proxy;    
  2.     
  3. import com.Activeproxy.jdk.tagService;    
  4. import java.lang.reflect.InvocationHandler;    
  5. import java.lang.reflect.Method;    
  6. import java.lang.reflect.Proxy;    
  7. import java.lang.reflect.UndeclaredThrowableException;    
  8.     
  9. public final class $Proxy0 extends Proxy implements tagService{    
  10.   private static Method m1;    
  11.   private static Method m3;    
  12.   private static Method m0;    
  13.   private static Method m2;    
  14.     
  15.   public $Proxy0(InvocationHandler paramInvocationHandler) {    
  16.     super(paramInvocationHandler);    
  17.   }<strong>  </strong>  
  18.     
  19.   public final boolean equals(Object paramObject) {    
  20.     try {    
  21.       return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();    
  22.     }    
  23.     catch (Error|RuntimeException localError) {    
  24.       throw localError;    
  25.     }    
  26.     catch (Throwable localThrowable) {    
  27.       throw new UndeclaredThrowableException(localThrowable);    
  28.     }    
  29.   }    
  30.     
  31.   public final void printSomeThing(String paramString) {    
  32.     try {    
  33.       this.h.invoke(this, m3, new Object[] { paramString });    
  34.       return;    
  35.     }    
  36.     catch (Error|RuntimeException localError) {    
  37.       throw localError;    
  38.     }    
  39.     catch (Throwable localThrowable) {    
  40.       throw new UndeclaredThrowableException(localThrowable);    
  41.     }    
  42.   }    
  43.     
  44.   public final int hashCode() {    
  45.     try {    
  46.       return ((Integer)this.h.invoke(this, m0, null)).intValue();    
  47.     }    
  48.     catch (Error|RuntimeException localError) {    
  49.       throw localError;    
  50.     }    
  51.     catch (Throwable localThrowable) {    
  52.       throw new UndeclaredThrowableException(localThrowable);    
  53.     }    
  54.   }    
  55.     
  56.   public final String toString() {    
  57.     try {    
  58.       return (String)this.h.invoke(this, m2, null);    
  59.     }    
  60.     catch (Error|RuntimeException localError) {    
  61.       throw localError;    
  62.     }    
  63.     catch (Throwable localThrowable) {    
  64.       throw new UndeclaredThrowableException(localThrowable);    
  65.     }    
  66.   }    
  67.     
  68.   static {    
  69.     try {    
  70.       m1 = Class.forName("java.lang.Object").getMethod("equals"new Class[] { Class.forName("java.lang.Object") });    
  71.       m3 = Class.forName("com.Activeproxy.jdk.tagService").getMethod("printSomeThing"new Class[0]);    
  72.       m0 = Class.forName("java.lang.Object").getMethod("hashCode"new Class[0]);    
  73.       m2 = Class.forName("java.lang.Object").getMethod("toString"new Class[0]);    
  74.       return;    
  75.     }    
  76.     catch (NoSuchMethodException localNoSuchMethodException) {    
  77.       throw new NoSuchMethodError(localNoSuchMethodException.getMessage());    
  78.     }    
  79.     catch (ClassNotFoundException localClassNotFoundException) {    
  80.       throw new NoClassDefFoundError(localClassNotFoundException.getMessage());    
  81.     }    
  82.   }    
  83. }    
看到这个方法,就算破案了。这个就是jdk动态代理和为什么需要接口。因为

1.在需要继承proxy类获得有关方法和InvocationHandler构造方法传参的同时,java不能同时继承两个类,我们需要和想要代理的类建立联系,只能实现一个接口

2.需要反射获得代理类的有关参数,必须要通过某个类,反射获取有关方法,如本次测试用的 :printSomeThing

3.成功返回的是object类型,要获取原类,只能继承/实现,或者就是那个代理类

4.对具体实现的方法内部并不关心,这个交给InvocationHandler.invoke那个方法里去处理就好了,我只想根据你给我的接口反射出对我有用的东西。

5.考虑到设计模式,以及proxy编者编写代码的逻辑使然


猜你喜欢

转载自blog.csdn.net/linglingma9087/article/details/80311518