手写Spring---AOP面向切面编程(3)
接上一篇《手写Spring---DI依赖注入(2)》继续更新
一、AOP分析
① AOP(Aspect Oriented Programming)是什么?
在不改变类的代码的前提下,对类方法进行功能增强。
② 我们需要达成的目标?
向用户提供我们的AOP功能让他们来进行对类方法的增强。
③ 提取到的信息与基本概念
1.进行功能增强:功能---Advice 通知
2.对类方法增强:可选的增强方法---PointCut 切入点
3.不改原类代码:与原类解耦---Weaving 织入
二、AOP概念
① 请理解下方图片:

Advice:通知,增强的功能
Join Point:连接点,可选的方法点
Pointcut:切入点,选择切入的方法点
Aspect:切面,选择的(多个)方法点+增强的功能
Introduction:引入,添加新的方法或属性到已存在的类中
Weaving:织入,不改变原类的代码进行增强
② 以上提到的概念哪些是需要用户提供,又哪些需要我们完成
其中 Advice, Join Point,Pointcut,Aspect 都是用户提供我们使用
Weaving 需要我们自己实现
Introduction 在spring中存在但是很少涉及
ps:提供者和使用者的概念在设计模式中经常涉及
③ Advice,Pointcut,Weaving各自有什么特点?
Advice:
1.由用户提供增强功能逻辑代码
2.不同的增强需求会存在不同的逻辑
3.可选时机,在方法前后或异常时进行增强
4.同一个切入点,可能存在多个增强
Pointcut
1.用户指定切入点
2.用户可在多点进行增强
Weaving
1.不改变原类代码
2.在框架中已经实现
三、Aspect切面的实现
Aspect 分析:
(1)Advice是由用户提供我们使用,且是多变的,那我们如何能认识用户提供的东西, (2)如何让我们的代码隔绝用户提供的多变?
能否由我们提供一套标准接口,用户通过实现接口来提供他们不同的逻辑
应对变化---面向接口编程(比如JDBC,日志等)
此时我们作为空壳接口来编写即可,作为增强功能的总标识
public interface Advice {
}
Advice设计
首先围绕Advice的特点3,可选时机这块,它可以进行前置增强,后置增强,环绕增强,异常处理增强,这时我们需要定义一个标准接口方法,让用户做到实现它就可以进行增强。此时我们需要考虑的因素有:
四种增强所需的参数是否一样?
(1)Advice的前置增强
Q1:前置增强可能需要的参数
目的是对方法进行增强,应该需要提供的是方法相关的信息,我们也仅能提供有关方法的信息
其中方法包含的信息有:
1.方法本身:Method
2.方法所属对象:Object
3.方法的参数:Object[]
Q2:前置增强方法的返回值
在方法执行前进行增强,不需要任何的返回值
(2)Advice的后置增强
Q1:后置增强所需要的参数
1.方法本身:Method
2.方法所属对象:Object
3.方法的参数:Object[]
4.方法的返回值:Object
Q2:后置增强的方法返回值?
方法执行后进行增强也不需要返回值
(3)Advice的环绕增强
Q1:环绕增强所需要的参数
1.方法本身:Method
2.方法所属对象:Object
3.方法的参数:Object[]
Q2:环绕增强的方法返回值?
方法被它包裹,也就是本身类方法的执行需要这个方法的执行逻辑来带动执行,
所以它需要返回方法的返回值Object
(4)Advice的异常处理增强
Q1:异常处理增强所需的参数
异常信息
Q2:异常处理增强的返回值?
已经异常了···
Q3:进行异常处理增强需要包裹方法吗?
需要的,正常来说是使用try-catch来根据不同的异常来进行不同的处理,
就是在不同的catch中进行不同的增强处理,那其实就是可以借助环绕增强的效果来实现
(5)接口设计
经过前面的分析,我们已经可以总结出我们所需要的三个方法
Q1:三个方法是一个接口中定义还是分开三个接口更好?
分三个接口,此时还可以通过类型来区分不同的Advice
MethodBeforeAdvice.java
public interface MethodBeforeAdvice extends Advice{
/**
* 实现方法的前置增强
*
* @param method 被增强的方法
* @param args 方法的参数
* @param target 被增强的目标对象
* @throws Throwable
*/
void before(Method method,Object[] args,Object target) throws Throwable;
}
AfterReturnAdvice.java
public interface AfterReturnAdvice extends Advice {
/**
* 实现方法的后置增强
*
* @param returnValue 返回值
* @param method 被增强的方法
* @param args 方法的参数
* @param target 方法的目标对象
* @throws Throwable
*/
void afterReturn(Object returnValue, Method method,Object[] args,Object target) throws Throwable;
}
MethodSurroudAdvice.java
public interface MethodSurroudAdvice extends Advice {
/**
* 对方法进行环绕增强还有异常处理的增强
*
* @param method
* @param args
* @param target
* @return
*/
Object invoke(Method method,Object[] args,Object target);
}
Pointcut的分析
Pointcut的特点?---用户指定并多点指定
我们需要为用户提供一个东西让他们来灵活指定多个方法点,切入点就是这些点。那问题来了
如何来完成对这个切入点的判断呢?
1.指定方法,是否以方法作为描述信息
2.如何指定方法?---XX类的XX方法
3.方法重载如何处理?---加上参数类型
此时是否有感觉,这些东西刚好就组成了一个完整的方法签名呢!
如何做到多点性与灵活性?在一个描述中指定一类类的某些方法
ps:一类类的某些方法,比如说,某个包或者某个类的所有方法,所有类中do开头的方法,所有类中带有service的方法等等
我们需要一个表达式来描述这些信息
包名:有父子特点,要能模糊匹配
类名:模糊匹配
方法名与参数类型:模糊匹配,参数可以多个
我们常见的表达式,比如正则(其实也是可行),Ant Path,
AspectJ里面的pointcut(首选,因为AspectJ本身就是面向切面编程的组件)
补充:AspectJ的语法
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
通过spring的官网可以进行学习: docs.spring.io/spring/docs…
Pointcut的设计分析
① 切点Pointcut应该具备哪些属性?
切点定义表达式
② 切点Pointcut应该对外提供什么行为?
③ 我们如何来使用切点Pointcut?
对类,方法进行匹配
切点应提供匹配类,匹配方法的行为
④ 如果在我们的设计框架中要能灵活扩展切点的实现方式,我们该如何设计?
支持可变性问题需要由我们来定义一个标准接口,定义好基本行为,面向接口编程
屏蔽掉具体的实现.(像实现Advice一样)
所以我们设计一个Pointcut的标准接口
public interface Pointcut {
//提供两个方法,匹配类和匹配方法
boolean matchClass(Class<?> targetClass);
boolean matchMethod(Method method,Class<?> targetClass);
}
⑤ 基于AspectJ的pointcut实现---AspectJExpressionPointcut.java
public class AspectJExpressionPointcut implements Pointcut{
private String expression;
public AspectJExpressionPointcut(String expression){
this.expression = expression;
}
public String getExpression(){
return this.expression;
}
@Override
public boolean matchClass(Class<?> targetClass) {
return false;
}
@Override
public boolean matchMethod(Method method, Class<?> targetClass) {
return false;
}
}
此时我们完成了一个空壳实现,还需引入AspectJ的jar包来完成切点表达式的实现,Spring AOP也仅仅使用了AspectJ的表达式api
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
大致理解下AspectJ的简单应用,我们应该执行的步骤是:
(1) 获得切点解释器 org.aspectj.weaver.tools.PointcutParser
PointcutParser pp = PointcutParser
.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();
(2) 解析表达式,得到org.aspectj.weaver.tools.PointcutExpression
PointcutExpression pe =
pp.parsePointcutExpression(expression)
(3) 用PointcutExpression匹配类是不可靠的,所以需要匹配方法
pe.couldMatchJoinPointsInType(targetClass)为匹配类的方法,但有匹配不准确的问题
所以我们需要匹配方法的api
pe.matchesMethodExecution(method)
然后使用ShadowMatch类中的alwaysMatches()方法即可
⑥ 加入AspectJ的api后的实现
public class AspectJExpressionPointcut implements Pointcut{
//得到了一个全局的切点解析器
private static PointcutParser pp = PointcutParser
.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();
private String expression;
private PointcutExpression pe;
public AspectJExpressionPointcut(String expression) {
super();
this.expression = expression;
//解析成对应的表达式对象
pe = pp.parsePointcutExpression(expression);
}
@Override
public boolean matchClass(Class<?> targetClass) {
return pe.couldMatchJoinPointsInType(targetClass);
}
@Override
public boolean matchMethod(Method method, Class<?> targetClass) {
ShadowMatch sm = pe.matchesMethodExecution(method);
return sm.alwaysMatches();
}
public String getExpression() {
return expression;
}
}
Aspect 的设计
为了给用户提供操作优化,我们设计一个Advisor把Advice和Pointcut组合起来,当用户使用aspectJ来指定他的切入点时,就只需指定adviceBeanName,expression即可
① 通用Advisor
public interface Advisor {
String getAdviceBeanName();
String getExpression();
}
② 基于AspectJ语法的切面实现
public class AspectJPointcutAdvisor implements Advisor{
private String adviceBeanName;
private String expression;
private AspectJExpressionPointcut pointcut;
public AspectJPointcutAdvisor(String adviceBeanName, String expression) {
super();
this.adviceBeanName = adviceBeanName;
this.expression = expression;
this.pointcut = new AspectJExpressionPointcut(this.expression);
}
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public String getAdviceBeanName() {
return this.adviceBeanName;
}
@Override
public String getExpression() {
return this.expression;
}
}
由于AOP的内容比较多而上一篇DI的篇幅过长的问题,所以分2P来写
比较纯理论,代码不多且简单,更多地还是要理清楚一些概念性的问题.不足之处请在评论处留言...望共同进步,望能在点滴积累中慢慢收获成长
转载自:https://juejin.cn/post/6844903830883139597