Spring5源码(29)-Pointcut和Advisor以及静态普通方法名匹配切面

简介: Spring5源码(29)-Pointcut和Advisor以及静态普通方法名匹配切面


上一篇我们简单介绍了一下AOP中的一些相关术语、以及Advice接口下的一些增强实现,但是这里会有一个问题,那就是增强方法还会被应用到目标类的所有接口。修改一下上一节的测试类并运行。(本篇很多简介摘自Spring3.X企业应用开发实战,实在想不出来如何去介绍这些概念类的信息。。。)

1.Pointcut概念的引入及简介

@Test
public void test5() {
    // 前置增强
    // 1、实例化bean和增强
    Animal dog = new Dog();
    MyMethodBeforeAdvice advice = new MyMethodBeforeAdvice();
    // 2、创建ProxyFactory并设置代理目标和增强
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.setTarget(dog);
    proxyFactory.addAdvice(advice);
    // 3、生成代理实例
    Animal proxyDog = (Animal) proxyFactory.getProxy();
    proxyDog.sayHello("二哈", 3);
    System.out.println("\n\n");
    proxyDog.sayException("二哈", 3);
}

==前置增强
==方法名:sayHello
==第1参数:二哈
==第2参数:3
==目标类信息:com.lyc.cn.v2.day04.advisor.Dog@65e579dc
==名字:二哈 年龄:3
==前置增强
==方法名:sayException
==第1参数:二哈
==第2参数:3
==目标类信息:com.lyc.cn.v2.day04.advisor.Dog@65e579dc
==名字:二哈 年龄:3
java.lang.ArithmeticException: / by zero
    at com.lyc.cn.v2.day04.advisor.Dog.sayException(Dog.java:17)
    at com.lyc.cn.v2.day04.advisor.Dog$$FastClassBySpringCGLIB$$a974b1ec.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)

从测试结果上看,只要我们调用了Animal类的接口,增强方法都会被应用到目标类的方法上,这样的增强效果肯定不能满足我们实际的应用,那么这个时候就需要引入一个概念----切入点(Pointcut)。通过切入点就可以有选择的将增强应用到目标类的方法上,而目标类的方法就是我们上一节说的连接点,即sayHello和sayException方法,目标类就是要被增强的类,即Dog类,所以增强描述了连接点的方位信息,例如织入到方法之前、方法之后,而切入点则进一步的描述了织入到那些类的那些方法上。到这里相信大家对连接点、切入点、增强、目标对象等概念有了更为深刻的理解。

但是这又带了一个新的问题,那就是如何将切入点定位到连接点,换言之,就是切入点如何知道自己要被应用到那些连接点上呢?

接下来就有必要看一下Pointcut接口的源码了。

  • Pointcut接口

public interface Pointcut {
    /**
     * 返回当前切点匹配的类
     */
    ClassFilter getClassFilter();
    /**
     * 返回当前切点匹配的方法
     */
    MethodMatcher getMethodMatcher();
    /**
     * Canonical Pointcut instance that always matches.
     */
    Pointcut TRUE = TruePointcut.INSTANCE;
}

Pointcut接口的定义非常简单,仅仅包含了ClassFilter和MethodMatcher的定义,ClassFilter可以定位到具体的类上,MethodMatcher可以定位到具体的方法上,这样通过Pointcut我们就可以将将增强织入到特定类的特定方法上了。再来看下ClassFilter和MethodMatcher的定义:

  • ClassFilter接口

