本篇,我们来探讨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的一些基本思路和原理。