美文网首页IT@程序员猿媛
【Spring 笔记】AOP 基础相关整理

【Spring 笔记】AOP 基础相关整理

作者: 58bc06151329 | 来源:发表于2020-02-11 20:39 被阅读0次

文前说明

作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。

本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。

1. 概述

  • AOP 全称是 Aspect Oriented Programming,即面向切面的编程,是一种开发理念,在程序开发中主要用于解决一些系统层面上的问题。
    • 通过 AOP 可以把一些非业务逻辑的代码,比如安全检查,监控等代码从业务方法中抽取出来,以非侵入的方式与原方法进行协同。
    • 这样可以使原方法更专注于业务逻辑,代码结构会更加清晰,便于维护。
    • spring-aop 提供了符合 aop 联盟规范的面向切面的编程实现,可以定义如方法拦截器和切入点,从逻辑上讲,可以减弱代码的功能耦合,清晰地被分离开。
      • 利用源码级地元数据功能,还可以将各种行为信息合并到代码中。
    • spring-aspects 集成了 AspectJ,这是一个功能强大且成熟的面向方面编程(AOP)框架。

2. 原理

  • 通过代理模式为目标对象生产代理对象,并将横切逻辑插入到目标方法执行的前后。

2.1 Joinpoint(连接点)

  • 连接点是指程序执行过程中的一些点,比如方法调用,异常处理等。
  • Spring AOP 中,仅支持方法级别的连接点。
  • 如下例所示,每个方法调用都是一个连接点。
// TestService.java
public interface TestService {

    void save(String name);

    void update(String name);

    void delete(String name);

    String findOne(String name);

    List<String> findAll();
}

// TestServiceImpl.java
public class TestServiceImpl implements TestService {

    public void save(String name) {// 连接点
    }

    public void update(String name) {// 连接点
    }

    public void delete(String name) {// 连接点
    }

    public String findOne(String name) {// 连接点
        System.out.println("TestServiceImpl`s findOne");
        return "";
    }

    public List<String> findAll() {// 连接点
        System.out.println("TestServiceImpl`s findAll");
        return new ArrayList<String>();
    }
}
  • Joinpoint 接口中,proceed() 方法用于执行拦截器逻辑。
    • 前置通知拦截器 为例,在执行目标方法前,该拦截器首先会执行前置通知逻辑,如果拦截器链中还有其他的拦截器,则继续调用下一个拦截器逻辑,直到拦截器链中没有其他的拦截器后,再去调用目标方法。
// Joinpoint.java
public interface Joinpoint {

    // 用于执行拦截器链中的下一个拦截器逻辑
    Object proceed() throws Throwable;

    Object getThis();

    AccessibleObject getStaticPart();

}
  • 一个方法调用是一个连接点,方法调用 接口的定义如下。
// Invocation.java
public interface Invocation extends Joinpoint {
    Object[] getArguments();
}

// MethodInvocation.java
public interface MethodInvocation extends Invocation {
    Method getMethod();
}
  • 方法调用接口 MethodInvocation 继承自 InvocationInvocation 接口又继承自 Joinpoint

2.2 Pointcut(切点)

  • 切点的作用在于选择连接点。
// Pointcut.java
public interface Pointcut {

    // 返回一个类型过滤器
    ClassFilter getClassFilter();

    // 返回一个方法匹配器
    MethodMatcher getMethodMatcher();

    Pointcut TRUE = TruePointcut.INSTANCE;
}
  • Pointcut 接口中定义了两个接口,分别用于返回类型过滤器和方法匹配器。
  • 类型过滤器方法匹配器 接口的定义如下。
// ClassFilter.java
public interface ClassFilter {
    boolean matches(Class<?> clazz);
    ClassFilter TRUE = TrueClassFilter.INSTANCE;

}

// MethodMatcher.java
public interface MethodMatcher {
    boolean matches(Method method, Class<?> targetClass);
    boolean matches(Method method, Class<?> targetClass, Object... args);
    boolean isRuntime();
    MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
  • 两个接口均定义了 matches() 方法,只需要实现该方法,即可对连接点进行选择。
  • Spring Aspects 中使用 AspectJ 表达式对连接点进行选择,提供了一个 AspectJ 表达式的切点类 AspectJExpressionPointcut
AspectJExpressionPointcut 体系
  • AspectJExpressionPointcut 实现了 PointcutClassFilterMethodMatcher 接口,具备了通过 AspectJ 表达式对连接点进行选择的能力。
  • 对 <2.1> 中的方法连接点用例使用 execution(* *.find*(..)) 表达式,则选择出以 find 开头的 findOne()findAll() 两个方法。

2.3 Advice(通知)

