点击上方“Java基基”,选择“设为星标”
做积极的人,而不是积极废人!
每天 14:00 更新文章,每天掉亿点点头发...
源码精品专栏
学习一个模块的设计主要是看接口设计,通过接口设计我们就能够从整体知道模块怎么实现的,具体实现就是组装这些接口来进行实现的,知道了模块接口设计,实现也就变得很简单了。
本文主要从aop背景出发点,来自己去想需要哪些接口,就能够描述一个模块的功能设计规则。
AOP产生背景
使用面向对象编程 ( OOP )有一些弊端,当需要为多个不具有继承关系的对象引人同一个公共行为时,例如日志、安全检测等,我们只有在每个对象里引用公共行为,这样程序中就产生了大量的重复代码,程序就不便于维护了。
所以就有了一个对面向对象编程的补充,即面向方面编程 ( AOP ), AOP 所关注的方向是横向的,区别于 OOP 的纵向。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能。
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
什么是AOP
什么是面向方面编程,3个过程:
找到横切点:首要目标确定在程序的哪个位置进行横切逻辑
横切逻辑(业务代码):横切逻辑代码,这个就是横切业务代码,与aop无关
织入:将横切逻辑织入到横切点
开发者主要关心的是横切逻辑的编写,只需要很少的代码编写确定横切点有哪些,而不需要去为每个横切点添加横切逻辑,不然就是面向对象编程了。
既然是横向的编程,那么在我们的程序中,哪些可以作为横线切入点呢?
看下示例代码:
public class Test {
public static void main(String[] args) {
//@1
B b = new B();
//@2
b.method();
//@3
B.say();
}
static class B {
//字段
//@4
private String name;
//构造方法
public B() {
//@1.1
}
//对象方法
public void method(){
//@2.2
}
//静态方法
static void say(){
//@3.3
}
}
}
所以我们可以将横切点主要分为两大类:字段、方法。方法又分为很多种,
![c9bb85da2f69c1cfd7c0733a70b2bb64.png](https://i-blog.csdnimg.cn/blog_migrate/6b087a0283f0b6342c6b0e764c704256.png)
横切点有很多地方,从代码上看得见的,有如下几个地方:
使用构造函数创建对象
构造函数执行
对象方法调用
对象方法执行
静态方法调用
静态方法执行
反射读写对象字段
目标1:找到横切点
那么怎么去定义一个横切点呢?怎么用一个接口来描述一个横切点呢?
在Java中,一切皆对象,在Java中一个类有2方面内容:字段、方法(构造函数、对象方法、静态方法),java中使用AccessibleObject来抽象公共行为。方法:就是一段可以执行的程序,一段代码。
所以在横切点接口中,首先一个功能就是返回给用户当前横切点,有两种情况:
如果横切点作用于对象(对象字段、对象方法、构造函数),则不仅需要返回AccessibleObject,还需要返回当前对象,因为调用通过反射调用对象方法需要传入当前对象。
如果横切点作用于类,则仅返回AccessibleObject即可。
另一个接口功能就是要不要考虑在横切点来控制多个横切逻辑的调用。这个可以有框架支持,也可以由横切点控制。这对应的就是责任链模式的API设计。比如tomcat中的Filter链式调用就是以集合形式调用;netty中的Handler组织就是以链表形式。如果是以集合形式调用,则在横切点接口需要定义一个方法来链式调用。(aop联盟的JoinPoint采用是集合形式调用)
那么AOP联盟使用JointPoint接口来定义横切点。
public interface Joinpoint {
Object proceed() throws Throwable;
Object getThis();
AccessibleObject getStaticPart();
}
Object proceed() throws Throwable
: 链式调用横切点
Object getThis();
返回连接点当前对象。如果当前连接点是静态,比如静态方法,则该方法返回null,因为反射不需要对象,而且静态方法是通过类调用的,压根就没有对象,所以返回null。spring aop不支持静态方法的拦截,所以在spring中这里返回的就是目标对象(被代理对象)
AccessibleObject getStaticPart();
返回连接点静态部分,对于连接点是方法,返回的就是Method对象。
现在对连接点的设计比较清晰了,然后就是对连接点的扩展了,比如可执行程序(构造方法、Method)的子接口,字段的子接口(aop联盟没有定义,只有方法级别的)。
AOP联盟对连接点接口的设计:
![a1392d52a2de3c4744bb042c03d542c2.png](https://i-blog.csdnimg.cn/blog_migrate/b410b96fcbc038b7567a1f55e056168e.png)
比如在MethodInvocation,就是返回Method。
目标2:横切逻辑(增强)抽象定义
增强的抽象,其实就需要连接点信息,毕竟增强是要投入到一个地方的,所以需要连接点信息。
在aop联盟的接口定义:
![6784b7b762abb344a01cfb62178f351e.png](https://i-blog.csdnimg.cn/blog_migrate/83126d18627f84326b0dd5a924e7aeb5.png)
Advice作为一个tag标识,在aop联盟中使用拦截器来作为增强的命名,这里完全可以去掉Interceptor,而直接定义一个MethodAdvice。之所以定义为Interceptor,是因为拦截器命名更符合编程命名规范,让人从命名就知道接口功能。
在MethodInterceptor,传入连接点信息(因为是方法拦截,所以这里是方法级别的连接点接口定义)
Object invoke(MethodInvocation invocation) throws Throwable;
目标3:织入
首先就是怎么织入。织入由两种方案。
静态织入:采用自定义类加载器机制。自定义类加载器根据织入规则在加载class文件期间对class文件动手织入横切逻辑,然后将改动后的class文件交给JVM运行。
动态织入:由多种选择,动态代理(JDK Proxy)、动态字节码生成技术(cglib)
spring采用动态织入。动态织入就是生成代理对象,代理对象中维护了当前连接点所有拦截器,然后调用目标方法时被代理类拦截,在代理类中作aop功能。
来一个完整的流程图:
![cf37910549b7bc39e0067237460a9a86.png](https://i-blog.csdnimg.cn/blog_migrate/850fe7c12b6f1c81a3814ebdbe9d5230.png)
Spring AOP的实现基于AOP联盟接口标准设计实现的,全局看下aopalliance有哪些接口以及接口的API设计,我们上面已经分析完了。
AOP联盟的接口很少:
![9a9bc007a06e00c5f6cc4ca0c8c3f426.png](https://i-blog.csdnimg.cn/blog_migrate/b55711ea8c51fd1659394f36bef36009.png)
欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:
已在知识星球更新源码解析如下:
最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。
提供近 3W 行代码的 SpringBoot 示例,以及超 6W 行代码的电商微服务项目。
获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)