Kotlin语言实现AOP框架

本文将简单介绍如何用Kotlin实现一个AOP框架(在此之前本人先行编写了IOC框架,IOC实现为采用xml配置的方式,比较简单,可直接在GitHub参阅源码,这里就不赘述了)。

基本注意事项

Spring AOP构建在动态代理基础之上,因此Spring对AOP的支持局限于方法的拦截;

目标对象实现了接口,Spring默认采用JDK动态代理实现AOP;

条件满足时,CGLIB可以强行取代JDK动态代理来实现AOP;

如果目标对象没有实现接口,则采用CGLIB实现AOP;

框架类/接口介绍

Spring AOP的核心是代理模式,而代理模式有多种,包括JDK动态代理以及CGLIB,这里首先采用JDK动态代理来实现。

和Spring一样,本框架倚赖AspectJ框架,以期快速完成框架的编写。

下面介绍一些主要的类/接口。

TargetSource

被代理的目标对象包,之所以称之为包是因为它除了包含目标对象,还包含目标对象的元数据。

Advisor

用于获得Advice,Advice是AspectJ框架提供的接口。

Pointcut

切点接口,表示在哪里切入,它包括两个方法,分别用来获取ClassFilter和MethodMatcher,一个是类过滤器,一个是方法匹配器。

ClassFilter & MethodMatcher

类过滤器接口和方法匹配器接口,分别包含匹配方法判断是否满足匹配条件。

PointcutAdvisor

切面接口,提供切点和advice的获取方法。

AdvisedSupport

一个支持类,它包含目标对象包引用、拦截器对象引用以及判断方法匹配接口的引用。MethodInterceptor是拦截器接口,本质上仍然是一个Advice,MethodMatcher代表切点,所以AdvisedSupport提供了“在什么地方对什么样的目标对象进行什么样的拦截”的信息。

AopProxy

代理类的接口,不同模式的代理都实现该接口。

ReflectiveMethodInvocation

该类的作用主要是通过反射调用目标对象的方法。ReflectiveMethodInvocation是MethodInvocation的具体实现,MethodInvocation是AspectJ框架提供的接口,该接口还继承了AspectJ提供的另一接口Invocation。

AspectJExpressionPointcut

切点的具体实现类。AspectJ框架提供了AspectJExpressionPointcut类,本人在此基础上修改了一些内容,去掉了暂时不需要的部分。

AspectJExpressionPointcutAdvisor

切面的实现类。

BeanFactoryAware

设置工厂类的接口。

JdkDynamicAopProxy

JDK动态代理类,实现AOP的核心。

AspectJAwareAdvisorAutoProxyCreator

它是一个BeanPostProcessor的实现类,用于给拦截到的Bean对象创建代理实例。

JDK动态代理

首先贴出代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class JdkDynamicAopProxy(var advised: AdvisedSupport) : AopProxy, InvocationHandler{
override fun getProxy(): Any? {
return Proxy.newProxyInstance(advised.targetSource!!.target!!.javaClass.classLoader, advised.targetSource!!.targetClass, this)
}
override fun invoke(proxy: Any?, method: Method?, args: Array<Any>?): Any? {
var methodInterceptor = advised.methodInterceptor
var methodMatcher = advised.methodMatcher
if (methodMatcher != null && methodMatcher.matches(method!!, advised.targetSource!!.target!!::class.java))
// 匹配时返回拦截器的处理方法
return methodInterceptor?.invoke(ReflectiveMethodInvocation(advised.targetSource!!.target, method, args))
else {
// 不匹配时返回目标对象的原本方法
return method?.invoke(advised.targetSource!!.target, *(p2 ?: arrayOfNulls<Any>(0)))
}
}
}

和通常JDK动态代理的实现最大区别是,增加了MethodInterceptor和ReflectiveMethodInvocation对象的参与,比较if…else的两个返回部分就可以知道一切都是为了将逻辑更进一层的封装,这样通过实现MethodInterceptor就可以完成前置Advice、后置Advice等等切面工作。

比如在后文还将运用于项目代码的SimpleInterceptor:

1
2
3
4
5
6
7
8
public class SimpleInterceptor : MethodInterceptor {
override fun invoke(invocation: MethodInvocation?): Any? {
println("Before ...")
var rval = invocation!!.proceed()
println("After ...")
return rval
}
}

这就是一个简单的环绕Advice拦截器。

测试JDK动态代理

写一个测试来检测目前写的代码。

首先写一个环切Advice:

