手写Spring---AOP面向切面编程(3)

2,241 阅读8分钟

接上一篇《手写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来写

比较纯理论,代码不多且简单,更多地还是要理清楚一些概念性的问题.不足之处请在评论处留言...望共同进步,望能在点滴积累中慢慢收获成长