微热山丘,探索 IoC、AOP 实现原理(二) AOP 实现原理

AOP 实现原理

一、简介

AOP 是 Aspect Oriented Programming 的简写,面向切面编程。主要的作用是以一种统一的方式对程序的逻辑进行修改、增强处理。可以在编译时也可以在运行时实现。编译时处理一般是通过字节码处理技术,运行时进行的一般是通过动态代理技术实现。

二、AOP 核心概念

  • Concerns, 关注:有两类,核心关注–是关于业务逻辑的;横切关注–是一些通用的逻辑,比如日志、缓存。
  • Joinpoint, 连接点:是执行时的切入点。可以是字段访问也可以是被调用方法。基于动态代理技术实现的一般只支持方法调用的连接点。
  • Target, 目标:是一个被切入的地方,一般是一个业务逻辑。比如是对一个业务逻辑的方法的调用。
  • Pointcut, 切入点:并不是所有的连接点都需要切入,切入点用于指定哪些连接点需要切入。
  • Advice, 建议:定义了 Aspect 的任务和什么时候执行它,是在核心关注之前还是之后。
  • Aspect, 方面:Advice 和 pointcut 定义一个方面。Advice 定义 Aspect 的任务和什么时候执行它,而切入点 pointcut 定义在哪里具体地方切入。就是说 Aspect 定义了它是什么东西、什么时候切入和在哪里切入。
  • Weaving, 织入:织入是一个把横切方面混合到业务目标对象的过程。可以是在编译时间,也可以是在运行时使用类加载机制,Spring AOP 是在运行时生成 bean 时处理。

下面是在 Sping 的 XML 里定义一个 AOP 的配置,注意其中个元素间的关系:

<!-- 一个 weaving 的定义 -->
<aop:config>
    <!-- 定义 aspect, ref 指向 任务的定义 -->
    <aop:aspect id="aspectCommonLogHandler" ref="commonLogHandler">
        <!-- 定义 pointcut -->
        <aop:pointcut id="commonLogPointcut" expression="execution( * net.coderbee.*.controller..*.*(..))" />

        <!-- 定义 advice, around 表示在目标的前/后执行 -->
        <aop:around method="inceptor" pointcut-ref="commonLogPointcut" />
    </aop:aspect>
</aop:config>

三、AOP 实现

1. 各组件定义配置

warnhill 的 AOP 实现没有采用 Spring 那样的配置方式,而是采用 bean 定义的形式组织起来:

<!-- 定义织入逻辑的实现 -->
<bean id="aspectJAutoProxyCreator" class="net.coderbee.warmhill.aop.AspectJAutoProxyCreator" />

<!-- 定义 pointcut -->
<bean id="pointcut" class="net.coderbee.warmhill.aop.AspectJExpressionPointcut" >
    <property name="expression" value="execution(* net.coderbee..*.*(..))" />
</bean>

<!-- 定义 任务 -->
<bean id="timerInterceptor" class="net.coderbee.warmhill.aop.TimerInterceptor" />
<bean id="anotherInterceptor" class="net.coderbee.warmhill.aop.AnotherInterceptor" />

<!-- 定义 aspect -->
<bean id="testAdvisor" class="net.coderbee.warmhill.aop.AspectJExpressionPointcutAdvisor">
    <property name="advice" ref="timerInterceptor" />
    <property name="pointcut" ref="pointcut" />
</bean>
<bean id="testAdvisor2" class="net.coderbee.warmhill.aop.AspectJExpressionPointcutAdvisor">
    <property name="advice" ref="anotherInterceptor" />
    <property name="pointcut" ref="pointcut" />
</bean>

2. 任务实现

任务的实现类必须实现 org.aopalliance.intercept.MethodInterceptor 接口,至于是在目标关注之前还是之后执行由任务实现类自行决定。

下面是个计算目标方法调用耗时的任务实现:

public class TimerInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println("start invoke " + invocation.getMethod().getName());

        Object result = invocation.proceed();

        System.out.println("end invoke " + invocation.getMethod().getName() + " used time millils :" + (System.currentTimeMillis() - start));
        return result;
    }
}

3. 切入点实现

切入点只支持被调用方法,采用 aspectj 类库实现,用于匹配给定的 Class 是否满足该切入点。

具体实现见类: AspectJExpressionPointcut

4. 织入实现

warnhill 基于 IoC 容器的 BeanPostProcessor 机制对生成过程中的 bean 进行加工,织入相关的目标逻辑。

对于同一目标上的多个切面,是按声明的顺序生效的。对于代理机制,最后加入的代理反而是最先生效的。因此,对于多个切面,需要按声明的逆序进行切入,以便让调用时按声明顺序调用任务的逻辑。

对于框架基础设施类、任务类的实现是不能做织入的,避免循环地织入。

AspectJAutoProxyCreatorBeanPostProcessor 的一个实现类。下面是织入的核心:

public Object postProcessAfterInit(Object bean, String beanId) {
    if (Advice.class.isAssignableFrom(bean.getClass())
            || Advisor.class.isAssignableFrom(bean.getClass())
            || MethodInterceptor.class.isAssignableFrom(bean.getClass())
            || Pointcut.class.isAssignableFrom(bean.getClass())) {
        return bean;
    }

    Class<?> beanClass = bean.getClass();
    List<AspectJExpressionPointcutAdvisor> pointcutAdvisors = beanFactory.getBeans(AspectJExpressionPointcutAdvisor.class);
    Collections.reverse(pointcutAdvisors);

    for (AspectJExpressionPointcutAdvisor pointcutAdvisor : pointcutAdvisors) {
        if (pointcutAdvisor.getPointcut().getClassFilter().isMatch(beanClass)) {
            ProxyFactory proxyFactory = new ProxyFactory();
            proxyFactory.setMethodMatcher(pointcutAdvisor.getPointcut().getMethodMatcher());
            proxyFactory.setInterceptor((MethodInterceptor) pointcutAdvisor.getAdvice());

            TargetSource targetSource = new TargetSource(bean, beanClass, beanClass.getInterfaces());
            proxyFactory.setTargetSource(targetSource);

            bean = proxyFactory.getProxy();
        }
    }

    return bean;
}

注意:上面的逻辑只是对 bean 的类型进行匹配,匹配则生成代理对象,对方法的判断是在调用时进行的。

CglibAopProxy 里面有对方法进行判断:

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

    CglibMethodInvocation cglibMethodInvocation = new CglibMethodInvocation(method, advisorSupport
            .getTargetSource().getTarget(), objects, methodProxy);
    if (advisorSupport.getMethodMatcher() != null && advisorSupport.getMethodMatcher().matches(method,
            advisorSupport.getTargetSource().getTargetType())){
        return advisorSupport.getInterceptor().invoke(cglibMethodInvocation);
    }

    return cglibMethodInvocation.proceed();
}

欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据