本身实现 aop 和 spring aop

上文说到,咱们能够在 BeanPostProcessor 中对 bean 的初始化前化作手脚,当时也说了,我彻底能够生成一个代理类丢回去。java

代理类确定要为用户作一些事情,不可能像学设计模式的时候建立个代理类,而后简单的在前面打印一句话,后面打印一句话,这叫啥事啊,难怪当时听不懂。最好是这个方法的先后过程能够自户本身定义。git

小明说,这还很差办,cglib 已经有现成的了,jdk 也能够实现动态代理,看 mybatis 其实也是这么干的,否则你想它一个接口怎么就能找到 xml 的实现呢,能够参照下 mybatis 的代码。redis

因此首先学习下 cglib 和 jdk 的动态代理,咱们来模拟下 mybatis 是如何经过接口来实现方法调用的spring

cglibsql

目标接口:数据库

public interface UserOperator {
    User queryUserByName(String name);
}

代理处理类:json

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class ProxyHandle implements MethodInterceptor{
    // 实现 MethodInterceptor 的代理拦截接口
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("获取到 sqlId:"+method);
        System.out.println("获取到执行参数列表:"+args[0]);
        System.out.println("解析 spel 表达式,并获取到完整的 sql 语句");
        System.out.println("执行 sql ");
        System.out.println("结果集处理,并返回绑定对象");
        return new User("sanri",1);
    }
}

真正调用处:设计模式

Enhancer enhancer = new Enhancer();

enhancer.setSuperclass(UserOperator.class);
enhancer.setCallback(new ProxyHandle());

//能够把这个类添加进 ioc 容器,这就是真正的代理类
UserOperator userOperator = (UserOperator) enhancer.create();

User sanri = userOperator.queryByName("sanri");
System.out.println(sanri);

jdkmybatis

import java.lang.reflect.InvocationHandler;
public class ProxyHandler implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("获取到 sqlId:"+method);
        System.out.println("获取到执行参数列表:"+args[0]);
        System.out.println("解析 spel 表达式,并获取到完整的 sql 语句");
        System.out.println("执行 sql ");
        System.out.println("结果集处理,并返回绑定对象");
        return new User("sanri",1);
    }
}

真正调用处:maven

UserOperator proxyInstance = (UserOperator)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{UserOperator.class}, new ProxyHandler());
User sanri = proxyInstance.queryByName("sanri");
System.out.println(sanri);

注:jdk 只能支持代理接口,但 cglib 是接口和实体类均可以代理; jdk 是使用实现接口方式,能够多实现,但 cglib 是继承方式,也支持接口方式。

代理模式和装饰模式的区别:

从这也能够看到代理模式和装饰模式的区别 ,代理模式的方法签名通常是不动的,但装饰模式是为了方法的加强,通常会使用别的更好的方法来代替原方法。

如何织入

回到正文,这时咱们已经能够建立一个代理类了,如何把用户行为给弄进来呢,哎,又只能 回调 了,咱们把现场信息给用户,用户实现个人接口,而后我找到接口的全部实现类进行顺序调用,但这时候小明想到了几个问题

  • 用户不必定每一个方法都要作代理逻辑,可能只是部分方法须要,咱们应该可以识别出是哪些方法须要作代理逻辑 (Pointcut)
  • 方法加代理逻辑的位置,方法执行前(Before),方法执行后(After),方法返回数据后(AfterReturning),方法出异常后(AfterThrowing),自定义执行(Around)

根据单一职责原则,得写五个接口,每一个接口要包含 getPointCut() 方法和 handler() 方法,或者绕过单一职责原则,在一个接口中定义 6 个方法,用户不想实现留空便可。总得来讲,用户只须要提交一份规则给我就行,这个规则你不论是用 json,xml ,或者 注解的方式,只要我可以识别在 这个 pointcut 下,须要有哪些自定义行为,在另外一个 pointcut 下又有哪些自定义行为便可。

现拿到用户行为了和切点了,还须要建立目标类的代理类,并把行为给绑定上去,在何时建立代理类呢,确定在把 bean 交给容器的时候悄悄的换掉啊,上文 说到 bean 有一个生命周期是用于作全部 bean 拦截的,而且能够在初始化前和初始化后进行拦截,没错,就是 BeanPostProcessor 咱们能够在初始化后生成代理类。

这里须要注意,并非全部类都须要建立代理。咱们能够这样检测,让 pointcut 提供一个方法用于匹配当前方法是否须要代理,固然这也是 pointcut 的职责,若是当前类有一个方法须要代理,那么当前类是须要代理的,不然认为不须要代理,这么作须要遍历全部类的全部方法,若是运气差的话,看上去很耗费性能 ,但 spring 也是这么干的。。。。。。优化的方案能够这么玩,若是方法须要代理,在类上作一个标识,若是类上存在这个标识,则能够直接建立代理类。