1
2
3
4
5
6
7
8
public class SimpleInterceptor : MethodInterceptor {
override fun invoke(invocation: MethodInvocation?): Any? {
println("Before ...")
var rval = invocation!!.proceed()
println("After ...")
return rval
}
}

然后是目标被代理类:

1
2
3
4
5
6
7
8
9
10
11
12
interface HelloJDKFather {
fun helloMethod(x: Int)
}
interface HelloJDKMother
class HelloJDKProxy : HelloJDKFather, HelloJDKMother {
override fun helloMethod(x: Int) {
println("I am Hello! $x")
}
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class JdkDynamicAopProxyTest {
@Test
fun getProxy() {
var helloJDKProxy = HelloJDKProxy()
var aspectJExpressionPointcut = AspectJExpressionPointcut()
var expression = "execution(* testresources.beans.*.*(..))"
var advisedSupport = AdvisedSupport()
var simpleInterceptor = SimpleInterceptor()
aspectJExpressionPointcut.expression = expression
var targetSource = TargetSource(helloJDKProxy, *helloJDKProxy.javaClass.interfaces)
var methodMatcher = aspectJExpressionPointcut.getMethodMatcher()
advisedSupport.targetSource = targetSource
advisedSupport.methodMatcher = methodMatcher
advisedSupport.methodInterceptor = simpleInterceptor
var jdkDynamicAopProxy = JdkDynamicAopProxy(advisedSupport).getProxy() as HelloJDKFather
jdkDynamicAopProxy.helloMethod(5)
}
}

输出为:

1
2
3
4
5
Before ...
I am Hello! 5
After ...
Process finished with exit code 0

成功完成动态代理的工作。

Kotlin编写问题 & 解决

暂时停下框架的编写,讨论在之前使用Kotlin语言的过程中积累的几个问题。

问题一

这是编写ReflectiveMethodInvocation的方法proceed()时产生的问题。如果是代理没有显示参数的方法,原本的代码在IDE下会报IllegalArgumentException异常。内部调用的是public Object invoke(Object obj, Object… args),由于可变参数需要星号投影,且如果args是null需要生成零长null型数组,所以解决代码如下:

1
2
3
4
override fun proceed(): Any? {
var re = method!!.invoke(target, *(args ?: arrayOfNulls<Any>(0)))
return re
}

问题二

编写TargetSource类时传入的可变参数(vararg)赋值给数组出现Type mismatch问题。本人解决的方法:

1
2
3
4
constructor(target: Any, vararg targetClass: Class<*>) {
this.target = target
this.targetClass = Array(targetClass.size) { i -> targetClass[i] }
}

问题三

在编写initializeBean方法时,出现参数Val cannot be reassigned的问题。用代码描述就是:

1
2
3
4
5
6
7
8
fun testMethod(bean: Bean) {
bean = otherMethod(bean)
}
protected fun initializeBean(bean: Any, id: String) {
for (beanPostProcessor in beanPostProcessors) {
bean = beanPostProcessor.postProcessAfterInitialization(bean, id)
}
}

在本文最后的参考链接Function parameters are “val” not “var”?下的回答中,语言设计的作者给出了问题产生原因。在这里,本人用返回值来解决,结合

1
2
3
4
5
6
7
protected fun initializeBean(_bean: Any, id: String): Any {
var bean = _bean
for (beanPostProcessor in beanPostProcessors) {
bean = beanPostProcessor.postProcessAfterInitialization(bean, id)
}
return bean
}

问题四

继承父类get、set方法和Kotlin的属性机制冲突。这个问题可以通过@JvmField解决:

1
2
3
4
5
6
7
class ReflectiveMethodInvocation(var target: Any?, @JvmField var method: Method?, var args: Array<Any>?) : MethodInvocation {
override fun getMethod(): Method? {
return method
}
...
}

CGLIB动态代理

继续代码的编写,创造一个类CglibAopProxy,整个代码结构和JdkDynamicAopProxy是很相似的,直接贴出:

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
class CglibAopProxy(var advised: AdvisedSupport) : MethodInterceptor, AopProxy {
var targetObject = advised.targetSource!!.target
override fun getProxy(): Any? {
return createProxyInstance()
}
fun createProxyInstance(): Any? {
var enhancer = Enhancer()
enhancer.setSuperclass(this.targetObject!!::class.java)
enhancer.setCallback(this)
return enhancer.create()
}
override fun intercept(p0: Any?, p1: Method?, p2: Array<Any>?, p3: MethodProxy?): Any? {
var methodInterceptor = advised.methodInterceptor
var methodMatcher = advised.methodMatcher
if (methodMatcher != null && methodMatcher.matches(p1!!, advised.targetSource!!.target!!::class.java))
// 匹配时返回拦截器的处理方法
return methodInterceptor?.invoke(ReflectiveMethodInvocation(advised.targetSource!!.target, p1, p2))
else {
// 不匹配时返回目标对象的原本方法
return p1?.invoke(advised.targetSource!!.target, *(p2 ?: arrayOfNulls<Any>(0)))
}
}
}

intercept方法中的逻辑和JDK动态代理的一样,AspectJ框架能将引用部分的代码和被代理的方法解耦合。

测试CglibAopProxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class CglibAopProxyTest {
@Test
fun intercept() {
var helloJDKProxy = HelloJDKProxy()
var aspectJExpressionPointcut = AspectJExpressionPointcut()
var expression = "execution(* testresources.beans.*.*(..))"
var advisedSupport = AdvisedSupport()
var simpleInterceptor = SimpleInterceptor()
aspectJExpressionPointcut.expression = expression
var targetSource = TargetSource(helloJDKProxy, *helloJDKProxy.javaClass.interfaces)
var methodMatcher = aspectJExpressionPointcut.getMethodMatcher()
advisedSupport.targetSource = targetSource
advisedSupport.methodMatcher = methodMatcher
advisedSupport.methodInterceptor = simpleInterceptor
var CglibAopProxy = CglibAopProxy(advisedSupport).getProxy() as HelloJDKProxy
CglibAopProxy.helloMethod(5)
}
}

测试逻辑基本上和JdkDynamicAopProxyTest一摸一样,只有两点不同:

