spring-aop 学习笔记

阅读必备

了解spring的IOC容器原理 && 设计模式–代理模式

说明

作者是AOP初学者,文章内容是AOP学习复盘思路,是作者的思考思路,聊以记录,不免会有些漏误,提前说明以免误人时间,思想跑偏

背景

最近在学习spring源码,刚刚结束了IOC的学习,然后学习AOP,相比IOC,AOP对于作者来说更加的琐碎,需要更多的耐心,但通透之后顿觉十分畅快,趁兴而书

AOP

1.作用及思想

作为学习的对象,第一个要了解的是它的作用,之后便是很重要的设计思想,很多文章都有介绍,虽不赘述,但必须知道

2.代理

aop的实现需要借助代理模式,因此理解代理模式也是十分必要,代理模式的文章同样很多,这里同样不做具体介绍

开始 代码环节

找到关键点

// 1. 设置被代理对象(Joinpoint)
AdvisedSupport advisedSupport = new AdvisedSupport();
TargetSource targetSource = new TargetSource(helloService, HelloServiceImpl.class, new Class[]{HelloService.class});
advisedSupport.setTargetSource(targetSource);

// 2.设置拦截器(Advice)
TimerInterceptor timerInterceptor = new TimerInterceptor();
advisedSupport.setMethodInterceptor(timerInterceptor);

// 3.创建代理(Proxy)
JdkDynamicAopProxy jdkDynamicAopProxy = new JdkDynamicAopProxy(advisedSupport);
HelloService helloServiceProxy = (HelloService) jdkDynamicAopProxy.getProxy();

// 4.基于AOP的调用
helloServiceProxy.helloWorld();

上述代码为一次aop的普通Jdk动态代理实现,没有整合spring框架,可以看到十分清晰的实现思路

设置对象-->设置拦截器-->生成代理对象-->调用方法

实现aop有两个重要的对象
1.TargetSource
2.TimerInterceptor
TimerInterceptor是拦截方法,很容易理解,TargetSource是拦截对象,是我们要用拦截方法增强功能的对象(比如业务类)
所以看来实现aop需要做好两件事情,第一个是定义好拦截方法,第二个是找到拦截对象,接下来分析AOP整合spring是如何实现的

关键点深入

拆开来看拦截方法和拦截对象

拦截方法

定义好功能

public class TimerInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        long time = System.nanoTime();
        System.out.println("Invocation of Method " + invocation.getMethod().getName() + " start!");
        Object proceed = invocation.proceed();
        System.out.println("Invocation of Method " + invocation.getMethod().getName() + " end! takes " + (System.nanoTime() - time)
                + " nanoseconds.");
        return proceed;
    }
}

在spring-*.xml(自己定义)中配置好就ok

<bean id="timeInterceptor" class="com.xyz.aop.TimerInterceptor"></bean>
拦截对象

接下来就是如何找到拦截对象
这方面有很多的定义,具体可以参考彻底征服 Spring AOP 之 理论篇,非常感谢永顺的文章
拦截对象的识别获取可以通过AspectJ框架实现,用AspectJ表达式(AspectJExpression)来识别
如果只是使用,就很简单,只需要配置一下

<bean id="aspectjAspect" class="com.xyz.aop.AspectJExpressionPointcutAdvisor">
    <property name="advice"  ref="timeInterceptor"></property>
    <property name="expression" value="execution(* com.xyz.*.*(..))"></property>
</bean>

重点是“execution( com.xyz..*(..))” 及AspectJ表达式(AspectJExpression)
可以看到简单获取拦截对象的配置也是十分容易

织入

那么现在拦截方法和拦截对象都有了,如何将拦截方法增强到拦截对象呢?
这个就涉及到IOC中的BeanPostProcessor,所以需要了解IOC原理

public interface BeanPostProcessor {

    Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception;

    Object postProcessAfterInitialization(Object bean, String beanName) throws Exception;
}

可以看到BeanPostProcessor在bean的初始化前后能够做些操作,拦截方法就可以通过这两个方法织入到拦截对象中。

织入实现在AspectJAwareAdvisorAutoProxyCreator.postProcessAfterInitialization()方法中实现