如今咱们把用户行为绑定到代理类,根据上面 jdk 动态代理和 cglib 动态代理的学习,咱们发现,它们都有一个共同的家伙,那就是方法拦截,用于拦截目标类的当前正在执行的方法,并加强其功能,咱们能够在建立代理类的时候找到全部的用户行为并按照顺序和类型依次绑定,能够用责任链模式。

看一下 spring 是怎么玩的

spring 也是在 BeanPostProcessor 接口的 postProcessAfterInitialization 生命周期进行拦截,具体的类为 AspectJAwareAdvisorAutoProxyCreator

spring 配置切面有两种方式,使用注解和使用配置,固然,如今流行注解的方式,更方便,但不论是配置仍是注解,最后都会被解析成 Advisor(InstantiationModelAwarePointcutAdvisorImpl),spring 查找了全部实现 Advisor 的类,源代码在 BeanFactoryAdvisorRetrievalHelper.findAdvisorBeans

advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Advisor.class, true, false);

紧接着,spring 会使使用 Advisor 中的 pointcut 来看当前类是否须要建立代理类,跟进方法能够看到 canApply 方法中是遍历了全部方法一个个匹配来看是否须要建立代理类的,若是有一个须要,则直接返回 true 。固然 spring 更严谨一些,它考虑到了可能有接口的方法须要有代理,我上面说在类加标识是不正确的。

而后经过 createProxy 建立了代理类,里面有区分 cglib 仍是 aop ,下面单拿 cglib 来讲

CglibAopProxy.getProxy 中对类进行加强,主要看 Enhancer 类是如何设置的就行了,有一个 callback 参数 ,咱们通常是第 0 个 callback 也即 DynamicAdvisedInterceptor 它是一个 cglib 的 MethodInterceptor

它重写的是 MethodInterceptor 的 intercept 方法,下面看这个方法,this.advised 是前面传过来的用户行为,getInterceptorsAndDynamicInterceptionAdviceAdvisor 适配成了 org.aopalliance.intercept.MethodInterceptor 分别对应切面的五种行为

AbstractAspectJAdvice
  |- AspectJAfterReturningAdvice
  |- AspectJAfterAdvice implements org.aopalliance.intercept.MethodInterceptor
  |- AspectJAroundAdvice implements org.aopalliance.intercept.MethodInterceptor
  |- AspectJAfterThrowingAdvice implements org.aopalliance.intercept.MethodInterceptor
  |- AspectJMethodBeforeAdvice

最后它封装一个执行器,根据顺序调用拦截器链,也即用户行为列表,封装执行的时候是强转 org.aopalliance.intercept.MethodInterceptor 来执行的,但 AspectJAfterReturningAdviceAspectJMethodBeforeAdvice 没有实现 org.aopalliance.intercept.MethodInterceptor 怎么办,因此 spring 在获取用户行为链的时候增长了一个适配器,专门用于把这两种转换成 MethodInterceptor

其它说明

  • cglib 的 callback 只能写一个,filter 用于选择是第几个 callback ,不要认为也是链式的

  • spring aop 中有比较多的设计模式,学设计模式的能够看下这块的源码 ,至少责任链,适配器,动态代理均可以在这看到
  • 切面类中若是有两个同样的行为,好比有两个 @Before,排序规则为看方法名的 ascii 码值,只测试过,并没通过源码,有兴趣的能够本身去看一下。

来个示例更容易理解

咱们除了使用 @Aspect 注解把切面规则告诉 spring 外,也能够学自己 aop 的实现,咱们本身定义一个 Advisor ,由于 spring 就是扫描这个的,而后实现 pointcut 和 invoke 方法,同样能够实现 aop 。

联系上文: spring-data-redis-cache 使用及源码走读 咱们来看看 spring 的 redis-cache 是如何作切面的

文章说到,主要工做的类是 CacheInterceptor 它是一个 org.aopalliance.intercept.MethodInterceptor

Advisor 是 BeanFactoryCacheOperationSourceAdvisor 也就是说建立代理类会扫描到这个类,最后执行会把其转成 MethodInterceptor,由于它是一个 PointcutAdvisor ,查看 DefaultAdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice 方法,第一个就是把 PointcutAdvisor 转成 MethodInterceptor 继续进入获取拦截器的方法,能够知道就是获取的 advice 属性 CacheInterceptor

一点小推广

创做不易,但愿能够支持下个人开源软件,及个人小工具,欢迎来 gitee 点星,fork ,提 bug 。

Excel 通用导入导出,支持 Excel 公式
博客地址:https://blog.csdn.net/sanri1993/article/details/100601578
gitee:https://gitee.com/sanri/sanri-excel-poi

使用模板代码 ,从数据库生成代码 ,及一些项目中常常能够用到的小工具
博客地址:https://blog.csdn.net/sanri1993/article/details/98664034
gitee:https://gitee.com/sanri/sanri-tools-maven

相关文章
相关标签/搜索