public interface ClassFilter {
    /**
     * 切入点应该应用于给定的接口还是目标类
     * Should the pointcut apply to the given interface or target class?
     * @param clazz the candidate target class 候选目标类
     * @return whether the advice should apply to the given target class 增强是否应用于目标类
     */
    boolean matches(Class<?> clazz);
    /**
     * Canonical instance of a ClassFilter that matches all classes.
     */
    ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
  • MethodMatcher接口

public interface MethodMatcher {
    /**
     * 静态方法匹配判断
     */
    boolean matches(Method method, Class<?> targetClass);
    /**
     * 判断静态方法匹配或动态方法匹配
     * true:动态方法匹配
     * false:静态方法匹配
     */
    boolean isRuntime();
    /**
     * 动态方法匹配判断
     */
    boolean matches(Method method, Class<?> targetClass, Object... args);
    /**
     * Canonical instance that matches all methods.
     */
    MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}

虽然还没有看到以上三个接口的具体实现,但是现在我们只要知道Pointcut接口提供了这样的功能就行了。在MethodMatcher接口中又引入了一个新的概念,方法匹配模式,Spring支持两种方法匹配器:

  • 静态方法匹模式:所谓静态方法匹配器,仅对方法名签名(包括方法名和入参类型及顺序)进行匹配。
  • 动态方法匹配器:动态方法匹配器会在运行期方法检查入参的值。 静态匹配仅会判断一次,而动态匹配因为每次调用方法的入参可能不一样,所以每次调用方法都必须判断。

接下来简单介绍一下Spring提供的切点类型:

  • 静态方法切点-->org.springframework.aop.support.StaticMethodMatcherPointcut
    静态方法切点的抽象基类,默认情况下匹配所有的类。最常用的两个子类NameMatchMethodPointcut和 AbstractRegexpMethodPointcut , 前者提供简单字符串匹配方法签名,后者使用正则表达式匹配方法签名。
  • 动态方法切点-->org.springframework.aop.support.DynamicMethodMatcherPointcut
    动态方法切点的抽象基类,默认情况下匹配所有的类
  • 注解切点-->org.springframework.aop.support.annotation.AnnotationMatchingPointcut
  • 表达式切点-->org.springframework.aop.support.ExpressionPointcut
    提供了对AspectJ切点表达式语法的支持
  • 流程切点-->org.springframework.aop.support.ControlFlowPointcut
    该切点是一个比较特殊的节点,它根据程序执行的堆栈信息查看目标方法是否由某一个方法直接或间接发起调用,一次来判断是否为匹配的链接点
  • 复合切点-->org.springframework.aop.support.ComposablePointcut
    该类是为实现创建多个切点而提供的操作类
2.切面简介

由于增强包括横切代码,又包含部分连接点信息(方法前、方法后主方位信息),所以可以仅通过增强类生成一个切面。 但切点仅仅代表目标类连接点的部分信息(类和方法的定位),所以仅有切点无法制作出一个切面,必须结合增强才能制作出切面。Spring使用org.springframework.aop.Advisor接口标识切面概念,一个切面同时包含横切代码和连接点信息。

切面可以分为3类:一般切面、切点切面、引介切面

  • 一般切面Advisor
    org.springframework.aop.Advisor代表一般切面,仅包含一个Advice ,因为Advice包含了横切代码和连接点信息,所以Advice本身一个简单的切面,只不过它代表的横切的连接点是所有目标类的所有方法,因为这个横切面太宽泛,所以一般不会直接使用。
  • 切点切面PointcutAdvisororg.springframework.aop.PointcutAdvisor ,代表具有切点的切面,包括Advice和Pointcut两个类,这样就可以通过类、方法名以及方位等信息灵活的定义切面的连接点,提供更具实用性的切面。PointcutAdvisor主要有6个具体的实现类:
  1. DefaultPointcutAdvisor:最常用的切面类型,它可以通过任意Pointcut和Advice定义一个切面,唯一不支持的就是引介的切面类型,一般可以通过扩展该类实现自定义的切面
  2. NameMatchMethodPointcutAdvisor:通过该类可以定义按方法名定义切点的切面
  3. AspectJExpressionPointcutAdvisor:用于AspectJ切点表达式定义切点的切面
  4. StaticMethodMatcherPointcutAdvisor:静态方法匹配器切点定义的切面,默认情况下匹配所有的的目标类
  5. AspectJPointcutAdvisor:用于AspectJ语法定义切点的切面
  • 引介切面IntroductionAdvisor
    org.springframework.aop.IntroductionAdvisor代表引介切面, 引介切面是对应引介增强的特殊的切面,它应用于类层上面,所以引介切点使用ClassFilter进行定义。
3.静态普通方法名匹配切面

上面已经对切入点、切面做了简介,下面通过几个例子来加深大家的印象。先来看静态普通方法名匹配切面,前面我们介绍切入点 通过ClassFilter可以定位到具体的类上,MethodMatcher可以定位到具体的方法上,那么接下来通过定义一个接口、两个类。并通过实现类中的不同方法来验证我们之前的介绍。

