本篇,我们来探讨Java中另外一个动态特性:动态代理。动态代理是一种强大的功能,它可以在运行时动态创建一个类,实现一个或多个接口,可以在不修改原有类的基础上动态为通过该类获取的对象添加方法、修改行为,接下来详细介绍。
 
简介 动态代理是实现面向切面的编程 AOP (Aspect Oriented Programming)的基础。切面的例子有很多,比如:日志监控、性能监控、权限检查、数据库事务等等。它们在程序的很多地方都会用到,代码编写也差不多。如果在每个用到的地方都写,代码会很冗余,也难以维护,AOP讲这些切面与主体逻辑相分离,代码简单优雅很多。
要理解动态代理,首先要理解静态代理,了解静态代理之后,就可以很好理解动态代理。动态代理有两种实现方式:一种是Java SDK提供的;另一种是第三方库(如cglib)提供的。
静态代理 代理模式 作为软件设计模式,其基本概念和日常生活中的概念是类似的。代理背后一般至少有一个concrete对象,代理的外部功能和实际对象一般都是一样的,用户与代理打交道,不直接接触实际对象。 虽然外部功能和实际对象一直,但代理有其存在的价值:
静态代理代码示例  
public  class  SampleStaticProxy   {    interface  IService   {         void  sayHello ()  ;     }     static  class  ConcreteService  implements  IService   {         @Override          public  void  sayHello ()   {             System.out.println("hello" );         }     }     static  class  ProxyService  implements  IService   {         private  IService mService;         public  ProxyService (IService service)   {             mService = service;         }         @Override          public  void  sayHello ()   {             System.out.println("entering method" );             mService.sayHello();             System.out.println("leaving method" );         }     }     public  static  void  main (String[] args)   {         IService concreteService = new  ConcreteService();         ProxyService proxyService = new  ProxyService(concreteService);         proxyService.sayHello();     } } 
 
分析一下,代理和实际对象一般有相同的接口,在这个demo中,共同的接口是IService,实际对象是 ConcreteService,代理是 ProxyService。ProxyService 内部有一个 IService 的成员变量,指向实际对象,在构造方法中被初始化,对于方法 sayHello 的调用,它转发给了实际对象,在调用前后输出了一些跟踪调试信息,应该很好理解。上述例子是固定的所以称为静态代理。
输出跟踪调试信息可能是一个通用的需求,可以想象,如果每个类都需要,而又不希望修改类定义,我们需要为每个类创建代理,实现所有接口,那这个工作就太繁琐了,如果再有其他的切面需求,整个工作可能又要重新再来。这时,就需要动态代理了,接下来介绍两种实现方式:Java SDK 和 cglib。
动态代理 Java SDK动态代理 用法 在静态代理中,代理类是直接定义在代码中的,在动态代理中,代理类是动态生成的,那么怎么动态生成呢?先看示例代码:
public  class  SampleDynamicProxy   {    interface  IService   {         void  sayHello ()  ;     }     static  class  ConcreteService  implements  IService   {         @Override          public  void  sayHello ()   {             System.out.println("hello" );         }     }     static  class  SampleInvocationHandler  implements  InvocationHandler   {         private  Object mRealObj;         public  SampleInvocationHandler (Object obj)   {             mRealObj = obj;         }         @Override          public  Object invoke (Object proxy, Method method, Object[] args)  throws  Throwable  {             System.out.println("entering method: "  + method.getName());             Object result = method.invoke(mRealObj, args);             System.out.println("leaving method: "  + method.getName());             return  result;         }     }     public  static  void  main (String[] args)   {         IService concreteService = new  ConcreteService();         IService proxyService = (IService) Proxy.newProxyInstance(IService.class .getClassLoader (),                  new Class<?>[]{IService.class}, new SampleInvocationHandler(concreteService));         proxyService.sayHello();     } } 
 
分析一下,IService 和 ConcreteService 依旧没变,程序输出也没变化,但是代理对象 proxyService 的创建方式改变了。其使用 java.lang.reflect包中的Proxy类的静态方法 newProxyInstance 来创建代理对象,这个方法的声明如下:
public  static  Object newProxyInstance (ClassLoader loader,                                           Class<?>[] interfaces,                                           InvocationHandler h) 
 