  • Advice(通知)即定义的 横切逻辑,比如可以定义一个用于监控方法性能的通知,还可以定义一个用于安全检查的通知等。
  • 切点解决了在 何处 调用通知的问题,Spring 中定义了几种通知类型,解决 何时 调用通知的问题。
通知类型 何时调用
前置通知(Before advice) 在目标方便调用前执行通知。
后置通知(After advice) 在目标方法完成后执行通知。
返回通知(After returning advice) 在目标方法执行成功后,调用通知。
异常通知(After throwing advice) 在目标方法抛出异常后,执行通知。
环绕通知(Around advice) 在目标方法调用前后均可执行自定义逻辑。
// Advice.java
public interface Advice {
}

// BeforeAdvice.java
public interface BeforeAdvice extends Advice {

}

// MethodBeforeAdvice.java
public interface MethodBeforeAdvice extends BeforeAdvice {

    void before(Method method, Object[] args, Object target) throws Throwable;
}

// AfterAdvice.java
public interface AfterAdvice extends Advice {

}

// AfterReturningAdvice.java
public interface AfterReturningAdvice extends AfterAdvice {

    void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
}
  • 以实现类 AspectJMethodBeforeAdvice 为例。
AspectJMethodBeforeAdvice 体系

2.4 Aspect(切面)

  • 虽然有了切点和通知,但是还需要将它们整合起来,这样切点才可以为通知进行导航,然后精确的执行通知逻辑。
  • Pointcut(切点)解决了 where 问题,通知(Advice)解决了 when 和 how 问题,Aspect(切面)整合了切点和通知两个模块,从而解决了 何处方法(where)在何时(when 前置、后置、环绕等)执行如何(how)的横切逻辑 的问题。
  • AOP 中,切面只是一个概念,并没有一个具体的接口或类与此对应,不过 PointcutAdvisor 切点通知器接口与切面功能类似,该接口既可以返回切点,也可以返回通知。
// Advisor.java
public interface Advisor {

    Advice getAdvice();
    boolean isPerInstance();
}
// PointcutAdvisor.java
public interface PointcutAdvisor extends Advisor {

    Pointcut getPointcut();
}
  • 切面和 PointcutAdvisor 存在的差异是,在一个切面中,一个切点对应多个通知,是一对多的关系(也可以配置多个 Pointcut,形成多对多的关系)如下例配置,而 PointcutAdvisor 的实现类中,切点和通知是一一对应的关系。

  • 用例

// TestService.java
public interface TestService {

    void save(String name);

    void update(String name);

    void delete(String name);

    String findOne(String name);

    List<String> findAll();
}

// TestServiceImpl.java
public class TestServiceImpl implements TestService {

    public void save(String name) {
    }

    public void update(String name) {
    }

    public void delete(String name) {
    }

    public String findOne(String name) {
        System.out.println("TestServiceImpl`s findOne");
        return "";
    }

    public List<String> findAll() {
        System.out.println("TestServiceImpl`s findAll");
        return new ArrayList<String>();
    }
}

// AopTest.java
public class AopTest {

    public void before() {
        System.out.println("AopTest`s before");
    }

    public void after() {
        System.out.println("AopTest`s after");
    }
}

// Test.java
public class Test {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        TestService testService = (TestService) context.getBean("testServiceImpl");
        testService.findAll();
    }
}

/* print
AopTest`s before
TestServiceImpl`s findAll
AopTest`s after
*/
  • Spring 配置文件
<!-- spring.xml -->
<aop:aspectj-autoproxy/>
<bean id="testServiceImpl" class="spring.test.aop.TestServiceImpl"/>
<bean id="aopTest" class="spring.test.aop.AopTest"/>
    <aop:config expose-proxy="true">
        <aop:aspect ref="aopTest">
            <!-- pointcut -->
            <aop:pointcut id="testPointcut" expression="execution(* spring.test.aop.*.find*(..))" />

            <!-- advoce -->
            <aop:before method="before" pointcut-ref="testPointcut"/>
            <aop:after method="after" pointcut-ref="testPointcut"/>
        </aop:aspect>
    </aop:config>
  • 切面中配置了一个切点和两个通知,两个通知均引用了同一个切点,即 pointcut-ref=“testPointcut”,该配置最终会被转换成两个 PointcutAdvisor
转换成两个 PointcutAdvisor

2.5 Weaving(织入)

  • 织入是在切点的引导下,将通知逻辑插入到方法调用上,使得通知逻辑在方法调用时得以执行。
  • Spring 是通过 后置处理器 BeanPostProcessor 接口 实现的。
    • 该接口是 Spring 提供的一个拓展接口,通过实现该接口,可在 bean 初始化前后做一些自定义操作。
    • 在 bean 初始化完成后,即 bean 执行完初始化方法(init-method),Spring 进行织入操作。
    • Spring 通过切点对 bean 类中的方法进行匹配。若匹配成功,则为该 bean 生成代理对象,并将代理对象返回给容器。容器向后置处理器输入 bean 对象,得到 bean 对象的代理,这样就完成了织入的过程。

相关文章

网友评论

    本文标题:【Spring 笔记】AOP 基础相关整理

    本文链接:https://www.haomeiwen.com/subject/umtffhtx.html