23.5 动态代理的应用:AOP

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 {
//执行before方法List<Method>
beforeMethods = getInterceptMethods(object.getClass().getSuperclass(), InterceptPoint.BEFORE);
for(Method m : beforeMethods) {
m.invoke(null, new Object[] {object, method, args });
}
try {
//调用原始方法Object
result = proxy.invokeSuper(object, args);
//执行after方法List<Method>
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) {
//执行exception方法List<Method>
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中的类加载过程,探讨如何利用自定义的类加载器实现更为动态强大的功能。