有三个参数,具体如下:
newProxyInstance 的返回值类型为 Object,可以强制转换为 interfaces 数组中的某个接口类型。但是记住,它不能强制转换为某个类类型 ,比如这里的 ConcreteService,即使它实际代理的对象类型确实是 ConcreteService。
SampleInvocationHandler 实现了 InvocationHandler,它的构造方法接受一个参数 obj 表示被代理的对象,invoke方法处理所有的接口调用,它有三个参数:
在 SampleInvocationHandler 的 invoke 实现中,我们调用了 method 的invoke方法,传递了实际对象 mRealObj 作为参数,达到了调用实际对象对应方法的目的,在调用任何方法前后,输出跟踪调试语句。需要注意,不能将proxy作为参数传递给method.invoke ,比如:
Object result = method.invoke(proxy, args); 
 
上面的语句会出现死循环,因为proxy表示当前代理对象,这又会调用到 SampleInvocationHandler 的 invoke 方法。
接下来,我们看一下这里面的基本原理
基本原理 Proxy.newProxyInstance  在了解其内部源码实现之前,我们先用一段代码替代之前的创建 ProxyService 的代码。
Class<?> proxyClass = Proxy.getProxyClass(IService.class.getClassLoader(), new Class<?>[]{ISearchView.class}); try  {    Constructor<?> constructor = proxyClass.getConstructor(new  Class<?>[]{InvocationHandler.class }) ;     SampleInvocationHandler handler = new  SampleInvocationHandler(concreteService);     IService proxyService = (IService) constructor.newInstance(handler);     proxyService.sayHello();          saveProxy0(); } catch  (Exception e) {     e.printStackTrace(); } 
 
分为三步:
1)通过Proxy.getProxyClass创建代理类定义,类定义会被缓存;
 
2)获取代理类的构造方法,构造方法有一个 InvocationHandler 类型的参数;
 
3)创建 InvocationHandler 对象,创建代理对象。
 
 
Proxy.getProxyClass 需要两个参数:一个是ClassLoader;另一个是接口数组。它会动态生成一个类,类名以 $Proxy  开头,后跟一个数字。对于上述示例,动态生成的类定义如下所示,为简化起见,忽略异常处理代码:
【注】这里需要手动去获取JDK动态代理生成类$Proxy0的内容
private  static  void  saveProxy0 ()  throws  IOException  {    byte [] bytes = ProxyGenerator             .generateProxyClass("$Proxy0" , new  Class<?>[]{IService.class }) ;     String pathDir = "C:\\Users\\SZSS\\Desktop\\out" ;     String path = "\\$Proxy0.class" ;     File f = new  File(pathDir);     if  (!f.exists()) {         f.mkdir();     }     path = f.getAbsolutePath() + path;     f = new  File(path);     if  (f.exists()) {         f.delete();     }     f.createNewFile();     try  (FileOutputStream fos = new  FileOutputStream(path)) {         fos.write(bytes, 0 , bytes.length);     } catch  (Exception e) {         e.printStackTrace();     } } 
 
Java SDK动态生成的代理类示例 $Proxy0.class 
public  final  class  $Proxy0  extends  Proxy  implements  IService   {    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  sayHello ()  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("com.tufusi.lib.SampleDynamicProxy$IService" ).getMethod("sayHello" );             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());         }     } } 
 