  • 接口和实现类(目标对象)

package com.lyc.cn.v2.day05;
/**
 * @author: LiYanChao
 * @create: 2018-11-04 22:40
 */
public interface Animal {
    void sayHello();
}

package com.lyc.cn.v2.day05;
/**
 * @author: LiYanChao
 * @create: 2018-11-04 22:09
 */
public class Cat implements Animal {
    @Override
    public void sayHello() {
        System.out.println("我是Cat类的sayHello方法。。。");
    }
    public void sayHelloCat() {
        System.out.println("我是一只猫。。。");
    }
}

package com.lyc.cn.v2.day05;
/**
 * @author: LiYanChao
 * @create: 2018-11-04 22:09
 */
public class Dog implements Animal{
    @Override
    public void sayHello() {
        System.out.println("我是Dog类的sayHello方法。。。");
    }
    public void sayHelloDog() {
        System.out.println("我是一只狗。。。");
    }
}
  • 增强(为了演示,这里只实现MethodBeforeAdvice前置增强接口)

package com.lyc.cn.v2.day05;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
 * 前置增强
 * @author: LiYanChao
 * @create: 2018-11-01 21:50
 */
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("==前置增强");
        System.out.println("==方法名:" + method.getName());
        if (null != args && args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                System.out.println("==第" + (i + 1) + "参数:" + args[i]);
            }
        }
        System.out.println("==目标类信息:" + target.toString());
    }
}
  • 切面

package com.lyc.cn.v2.day05;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import java.lang.reflect.Method;
/**
 * 静态普通方法名匹配切面
 * @author: LiYanChao
 * @create: 2018-11-04 22:08
 */
public class MyStaticPointcutAdvisor extends StaticMethodMatcherPointcutAdvisor {
    private static String METHOD_NAME = "sayHello";
    /**
     * 静态方法匹配判断,这里只有方法名为sayHello的,才能被匹配
     */
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        return METHOD_NAME.equals(method.getName());
    }
    /**
     * 覆盖getClassFilter,只匹配Dog类
     * @return
     */
    public ClassFilter getClassFilter() {
        return new ClassFilter() {
            @Override
            public boolean matches(Class<?> clazz) {
                return Dog.class.isAssignableFrom(clazz);
            }
        };
    }
}

StaticMethodMatcherPointcutAdvisor抽象类继承了StaticMethodMatcherPointcut类并实现了PointcutAdvisor接口。在MyStaticPointcutAdvisor类中我们实现了matches静态方法匹配判断,并且只有方法名为sayHello的,才能被匹配;覆盖了getClassFilter方法,并且只匹配Dog类。

  • 测试一
    为了大家能够更便捷的使用测试类,也为了减少大家书写配置文件的负担,我们还是采用编码的形式实现。新建MyTest类并书写测试方法。

@Test
public void test1() {
    // 1、创建目标类、增强、切入点
    Animal animal = new Dog();
    MyMethodBeforeAdvice advice = new MyMethodBeforeAdvice();
    MyStaticPointcutAdvisor advisor = new MyStaticPointcutAdvisor();
    // 2、创建ProxyFactory并设置目标类、增强、切面
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.setTarget(animal);
    // 为切面类提供增强
    advisor.setAdvice(advice);
    proxyFactory.addAdvisor(advisor);
    // 3、生成代理实例
    Dog proxyDog = (Dog) proxyFactory.getProxy();
    proxyDog.sayHelloDog();
    System.out.println("\n\n");
    proxyDog.sayHello();
}

