美文网首页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