$Proxy0 的父类是 Proxy,它有一个构造方法,接受一个 InvocationHandler 类型的参数,保存为实例变量 h,h定义在父类Proxy中,它实现了 IService 接口,对于每个方法,如 sayHello,它调用 InvocationHandler 的 invoke 方法,对于 Object 中的方法,如 equals、toString、hashCode ,$Proxy0同样转发给了 InvocationHandler。
可以看出,这个类定义本身与被代理的对象并没有关系,与 InvocationHandler 的具体实现也没有关系,而主要与接口数组有关,给定这个接口数组,它动态创建每个接口的实现代码,实现就是转发给 InvocationHandler,与被代理对象的关系以及对它的调用由 InvocationHandler 的实现管理。 
动态代理优点 相比于静态代理,动态代理看起来复杂很多,但是也有很大优点。使用动态代理,可以编写通用的代理逻辑,用于各种类型的被代理对象,而不需要为每个被代理的类型都创建一个静态代理类。
通用的动态代理类方法模板 
private  static  <T> T getProxy (Class<T> interfaces, T realObj)   {     return  (T) Proxy.newProxyInstance(interfaces.getClassLoader(),              new  Class[]{interfaces},              new  SampleInvocationHandler(realObj));  } 
 
cglib 动态代理 JDK动态代理的局限在于,只能为接口创建代理,返回的代理对象也只能转换到某个接口类型。如果一个类没有接口,或者希望代理非接口中定义的方法,那就只能另寻他法。有一个第三方类库cglib ,可以做到这一点,并且Spring、Hibernate等都使用该类库。但是但是但是 ,一个很致命的缺点是:cglib底层采用的是ASM字节码生成框架,使用字节码技术生成代理类,即生成.class文件,而我们在Android中加载的是优化后的.dex文件,也就是说我们需要可以动态生成.dex文件的代理类,因此cglib在Android中是无法使用的。
cglib 如何使用 下面先看个简单的示例,jar包下载地址cglib-3.3.0.jar ,这里还会出错,因为会用到ASM,因此还需gradle集成 implementation group: ‘org.ow2.asm’, name: ‘asm’, version: ‘9.0’ 
public  class  SampleCglib   {    static  class  ConcreteService   {         public  void  sayHello ()   {             System.out.println("hello" );         }     }     static  class  SampleInterceptor  implements  MethodInterceptor   {         @Override          public  Object intercept (Object o, Method method, Object[] objects,                                  MethodProxy methodProxy)  throws  Throwable  {            System.out.println("entering method: "  + method.getName());             Object result = methodProxy.invokeSuper(o, objects);             System.out.println("leaving method: "  + method.getName());             return  result;         }     }     private  static  <T> T getProxy (Class<T> cls)   {         Enhancer enhancer = new  Enhancer();         enhancer.setSuperclass(cls);         enhancer.setCallback(new  SampleInterceptor());         return  (T) enhancer.create();     }     public  static  void  main (String[] args)   {         ConcreteService proxyService = getProxy(ConcreteService.class ) ;         proxyService.sayHello();     } } 
 
这里,ConcreteService是被代理的类,它没有接口。getProxy()为一个雷生成代理对象,这个代理对象可以安全地转换为被代理的类型,它使用了cglib的Enhancer类。
Enhancer 类的 setSuperclass 设置被代理的类, setCallback 设置被代理类的public非final方法被调用时的处理类(即示例的SampleInterceptor)。Enhancer支持多种类型,这里使用的类实现了 MethodInterceptor接口,它与 JDK 中 InvocationHandler 类似,方法名变成了 intercept,并且多了个 MethodProxy 类型的参数。
但是与 InvocationHandler 不同的是,SampleInterceptor 中没有被代理的对象,它通过 MethodProxy 的 invokeSuper 方法调用被代理类的方法:
Object result = methodProxy.invokeSuper(o, objects); 
 
但是注意,不能这样调用被代理类的方法:
Object result = method.invoke(o, objects); 
 
因为,o 是代理对象,调用这个方法还是会调用 SampleInterceptor 的 intercept 方法,造成死循环。在main方法里,我们并没有创建被代理的对象,创建的对象直接就是代理对象。
【注】如果想保存cglib生成的class文件,可以加入如下代码:
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/SZSS/Desktop/cglib/JavaHelper" ); 
 
