目录
关于AOP的简单分析
通知(advice)
通知定义了一个切面在什么时候需要完成什么样的功能,很明显advice的实现不是由框架来完成,而是由用户创建好advice然后注册到框架中,让框架在适当的时候使用它。这里我们需要考虑几个问题。
用户创建好的advice框架怎么感知?框架如何对用户注册的不同的advice进行隔离?
这个问题很简单,大多数人都明白,这就类似于Java中的JDBC,Java提供一套公共的接口,各个数据库厂商实现Java提供的接口来完成对数据库的操作。我们这里也提供一套用于AOP的接口,用户在使用时对接口进行实现即可。
advice的时机有哪些?需要提供哪些接口?
这里直接拿Spring中定义好的增强的时机。
- Before——在方法调用之前调用通知
- After——在方法完成之后调用通知,无论方法执行成功与否
- After-returning——在方法执行成功之后调用通知
- After-throwing——在方法抛出异常后进行通知
- Around——通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
好了,我们可以使用一个接口来定义上面的处理方法,在用户使用的时候实现方法即可,如下:

貌似差不多了,但是我们需要注意到,用户在使用advice的使用,不可能说每次都是需要对上诉几种方式同时进行增强,更多可能是只需要一种方式。但是如果只有一个接口的话就要求用户每次都需要实现所有的方法,这样显的十分的不友好。
我们应该让这些不同的方法对于用户来说是可选,需要什么就实现哪一个。那么我们需要将每一个方法都对应一个接口吗?不需要。上面的
after(...)和afterSuccess(...)都是在方法执行之后实现,不同在于一个需要成功后的返回值而另一个不需要,这两个可以作为一个实现由返回值区分。进行异常后的增强处理这要求对被执行的方法进行包裹住,捕获异常。这就和环绕差不多了,两者可以放一起。类图:

pointcut
advice基本就这样了,下面就是pointcut了。说起切点,用过Spring中的AOP的肯定对切入点表达式比较了解了,在Spring中用户通过切入点表达式来定义我们的增强功能作用在那一类方法上。这个切入点表达式十分的重要。对于我们的手写AOP来说,也需要提供这样的功能。当然表达式由用户来写,由我们的框架来解析用户的表达式,然后对应到具体的方法上。
如何解析用户定义的表达式?上面说到了,由一串字符来匹配一个或多个不同的目标,我们第一个反应肯定是正则表达式,很明显这个功能使用正则是可以进行实现的。但实际上这样的表达式还有很多。比如
AspectJ,Ant path等。具体使用什么就自己决定了,这里我实现正则匹配这一种。execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)- 如何找到我们要增强的方法呢?
当我们确定好有哪些类的哪些方法需要增强,后面就需要考虑我们如何获取到这些方法(对方法增强肯定需要获取到具体的方法)。
- 有了表达式我们可以确定具体的类和方法,表达式只是定义了相对的路径,如何根据相对路径获取Class文件地址?
对bean实例的增强是在初始化的时候完成的,初始化的时候判断如果需要增强,则通过代理生成代理对象,在返回时由该代理对象代替原实例被注册到容器中。
- Class文件有了,怎么取到类中的方法?
在前面章节中我们获取过方法,使用Class对象即可获取所有的非私有方法。在实际调用被增强方法时,将该方法与所有的advice进行匹配,如果有匹配到advice,则执行相应的增强。当然我们并不需要每一次都需要遍历获取,为了效率可以对方法和增强的advice进行缓存。
Aspect/Advisor
我们有了增强功能的实现和确定了需要增强那些方法。到了现在我们就需要将拿到的方法进行增强了。
在运行过程中对已有的类或方法的功能进行增强同时又不改变原有类的代码,这妥妥的代理模式嘛。如果不理解代理模式的可以看这个教程:
当我们在注册bean和调用方法时,对方法的增强会用到Advisor,所以我们还需要提供一个注册和获取Advisor的接口。
Weaving
现在我们有了切面,用户也已经能够比较简单的来定义如何使用切面,最重要的一步到了,那就是我们应该如何对需要增强的类进行增强呢?什么时候进行增强?
上面已经说过了对类和方法进行增强就使用代理模式来增强。那么我们作为框架该在什么什么时候来增强呢?
这里有两种时机。一是在启动容器初始化bean的时候就进行增强,然后容器中存放的不是bean的实例,而是bean的代理实例。二是在每一次使用bean的时候判断一次是否需要增强,需要就对其增强,然后返回bean的代理实例。这两种方法很明显第一种比较友好,只是让容器的启动时间稍微长了一点,而第二种在运行时判断,会使得用户的体验变差。
在初始化bean的那个过程来增强?会不会存在问题?
根据之前的介绍,我们的框架初始化bean是在BeanFactory中进行,还包括bean的实例化,参数注入以及将bean放入容器中等。很明显对bean的增强应该是在bean实例化完成并在还没有放进容器中的时候。那么也就是在BeanFactory的doGetBean方法中了。这里有一个小问题在于,doGetBean方法做的事情已经够多了,继续往里加入代码无疑会使得代码大爆炸,很难维护也不易扩展。为了解决这个问题这里我们可以使用观察者模式来解决这一问题,将doGetBean方法中每一个过程都作为一个观察者存在,当我们需要添加功能是既可以添加一个观察者然后注入,这样不会对已有代码做出改变。
定义一个观察者的接口:

这里我们暂时只定义了aop应用的观察者,其他的比如实例化,参数注入后面慢慢加。
BeanPostProcessor是在BeanFactory中对bean进行操作时触发,我们也应该在BeanFactory中加入BeanPostProcessor的列表和注册BeanPostProcessor的方法。
在这里的观察者模式的应用中,BeanFactory充当subject角色,BeanPostProcessor则充当observer的角色,BeanFactory监听BeanPostProcessor,我们可以将功能抽出为一个BeanPostProcessor,将其注册到BeanFactory中,这样既不会使得BeanFactory中代码过多,同时也比较容易做到了功能的解耦,假设我们不需要某一个功能,那么直接接触绑定即可而不需要任何其他操作。在这里我们只实现了Aop功能的注册。
假设我们要对其他功能也抽为一个观察者,那么直接继承BeanPostProcessor接口实现自己的功能然后注册到BeanFactory中。
功能实现分析
现在接口有了,我们现在需要考虑如何来实现功能了。那么我们现在梳理一下我们需要做什么。
- 在进行bean创建的时候,需要判断该bean是否需要被增强,这个工作是由AopPostProcessor接口来做,判断是否需要被增强和通过哪种方式来增强(JDK代理还是cglib代理)。如果需要增强则创建代理对象,注册到容器是则使用该代理对
