23.5 动态代理的应用:AOP
利用cglib动态代理,我们实现一个极简的AOP框架,演示AOP的基本思路和技术,先来看这个框架的用法,然后分析其实现原理。
23.5.1 用法
我们添加一个新的注解@Aspect
,其定义为:
1 2 3 4 5
| @Retention(RUNTIME) @Target(TYPE) public @interface Aspect { Class<? >[] value(); }
|
它用于注解切面类,它有一个参数,可以指定要增强的类,比如:
1 2
| @Aspect({ServiceA.class, ServiceB.class}) public class ServiceLogAspect
|
ServiceLogAspect就是一个切面,它负责类ServiceA和ServiceB的日志切面,即为这两个类增加日志功能。再如:
1 2
| @Aspect({ServiceB.class}) public class ExceptionAspect
|
ExceptionAspect也是一个切面,它负责类ServiceB的异常切面。
这些切面类与主体类怎么协作呢?我们约定,切面类可以声明三个方法before/after/exception,在主体类的方法调用前/调用后/出现异常时分别调用这三个方法,这三个方法的声明需符合如下签名:
1 2 3 4 5
| public static void before(Object object, Method method, Object[] args) public static void after(Object object, Method method, Object[] args, Object result) public static void exception(Object object, Method method, Object[] args, Throwable e)
|
object、method和args与cglib MethodInterceptor中 的invoke参 数 一 样,after中 的result表示方法执行的结果,exception中的e表示发生的异常类型。
ServiceLogAspect实现了before和after方法,加了一些日志,如代码清单23-6所示。
代码清单23-6 日志切面类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Retention(RUNTIME) @Target(TYPE) public @interface Aspect { Class<? >[] value(); } @Aspect({ServiceA.class, ServiceB.class}) public class ServiceLogAspect @Aspect({ServiceB.class}) public class ExceptionAspect public static void before(Object object, Method method, Object[] args) public static void after(Object object, Method method, Object[] args, Object result) public static void exception(Object object, Method method, Object[] args, Throwable e) @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只实现exception方法,在异常发生时,输出一些信息,如代码清单23-7所示。
代码清单23-7 异常切面类
1 2 3 4 5 6 7 8
| @Aspect({ ServiceB.class }) public class ExceptionAspect { public static void exception(Object object, Method method, Object[] args, Throwable e) { System.err.println("exception when calling: " + method.getName() + ", " + Arrays.toString(args)); } }
|
ServiceLogAspect的目的是在类ServiceA和ServiceB所有方法的执行前后加一些日志,而ExceptionAspect的目的是在类ServiceB的方法执行出现异常时收到通知并输出一些信息。它们都没有修改类ServiceA和ServiceB本身,本身做的事是比较通用的,与ServiceA和ServiceB的具体逻辑关系也不密切,但又想改变ServiceA/ServiceB的行为,这就是AOP的思维。
只是声明一个切面类是不起作用的,我们需要与第22章介绍的DI容器结合起来。我们实现一个新的容器CGLibContainer,它有一个方法:
1
| public static <T> T getInstance(Class<T> cls)
|
通过该方法获取ServiceA或ServiceB,它们的行为就会被改变,ServiceA和ServiceB的定义与第22章一样,如代码清单22-1所示,这里就不重复了。
通过CGLibContainer获取ServiceA,会自动应用ServiceLogAspect,比如:
1 2
| ServiceA a = CGLibContainer.getInstance(ServiceA.class); a.callB();
|
输出为:
1 2 3 4 5
| entering ServiceA::callB, args: [] entering ServiceB::action, args: [] I'm B leaving ServiceB::action, result: null leaving ServiceA::callB, result: null
|
23.5.2 实现原理
这是怎么做到的呢?CGLibContainer在初始化的时候,会分析带有@Aspect注解的类,分析出每个类的方法在调用前/调用后/出现异常时应该调用哪些方法,在创建该类的对象时,如果有需要被调用的方法,则创建一个动态代理对象,下面我们具体来看下代码。
为简化起见,我们基于第22章介绍的DI容器的第一个版本,即每次获取对象时都创建一个,不支持单例。我们定义一个枚举InterceptPoint,表示切点(调用前/调用后/出现异常):
1 2 3
| public static enum InterceptPoint { BEFORE, AFTER, EXCEPTION }
|
在CGLibContainer中定义一个静态变量,表示每个类的每个切点的方法列表,定义如下:
1 2
| static Map<Class<? >, Map<InterceptPoint, List<Method>>> interceptMethodsMap = new HashMap<>();
|
我们在CGLibContainer的类初始化过程中初始化该对象,方法是分析每个带有@Aspect
注解的类,这些类一般可以通过扫描所有的类得到,为简化起见,我们将它们写在代码中,如下所示:
1 2
| static Class<? >[] aspects = new Class<? >[] { ServiceLogAspect.class, ExceptionAspect.class };
|
分析这些带@Aspect
注解的类,并初始化interceptMethodsMap的代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| static { init(); } private static void init() { for(Class<? > cls : aspects) { Aspect aspect = cls.getAnnotation(Aspect.class); if(aspect ! = null) { 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<? >[] intercepttedArr = aspect.value(); for(Class<? > interceptted : intercepttedArr) { addInterceptMethod(interceptted, InterceptPoint.BEFORE, before); addInterceptMethod(interceptted, InterceptPoint.AFTER, after); addInterceptMethod(interceptted, InterceptPoint.EXCEPTION, exception); } } } }
|
对每个切面,即带有@Aspect
注解的类cls,查找其before/after/exception方法,调用方法addInterceptMethod将其加入目标类的切点方法列表中,addInterceptMethod的代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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 = new ArrayList<>(); map.put(point, methods); } methods.add(method); }
|
准备好了每个类的每个切点的方法列表,我们来看根据类型创建实例的代码:
1 2 3 4 5 6 7 8 9 10
| 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(); }
|
如果类型cls不需要增强,则直接调用cls.newInstance(),否则使用cglib创建动态代理,callback为AspectInterceptor,其代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| static class AspectInterceptor implements MethodInterceptor { @Overridepublic Object intercept(Object object, Method method,Object[] args, MethodProxy proxy) throws Throwable { beforeMethods = getInterceptMethods(object.getClass().getSuperclass(), InterceptPoint.BEFORE); for(Method m : beforeMethods) { m.invoke(null, new Object[] {object, method, args }); } try { result = proxy.invokeSuper(object, args); afterMethods = getInterceptMethods(object.getClass().getSuperclass(), InterceptPoint.AFTER); for(Method m : afterMethods) { m.invoke(null, new Object[] {object, method, args, result }); } return result; } catch (Throwable e) { exceptionMethods = getInterceptMethods(object.getClass().getSuperclass(), InterceptPoint.EXCEPTION); for(Method m : exceptionMethods) { m.invoke(null, new Object[] {object, method, args, e }); } throw e; } } }
|
这段代码也容易理解,它根据原始类的实际类型查找应该执行的before/after/exception方法列表,在调用原始方法前执行before方法,执行后执行after方法,出现异常时执行exception方法。getInterceptMethods方法的代码为:
1 2 3 4 5 6 7 8 9 10 11 12
| 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; }
|
这段代码也容易理解。CGLibContainer最终的getInstance方法就简单了,它调用create-Instance创建实例,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public static <T> T getInstance(Class<T> cls) { try { T obj = createInstance(cls); Field[] fields = cls.getDeclaredFields(); for(Field f : fields) { if(f.isAnnotationPresent(SimpleInject.class)) { if(! f.isAccessible()) { f.setAccessible(true); } Class<? > fieldCls = f.getType(); f.set(obj, getInstance(fieldCls)); } } return obj; } catch (Exception e) { throw new RuntimeException(e); } }
|
相比完整的AOP框架,这个AOP的实现是非常粗糙的,主要用于解释动态代理的应用和AOP的一些基本思路和原理。完整的代码在github上,地址为https://github.com/swift-ma/program-logic ,位于包shuo.laoma.dynamic.c86下。
本章探讨了Java中的代理,从静态代理到两种动态代理。动态代理广泛应用于各种系统程序、框架和库中,用于为应用程序员提供易用的支持、实现AOP,以及其他灵活通用的功能,理解了动态代理,我们就能更好地利用这些系统程序、框架和库,在需要的时候,也可以自己创建动态代理。
下一章,我们来进一步理解Java中的类加载过程,探讨如何利用自定义的类加载器实现更为动态强大的功能。