  • HelloJDKProxy要声明为open;
  • 生成的代理对象转换为被代理类而非公共接口;

这也说明了JDK动态代理和CGLIB动态代理的区别:前者被代理的类需要实现接口,而后者被代理的类不需要实现接口,但是该类不能是final的,是需要open的。

完成框架主逻辑

这一步即完善程序,在和之前测试条件相同的情况下,运行Main方法达到环切的效果。

整个程序的执行流程在于方法refresh:

1
2
3
4
5
fun refresh() {
loadBeanDefinitions(beanFac)
registerBeanPostProcessors(beanFac)
onRefresh()
}
  • loadBeanDefinitions方法用来将对xml解析的(name, BeanDef)键值对装入工厂的容器中;

  • registerBeanPostProcessors方法用来注册BeanPostProcessor,BeanPostProcessor对象可以在initializeBean方法中拦截bean,并将动态代理作用在他们身上;

  • onRefresh方法通过beanId获得创建的bean对象,其中最重要的是getBean方法;

getBean()代码如下:

1
2
3
4
5
6
7
8
9
10
11
override fun getBean(id: String): Any {
var beanDef = beanDefMap[id]
// Elvis操作符的作用是为null则返回":"右边的表达式
beanDef ?: throw IllegalArgumentException("该bean没有定义")
var bean = beanDef.bean
// 添加bean初始化处理
return bean ?: initializeBean(doCreateBean(beanDef)!!, id)
}

如果只是创建对象,那么应该使用doCreateBean()而不是getBean():

1
2
3
4
5
6
7
8
9
10
11
12
// 依赖注入产生bean
fun doCreateBean(beanDef: BeanDef): Any? {
// 添加判断语句避免重复创建对象
if (beanDef.bean != null)
return beanDef.bean
var bean = createBeanInstance(beanDef)
applyPropertyValues(bean, beanDef)
beanDef.bean = bean
return bean
}

在代码中为避免创建多个对象产生覆盖问题,可以使用if语句在创建前先进行判断bean是否已然存在。

最后要说明的是,动态代理产生的代理对象需要在beandefMap中取代原本的bean,不然在最终调用时获得的仍然是没有被代理的对象,例如:

1
2
3
4
5
6
...
beanFac!!.beanDefMap[beanName]!!.bean = CglibAopProxy(advisedSupport).getProxy()!!
return beanFac!!.beanDefMap[beanName]!!.bean!!
...

小结

通过编写本项目了解AOP的实现原理,对用Kotlin编程中遇到的问题进行了分析和解决。

参考

Spring AOP对AspectJ的支持:介绍了几种原语;

Why is Kotlin throw IllegalArgumentException when using Proxy

Kotlin function parameter: Val cannot be reassigned

Function parameters are “val” not “var”?

Parameters are Immutable

What’s the difference between Foo::class.java and Foo::javaClass?

Spring Aop原理之Advisor过滤:内有matches方法的详解