上文说到,我们可以在 BeanPostProcessor 中对 bean 的初始化前化做手脚,当时也说了,我完全可以生成一个代理类丢回去。

代理类肯定要为用户做一些事情,不可能像学设计模式的时候创建个代理类,然后简单的在前面打印一句话,后面打印一句话,这叫啥事啊,难怪当时听不懂。最好是这个方法的前后过程可以自户自己定义。

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

所以首先学习下 cglib 和 jdk 的动态代理,我们来模拟下 mybatis 是如何通过接口来实现方法调用的

cglib

目标接口:

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

代理处理类:

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);

jdk

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);
    }
}

真正调用处:

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 生命周期进行拦截

spring 配置切面有两种方式,使用注解和使用配置,当然,现在流行注解的方式,更方便,但不管是配置还是注解,最后都会被解析成 Advisor(InstantiationModelAwarePointcutAdvisorImpl)

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

然后通过 createProxy 创建了代理类,里面有区分 cglib 还是 aop ,下面单拿 cglib 来说

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

它重写的是 MethodInterceptor 的 intercept 方法,下面看这个方法,this.advised 是前面传过来的用户行为,getInterceptorsAndDynamicInterceptionAdvice 通过适配器模式把 Advisor 适配成了 AbstractAspectJAdvice 它的五个实现类如下 ,分别对应切面的五种行为

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 中有比较多的设计模式,学设计模式的可以看下这块的源码 ,至少责任链,适配器,动态代理都可以在这看到

一点小推广

创作不易,希望可以支持下我的开源软件,及我的小工具,欢迎来 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

自己实现 aop 和 spring aop的更多相关文章

  1. AOP及spring AOP的使用

    介绍 AOP是一种概念(思想),并没有设定具体语言的实现. AOP是对oop的一种补充,不是取而代之. 具体思想:定义一个切面,在切面的纵向定义处理方法,处理完成之后,回到横向业务流. 特征 散布于应 ...

  2. 【AOP】Spring AOP基础 + 实践 完整记录

    Spring AOP的基础概念 ============================================================= AOP(Aspect-Oriented Pr ...

  3. 【Spring AOP】Spring AOP之如何通过注解的方式实现各种通知类型的AOP操作进阶篇(3)

    一.切入点表达式的各种类型 切入点表达式的作用:限制连接点的匹配(满足时对应的aspect方法会被执行) 1)execution:用于匹配方法执行连接点.Spring AOP用户可能最经常使用exec ...

  4. 【Spring AOP】Spring AOP之你必须知道的AOP相关概念(1)

    一.什么是AOP AOP(Aspect-oriented Programming)即面向切面编程,是对OOP( Object-oriented Programming)即面向对象编程的一种补充,AOP ...

  5. 【Spring AOP】Spring AOP的使用方式【Q】

    Spring AOP的三种使用方式 经典AOP使用方式 改进XML配置方式 基于注解的方式 第1种方式可以作为理解spring配置AOP的基础,是最原始的配置方式,也体现了spring处理的过程. 使 ...

  6. AOP和spring AOP学习记录

    AOP基本概念的理解 面向切面AOP主要是在编译期或运行时,对程序进行织入,实现代理, 对原代码毫无侵入性,不破坏主要业务逻辑,减少程序的耦合度. 主要应用范围: 日志记录,性能统计,安全控制,事务处 ...

  7. 死磕Spring之AOP篇 - Spring AOP常见面试题

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...

  8. 死磕Spring之AOP篇 - Spring AOP总览

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...

  9. 死磕Spring之AOP篇 - Spring AOP自动代理(一)入口

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...

随机推荐

  1. COGS 2510. 拯救紫萱学姐

    [题目描述] 其实在开考前半个小时题面并不是这样的. 由于明天要考试,同学们要把抽屉里的书都搬空,书很多而且办了走读不能回寝室的学长一眼就看到了回班撩他的学姐,于是就把学姐当学长用♂了:“帮我把这摞书 ...

  2. linux系统下使用宝塔面板安装owncloud常见问题

    在安装owncloud时出现 无法写入“config”目录! 解决方法 在宝塔面板,找到owncloud根目录,点击"权限“设置权限 将权限设置为777,应用到子目录打勾(如下图) 确定后再 ...

  3. python自动化测试三部曲之request+django实现接口测试

    国庆期间准备写三篇博客,介绍和总结下接口测试,由于国庆期间带娃,没有按照计划完成,今天才完成第二篇,惭愧惭愧. 这里我第一篇博客的地址:https://www.cnblogs.com/bainianm ...

  4. 微信小程序开发环境安装以及相关设置配置

    微信小程序开发环境安装以及相关设置配置 一.安装 软件名称:wechat_devtools_1.02.1907232_x64 软件安装地址:https://developers.weixin.qq.c ...

  5. Windows 服务程序(二)

    服务控制管理器 (SCM, Service Control Manager),它在系统启动时自动启动,其主要作用是维护和管理一张服务信息表. OpenSCManager() 介绍:功能:建立了一个到服 ...

  6. Ubuntu 终端中文回显乱码

    参考文章 : http://wiki.ubuntu.org.cn/%E4%BF%AE%E6%94%B9locale 所用 Ubuntu的版本 : 猜想是这样的: 1.字符的编码和显示时,所处的环境不是 ...

  7. luogu P3878 [TJOI2010]分金币

    [返回模拟退火略解] 题目描述 今有 nnn 个数 {ai}\{a_i\}{ai​},把它们分成两堆{X},{Y}\{X\},\{Y\}{X},{Y},求一种分配使得∣∑i∈Xai−∑i∈Yai∣|\ ...

  8. [Luogu3797] 妖梦斩木棒

    题目背景 妖梦是住在白玉楼的半人半灵,拥有使用剑术程度的能力. 题目描述 有一天,妖梦正在练习剑术.地面上摆放了一支非常长的木棒,妖梦把它们切成了等长的n段.现在这个木棒可以看做由三种小段构成,中间的 ...

  9. [BZOJ1415][NOI2005]聪聪与可可

    Description Input 数据的第1行为两个整数N和E,以空格分隔,分别表示森林中的景点数和连接相邻景点的路的条数. 第2行包含两个整数C和M,以空格分隔,分别表示初始时聪聪和可可所在的景点 ...

  10. postman设置环境变量与全局变量

    1.环境变量可以设置多组 设置环境变量 编辑环境变量 2.全局变量只能设置一组 可以在Pre-request Script和Tests中设置全局变量 如:pm.globals.set("na ...