请记住,这段代码一定要设置在获取代理类前面,否则会失效。
cglib 实现机制 cglib的实现机制和JDK不同,它是通过继承实现的,同时也是动态创建一个类,但这个类的父类是被代理的类,代理类重写了父类的所有public 非final方法,改为调用Callback中相关方法,在上例中,调用 SampleInterceptor 的 intercept方法。
当然,可以通过jd-gui查看字节码文件,了解其实现原理,这个等后面字节码专栏写完,回来继续深挖这一块。立个flag吧!
Java SDK代理与cglib代理比较 
JDK代理面向的是一组接口,它为这些接口动态创建了一个实现类。接口的具体实现逻辑是通过自定义的InvocationHandler实现,这个实现是自定义的,也就是说,背后是不一定有真正被代理的对象,也可能是多个实际对象,根据情况动态选择 。而cglib代理面向的是一个具体的类,它动态创建了一个新类,继承了该类,通过代理方法重写了其方法。 
JDK代理的是对象 ,需要先有一个实际对象,自定义的 InvocationHandler 引用该对象,然后创建一个代理类和代理对象,客户端访问的是代理对象,代理对象最后再调用实际对象的方法; cglib代理的是类 ,创建的对象只有一个。
如果目的都是为了一个类的方法增强功能,JDK要求该类必须要有接口,且只能处理接口中的方法,但是cglib则没有这个限制。
动态代理的应用-AOP 利用cglib动态代理,我们可以实现一个极简的AOP框架,演示AOP的基本思路和技术原理。首先先简单的过一遍简单AOP框架的用法,然后分析其实现原理。熟悉AspectJ的可以通过这里知其所以然了。
简单AOP框架实现 定义一个新注解 @Aspect,该注解用于注解切面类,指定一个参数,该参数用于标记要增强的类。
@Target (ElementType.TYPE)@Retention (RetentionPolicy.RUNTIME)public  @interface  Aspect {    Class<?>[] value(); } 
 
接下来约定,切面类可以声明三个方法 before/after/exception,分别表示在主体类的方法调用前 、调用后 、出现异常时 调用这三个方法。
定义两个切面类,一个服务日志切面类,一个是异常切面类
ServiceLogAspect.java
@Aspect ({ServiceA.class , ServiceB .class }) public  class  ServiceLogAspect   {    public  static  void  before (Object object, Method method, Object[] args)   {         System.out.println("entering "  + method.getDeclaringClass().getSimpleName()                 + "::"  + method.getName() + ", args: "  + Arrays.toString(args));     }     public  static  void  after (Object object, Method method, Object[] args, Object result)   {         System.out.println("leaving "  + method.getDeclaringClass().getSimpleName()                 + "::"  + method.getName() + ", result: "  + result);     } } 
 
ExceptionAspect.java
@Aspect (ServiceB.class ) public  class  ExceptionAspect   {    public  static  void  exception (Object object, Method method, Object[] args, Throwable throwable)   {         System.out.println("exception when calling: "  + method.getName() + ", "  + Arrays.toString(args));     } } 
 
ServiceLogAspect目的是在类ServiceA和serviceB所有方法的执行前后加一些日志,而ExceptionAspect的目的是在类ServiceB的方法执行出现异常时收到通知,并输出一些信息。
它们都没有修改类本身,并且类本身做的事就是比较通用和业务化的,与ServiceA和ServiceB的具体逻辑关系也并不密切,但又想改变 ServiceA 和 ServiceB 的行为,那么这就是AOP的思维。
切面定义好之后,需要实现一个容器类,用来获取被切面注解的类并处理。
再定义一个新注解类,用来实现DI
@Retention (RetentionPolicy.RUNTIME)@Target (ElementType.FIELD)public  @interface  Inject {} 
 
ServiceA.java
public  class  ServiceA   {    @Inject      ServiceB b;     public  void  callB ()  {         b.action();     } } 
 
ServiceB.java
public  class  ServiceB   {    public  void  action ()  {         System.out.println("this is classB! " );     } } 
 
枚举类 InterceptPoint.java
public  enum  InterceptPoint {    BEFORE, AFTER, EXCEPTION } 
 