我是一只狗。。。
==前置增强
==方法名:sayHello
==目标类信息:com.lyc.cn.v2.day05.Dog@65e579dc
我是Dog类的sayHello方法。。。

之前我们在代码里配置了,在类一级只匹配Dog类,在方法一级只匹配sayHello方法。运行结果与我们的设置符合。只有Dog类的sayHello被增强了。

  • 测试二

@Test
public void test2() {
    // 1、创建目标类、增强、切入点
    Animal animal = new Cat();
    MyMethodBeforeAdvice advice = new MyMethodBeforeAdvice();
    MyStaticPointcutAdvisor advisor = new MyStaticPointcutAdvisor();
    // 2、创建ProxyFactory并设置目标类、增强、切面
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.setTarget(animal);
    // 为切面类提供增强
    advisor.setAdvice(advice);
    proxyFactory.addAdvisor(advisor);
    // 3、生成代理实例
    Cat proxyDog = (Cat) proxyFactory.getProxy();
    proxyDog.sayHelloCat();
    System.out.println("\n\n");
    proxyDog.sayHello();
}

我是一只猫。。。
我是Cat类的sayHello方法。。。

测试二的结果没有一个方法被增强,虽然在Cat类中也有sayHello方法,但是我们设置的是只匹配Dog类,所以虽然在Cat类中有sayHello方法,但是它也是无法被增强的。

至于其他的切点和切面,这里就不一一演示了,这里特别感谢《Spring3.X企业应用开发实战》这本书,本篇很多介绍均摘自本书,哈哈!

4.总结

本篇主要介绍了切点和切面的概念,并通过实际的例子为大家演示了切点是如何匹配类和方法的。概念性的东西大家只看简介是不行的,需要自己动手写代码,才能更深刻的理解AOP的相关概念,在接下来的源码分析中才不会陷入迷茫。

上一篇和本篇的测试类中,我们都是通过ProxyFactory创建的代理,这样的实现肯定无法满足我们的实际需要,那么接下来的篇幅,我们就要介绍Spring的自动代理机制。




目录
相关文章
|
4月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
140 2
|
2月前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
101 7
|
4月前
|
XML Java 数据库连接
Spring高手之路25——深入解析事务管理的切面本质
本篇文章将带你深入解析Spring事务管理的切面本质,通过AOP手动实现 @Transactional 基本功能,并探讨PlatformTransactionManager的设计和事务拦截器TransactionInterceptor的工作原理,结合时序图详细展示事务管理流程,最后引导分析 @Transactional 的代理机制源码,帮助你全面掌握Spring事务管理。
65 2
Spring高手之路25——深入解析事务管理的切面本质
|
3月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
80 2
|
4月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
99 9
|
5月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
246 5
|
5月前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
116 4
|
10月前
|
安全 Java 应用服务中间件
阿里技术官架构使用总结:Spring+MyBatis源码+Tomcat架构解析等
分享Java技术文以及学习经验也有一段时间了,实际上作为程序员,我们都清楚学习的重要性,毕竟时代在发展,互联网之下,稍有一些落后可能就会被淘汰掉,因此我们需要不断去审视自己,通过学习来让自己得到相应的提升。
|
10月前
|
Java 关系型数据库 数据库连接
Spring源码解析--深入Spring事务原理
本文将带领大家领略Spring事务的风采,Spring事务是我们在日常开发中经常会遇到的,也是各种大小面试中的高频题,希望通过本文,能让大家对Spring事务有个深入的了解,无论开发还是面试,都不会让Spring事务成为拦路虎。
122 1
|
5月前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
183 9