public class AspectJAwareAdvisorAutoProxyCreator implements BeanPostProcessor,BeanFactoryAware {

    private AbstractBeanFactory beanFactory;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception {
        return bean;
    }

    /**
     * bean实例化后要进行初始化操作,会经过这个方法满足条件则生成相关的代理类并返回
     * @param bean
     * @param beanName
     * @return
     * @throws Exception
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws Exception {
        // 如果是切点通知器,则直接返回
        if (bean instanceof AspectJExpressionPointcutAdvisor) {
            return bean;
        }
        // 如果是方法拦截器,则直接返回
        if(bean instanceof MethodInterceptor){
            return bean;
        }
        // 通过getBeanForType方法加载BeanFactory中所有的PointcutAdvisor(保证了PointcutAdvisor的实例化顺序优于普通bean。)
        // AspectJ方式实现织入,这里它会扫描所有的Pointcut,并对bean做织入。
        List<AspectJExpressionPointcutAdvisor> advisors = beanFactory.getBeansForType(AspectJExpressionPointcutAdvisor.class);
        for (AspectJExpressionPointcutAdvisor advisor : advisors) {
            // 匹配要拦截的类
            // 使用AspectJExpressionPointcut的matches匹配器,判断当前对象是不是要拦截的类的对象。
            if (advisor.getPointCut().getClassFilter().matches(bean.getClass())) {
                // ProxyFactory继承了AdvisedSupport,所以内部封装了TargetSource和MethodInterceptor的元数据对象
                ProxyFactory advisedSupport = new ProxyFactory();
                // 设置切点的方法拦截器
                advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());
                // 设置切点的方法匹配器  利用AspectJ表达式进行方法匹配
                // AspectJExpressionPointcutAdvisor里的AspectJExpressionPointcut的getMethodMatcher()方法
                advisedSupport.setMethodMatcher(advisor.getPointCut().getMethodMatcher());

                // 要拦截的类,生成一个TargetSource(要拦截的对象和器类型)(被代理对象)
                TargetSource targetSource = new TargetSource(bean, bean.getClass(), bean.getClass().getInterfaces());
                advisedSupport.setTargetSource(targetSource);
                // 交给实现了AopProxy接口的getProxy方法的ProxyFatctory区生成代理对象
                return advisedSupport.getProxy();
            }
        }
        return bean;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws Exception {
        this.beanFactory = (AbstractBeanFactory) beanFactory;
    }
}

对比一下最开始的代码,看出逻辑大致相同,

是否为拦截对象(类级别)-->(是)-->设置方法拦截器-->设置方法匹配器-->设置拦截对象-->返回生成的代理对象

只是借助spring,实现了更好的解耦,可以通过xml进行配置。

通过xml配置方式实现aop

看下完整的配置

//业务对象
<bean id="outputService" class="com.xyz.OutputServiceImpl">
</bean>

<bean id="helloService" class="com.xyz.HelloServiceImpl">
    <property name="text" value="Hello xmlLoader with bean"></property>
    <property name="outputService" ref="outputService"></property>
</bean>

// 织入
<bean id="autoProxyCreator" class="com.xyz.aop.AspectJAwareAdvisorAutoProxyCreator"></bean>
// 拦截方法
<bean id="timeInterceptor" class="com.xyz.aop.TimerInterceptor"></bean>
// 封装拦截方法和切点
<bean id="aspectjAspect" class="com.xyz.aop.AspectJExpressionPointcutAdvisor">
    //拦截器注入
    <property name="advice"  ref="timeInterceptor"></property>
    //切点注入 用于匹配拦截对象
    <property name="expression" value="execution(* com.xyz.*.*(..))"></property>
</bean>

最后

大致思路就是如此,当然有很多涉及到的要点都没很好的介绍说明,就如同开篇所说,这只是作者的思考思路,是复盘思路,更多的相关源码需要读者自己去探索了,这里推荐一下简化的spring源码–git项目tiny-spring,作者学习该项目时把全部代码亲自敲了一遍,只有自己亲自去实践了才能理解的更加透彻,希望与大家共勉!

    原文作者:ssignik
    原文地址: https://segmentfault.com/a/1190000015096804
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