涨知识!Spring AOP还能这么玩,看看你的项目能否用上
创始人
2025-07-10 00:40:46
0

环境:Spring5.3.23

本篇文章将介绍两个主题:

  • 控制流切入点(动态切入点)
  • 引介通知

1. 简介

Spring AOP是Spring框架的一个重要组成部分,它允许开发者定义跨多个模块的横切关注点,例如日志记录、事务管理、安全等。控制流切入和引介通知是Spring AOP中的两个关键特性,它们能够增强程序的可维护性和可读性。本文将深入探讨这两个特性的工作原理和使用方法。

控制流切入

控制流切入允许我们根据方法调用的控制流来定义切入点。控制流切入点与当前调用堆栈匹配。例如,如果连接点被com.pack.service包中的方法或PersonService类调用,它可能会触发。控制流切入点是通过使用org.springframework.aop.support.ControlFlowPointcut类指定的。

引介通知

引介通知能够声明被建议的对象实现给定的接口,并代表这些对象提供该接口的实现。简单说:你有个PersonService类,引介通知能够让你不修改代码的情况下去实现你给定的任意接口(CommonDAO)。

2. 实战案例

2.1 控制流切入点

准备基础类

@Component
public class PersonDAO {
  public void save(String name) {
    System.out.println("PersonDAO save method invoke...") ;
  }
}
@Component
public class PersonService {
  @Resource
  private PersonDAO dao ;
  public void save(String name) {
    System.out.println("PersonService save method inovke...") ;
    this.dao.save(name) ;
  }
}

定义切面类Advisor

低级切面Advisor,平时使用的@Aspect算是高级切面类,而这些高级切面类最终会被转换为Advisor低级切面类。

@Component
public class PackControlFlowAdvisor extends DefaultPointcutAdvisor {
  private static MethodInterceptor logInterceptor = invocation -> {
    System.out.println("before log...") ;
    Object ret = invocation.proceed() ;
    System.out.println("after log...") ;
    return ret ;
  } ;
  // 要进行匹配的类
  private static Class clazz = PersonService.class ;
  // 要进行匹配的方法(可以为null,这样指定类中的所有方法都会被匹配拦截)
  private static String methodName = "save" ;
  private static ControlFlowPointcut pointcut = new ControlFlowPointcut(clazz, methodName) ;
  public PackControlFlowAdvisor() {
    super(pointcut, logInterceptor) ;
  }
}

测试

PersonService ps = context.getBean(PersonService.class) ;
ps.save("王五") ;

控制台输出

PersonService save method inovke...
before log...
PersonDAO save method invoke...
after log...

PersonDAO中的save方法被拦截了。什么意思?怎么PersonDAO就被拦截了,先来看上面切点的定义ControlFlowPointcut

public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher {
  public boolean matches(Class clazz) {
    return true;
  }
  public boolean matches(Method method, Class targetClass) {
    return true;
  }
  public boolean isRuntime() {
    return true;
  }
  public boolean matches(Method method, Class targetClass, Object... args) {
    // 取得当前线程的整个执行栈(方法的调用)
    for (StackTraceElement element : new Throwable().getStackTrace()) {
      if (element.getClassName().equals(this.clazz.getName()) &&
          (this.methodName == null || element.getMethodName().equals(this.methodName))) {
        return true;
      }
    }
    return false;
  }
}

通过在这个切点类能知道:

  1. 当前容器中的所有类都会被代理;因为这里的类匹配直接返回true,2个参数的matches直接返回true,最后isRuntime返回true,最终执行3个参数的matches方法。
  2. 每个类中方法的调用都会获取当前执行的栈,都会进行判断类及方法是否被匹配。

结合上面的测试输出结果,PersonDAO#save方法被拦截了,因为它符合匹配条件,在PersonService#save方法中调用了PersonDAO#save方法,那PersonDAO#save方法执行栈中就包含了PersonService#save正好匹配了我们定义的切点。

简单说:某个类中的某个方法调用时会判断当前整个执行栈中是否有设定好的类及方法,如果有则拦截当前的方法(执行通知)。

注意:控制流切入点比正常切入点慢10-15倍,但在某些情况下它们是有用的。所以大家还是慎重使用吧,毕竟所有的类都被代理了(当然这里我们可以自定义matches来控制)。

2.2 引介通知

引介通知相对比较简单直接可以在@Aspect切面类中定义

注备基础类

// 这个接口是我们准备让其它类实现的
public interface CommonManager {
  void calc(int a, int b) ;
}
// 默认实现
public class DefaultCommonManager implements CommonManager {
  @Override
  public void calc(int a, int b) {
    System.out.printf("计算a + b = %d%n", (a + b)) ;
  }
}
// 该类是我们将要通过引介增强让其实现CommonManager类
@Component("us")
public class UserService {
  public void save() {
    System.out.println("UserService save...") ;
  }
}

切面类

@Aspect
public static class CommonAspect {
  /**
   * 这样声明后,匹配的类就会自动的实现这里指定的CommonManager接口,默认的实现类是使用DefaultCommonManager
   * value:该值决定了哪些类会被增强(实现指定的CommonManager接口)
   */
  @DeclareParents(value = "com.pack.main.aop_introductionadviser.IntructionDeclareMain2.*+", defaultImpl = DefaultCommonManager.class)
  public static CommonManager mixin;
}

注意:在这个切面类中我们并没有定义@Before,@Around等同志。

测试

CommonManager c = (CommonManager) context.getBean("us") ;
c.calc(10, 20) ;

控制台输出

计算a + b = 30

UserService能正确的转换为CommonManager类,这说明UserService生成的代理类实现了CommonManager接口类,同时在执行方法调用的时候使用的是我们制定的默认实现类DefaultCommonManager。

总结:控制流切入点(ControlFlowPointcut)和引介通知(@DeclareParents)是Spring AOP的两个重要概念。控制流切入点用于在特定的控制流条件下切入代码,而引介通知则让目标类具有更加强大的能力。

相关内容

热门资讯

如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...
如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
施耐德电气数据中心整体解决方案... 近日,全球能效管理专家施耐德电气正式启动大型体验活动“能效中国行——2012卡车巡展”,作为该活动的...
Windows恶意软件20年“... 在Windows的早期年代,病毒游走于系统之间,偶尔删除文件(但被删除的文件几乎都是可恢复的),并弹...
规避非法攻击 用好路由器远程管... 单位在市区不同位置设立了科技服务点,每一个服务点的员工都通过宽带路由器进行共享上网,和单位网络保持联...
20个非常棒的扁平设计免费资源 Apple设备的平面图标PSD免费平板UI 平板UI套件24平图标Freen平板UI套件PSD径向平...
范例解读VB.NET获取环境变... VB.NET编程语言的使用范围非常广泛,可以帮助开发人员处理各种程序中的需求,而且还能对移动设备进行...
德国电信门户网站可实时显示全球... 德国电信周三推出一个门户网站,直观地实时提供其安装在全球各地的传感器网络检测到的网络攻击状况。该网站...