容器处理类如下
public  class  CglibContainer   {    static  Map<Class<?>, Map<InterceptPoint, List<Method>>> interceptMethodsMap = new  HashMap<>();     static  Class<?>[] aspects = new  Class[]{             ServiceLogAspect.class ,              ExceptionAspect .class      } ;    static  {         init();     }     private  static  void  init ()   {         for  (Class<?> cls : aspects) {             Aspect aspect = cls.getAnnotation(Aspect.class ) ;             if  (aspect != null ) {                 try  {                     Method before = getMethod(cls, "before", new Class<?>[]{Object.class, Method.class, Object[].class});                     Method after = getMethod(cls, "after", new Class<?>[]{Object.class, Method.class, Object[].class, Object.class});                     Method exception = getMethod(cls, "exception", new Class<?>[]{Object.class, Method.class, Object[].class, Throwable.class});                     Class<?>[] interceptedArr = aspect.value();                     for  (Class<?> intercepted : interceptedArr) {                         addInterceptMethod(intercepted, InterceptPoint.BEFORE, before);                         addInterceptMethod(intercepted, InterceptPoint.AFTER, after);                         addInterceptMethod(intercepted, InterceptPoint.EXCEPTION, exception);                     }                 } catch  (NoSuchMethodException e) {                     e.printStackTrace();                 }             }         }     }     private  static  void  addInterceptMethod (Class<?> cls, InterceptPoint point, Method method)   {         if  (method == null ) {             return ;         }         Map<InterceptPoint, List<Method>> map = interceptMethodsMap.get(cls);         if  (map == null ) {             map = new  HashMap<>();             interceptMethodsMap.put(cls, map);         }         List<Method> methods = map.get(point);         if  (methods == null  || methods.size() == 0 ) {             methods = new  ArrayList<>();             map.put(point, methods);         }         methods.add(method);     }     public  static  <T> T getInstance (Class<T> cls)   {         try  {             T obj = createInstance(cls);             Field[] fields = cls.getDeclaredFields();             for  (Field f : fields) {                 if  (f.isAnnotationPresent(Inject.class ))  {                     if  (!f.isAccessible()) {                         f.setAccessible(true );                     }                     Class<?> fieldCls = f.getType();                     f.set(obj, getInstance(fieldCls));                 }             }             return  obj;         } catch  (Exception ex) {             throw  new  RuntimeException(ex);         }     }     private  static  <T> T createInstance (Class<T> cls)               throws  InstantiationException, IllegalAccessException  {        if  (!interceptMethodsMap.containsKey(cls)) {             return  (T) cls.newInstance();         }         Enhancer enhancer = new  Enhancer();         enhancer.setSuperclass(cls);         enhancer.setCallback(new  AspectInterceptor());         return  (T) enhancer.create();     }     static  class  AspectInterceptor  implements  MethodInterceptor   {         @Override          public  Object intercept (Object o, Method method, Object[] objects,                                  MethodProxy methodProxy)  throws  Throwable  {                         List<Method> beforeMethods = getInterceptMethods(o.getClass().getSuperclass(), InterceptPoint.BEFORE);             for  (Method m : beforeMethods) {                 m.invoke(null , new  Object[]{o, method, objects});             }             try  {                                  Object result = methodProxy.invokeSuper(o, objects);                                  List<Method> afterMethods = getInterceptMethods(o.getClass().getSuperclass(), InterceptPoint.AFTER);                 for  (Method m : afterMethods) {                     m.invoke(null , new  Object[]{o, method, objects, result});                 }                 return  result;             } catch  (Throwable e) {                                  List<Method> exceptionMethods = getInterceptMethods(o.getClass().getSuperclass(), InterceptPoint.EXCEPTION);                 for  (Method m : exceptionMethods) {                     m.invoke(null , new  Object[]{o, method, objects, e});                 }                 throw  e;             }         }     }     static  List<Method> getInterceptMethods (Class<?> cls, InterceptPoint point)   {         Map<InterceptPoint, List<Method>> map = interceptMethodsMap.get(cls);         if  (map == null ) {             return  Collections.emptyList();         }         List<Method> methods = map.get(point);         if  (methods == null ) {             return  Collections.emptyList();         }         return  methods;     } } 
 
小结 相比完整AOP框架,这个是非常粗粒度的实现的,还有很多细节需要打磨,这里主要用于解释动态代理的应用以及AOP的一些基本思路和原理。