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

接上一篇《手写Spring---DI依赖注入(2)》继续更新

1、AOP分析

① AOP(Aspect Oriented Programming)是什么?

在不改变类的代码的前提下,对类方法进行功能加强。
复制代码

② 咱们须要达成的目标?

向用户提供咱们的AOP功能让他们来进行对类方法的加强。
复制代码

③ 提取到的信息与基本概念

1.进行功能加强:功能---Advice 通知
2.对类方法加强:可选的加强方法---PointCut 切入点
3.不改原类代码:与原类解耦---Weaving 织入
复制代码

2、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.在框架中已经实现
复制代码

3、Aspect切面的实现

Aspect 分析:html

(1)Advice是由用户提供咱们使用,且是多变的,那咱们如何能认识用户提供的东西, (2)如何让咱们的代码隔绝用户提供的多变?java

可否由咱们提供一套标准接口,用户经过实现接口来提供他们不一样的逻辑
应对变化---面向接口编程(好比JDBC,日志等)
复制代码

此时咱们做为空壳接口来编写便可,做为加强功能的总标识spring

public interface Advice {
}
复制代码

Advice设计

首先围绕Advice的特色3,可选时机这块,它能够进行前置加强,后置加强,环绕加强,异常处理加强,这时咱们须要定义一个标准接口方法,让用户作到实现它就能够进行加强。此时咱们须要考虑的因素有:express

四种加强所需的参数是否同样?编程

(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的特色?---用户指定并多点指定api

咱们须要为用户提供一个东西让他们来灵活指定多个方法点,切入点就是这些点。那问题来了框架

如何来完成对这个切入点的判断呢?

1.指定方法,是否以方法做为描述信息
2.如何指定方法?---XX类的XX方法
3.方法重载如何处理?---加上参数类型
复制代码

此时是否有感受,这些东西恰好就组成了一个完整的方法签名呢!ide

如何作到多点性与灵活性?在一个描述中指定一类类的某些方法

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来写

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

相关文章
相关标签/搜索