当心!SpringBoot在这几种情况下将导致代理失效

开发 前端
Pointcut定义了哪些方法会被增强,而切点通常通过表达式来定义,这些表达式可以基于方法名、参数类型、注解等多种条件。

环境:SpringBoot2.7.18

1. 简介

Spring AOP(面向切面编程)是Spring框架的核心特性之一,它以一种非侵入式的方式增强了应用程序的模块性和可维护性。通过AOP,开发者能够将横切关注点(如日志记录、事务管理、安全控制等)从业务逻辑中分离出来,形成独立的切面,从而实现了关注点的模块化。这种分离不仅简化了代码结构,还提高了代码的重用性和灵活性。Spring AOP利用代理机制在运行时动态地将切面织入到目标对象中,无需修改原有代码,极大降低了系统间的耦合度。

实现代理的核心元素

  • 切入点

Pointcut定义了哪些方法会被增强,而切点通常通过表达式来定义,这些表达式可以基于方法名、参数类型、注解等多种条件。

  • 通知类

通知则是你需要增强的逻辑,这其中包括了前置通知(Before Advice)、后置通知(After Advice)、环绕通知(Around Advice)、异常通知(Throws Advice)和引介通知(Introduction Advice)。

  • 处理器

有了上面2个关键元素后,那如何才能创建代理呢?这时候的BeanPostProcessor就是最为关键的类了,它会根据切入点来判断你当前的bean是否符合条件,对于符合条件的则进行代理的创建最终返回给Spring容器。Spring容器中保存的是代理对象。而在Spring中我们最常见的几种注册处理器的方式是:通过下面3个注解

@Configuration
// 开启事务(针对的事务注解@Transactional)
@EnableTransactionManagement
// 开启AOP代理(只要具备上面的1,2条件即可)
@EnableAspectJAutoProxy
// 开启异步支持(针对的是@Async注解)
@EnableAsync
public class AppConfig {}

具备了上面3个核心元素后,是否就一定能为bean对象创建代理呢?这将是接下来要介绍的内容。

2. 不创建代理情况

2.1 环境准备

先准备基础环境进行接下来的测试使用

@Service
public class Service {
  public void save() {
    System.out.println("Service save...") ;
  }
}

将围绕该Service创建代理

@Component
@Aspect
public class LogAspect {
  @Pointcut("execution(* com.pack..*.*(..))")
  private void log() {
  }
  @Before("log()")
  public void recordLog() {
    System.out.println("before log...") ;
  }
}

该切面定义了一个前置通知,切入点匹配com.pack包及其子包下的所有方法。

2.2 正常创建代理

到此,以上定义没有任何特殊的程序能正常的创建代理,如下示例:

ConfigurableApplicationContext context = SpringApplication.run(App.class, args) ;
Service service = context.getBean(Service.class);
System.out.println(service.getClass()) ;
service.save();

输出结果

class com.pack.Service$$SpringCGLIB$$0
before log...
Service save ...

正常通过cglib创建代理对象。

2.3 不创建代理

  • Service实现Advice接口
public class Service implements Advice {}

再次运行后,输出结果

class com.pack.Service
Service save...

没有创建代理,没有执行通知方法。

  • Service实现Pointcut接口
public class Service implements Pointcut {


  // 该接口需要实现下面2个方法
  // 这里无所谓,默认实现即可
  public ClassFilter getClassFilter() {
    return null;
  }
  public MethodMatcher getMethodMatcher() {
    return null;
  }
}

输出结果:

class com.pack.Service
Service save...

同样,没有创建代理:

  • Service实现AopInfrastructureBean接口
public class Service implements AopInfrastructureBean {}

该接口没有任何方法标记接口基础设施类,输出结果

class com.pack.Service
Service save...

没有创建代理

  • Service实现Advisor接口

Spring创建代理对象,底层实现即使你通过注解@Aspect方式声明的切面都会将其转换为Advisor这种低级切面。

Advisor接口只有一个抽象方法。

public class Service implements Advisor {
  // 空实现即可
  public Advice getAdvice() {
    return null ;
  }
}

输出结果与上面一样,同样不会创建代理。

  • 特殊的beanName

给Service一个特殊的beanName。

@Component("com.pack.Service.ORIGINAL")
public class Service {}

这个beanName以当前的完整包名+类名+.ORIGINAL命名,输出结果:

class com.pack.Service
Service save...

没有创建代理,修改beanName:

@Component("xxxooo.ORIGINAL")

当修改成上面的名称后,再次运行:

class com.pack.Service$$SpringCGLIB$$0
before log...
Service save ...

被代理了,这说明beanName只有是"完整包名+类名+.ORIGINAL"才不会创建代理对象。

  • 特殊的Advisor

该情况非常特殊也比较复杂,直接上代码:

@Component
public class LogAdvisor extends AspectJPointcutAdvisor {


  public LogAdvisor(AbstractAspectJAdvice advice) {
    super(advice);
  }
  @Override
  public String getAspectName() {
    return "service" ;
  }
}

只要上面getAspectName方法返回值与对应Service的beanName一致也将不会创建代理。

责任编辑:武晓燕 来源: Spring全家桶实战案例源码
相关推荐

2021-04-15 08:01:27

Spring声明式事务

2020-12-08 09:45:07

MySQL数据库索引

2022-09-14 19:50:22

事务场景流程

2022-06-27 07:23:44

MySQL常量优化

2017-12-05 13:25:40

PHP开发服务器内存

2023-11-23 23:52:06

options请求浏览器

2020-04-02 11:16:28

Linux进程高并发

2023-08-10 17:23:39

2019-07-26 11:51:20

云计算IT系统

2023-03-27 13:00:13

Javascript前端

2009-12-09 10:41:26

配置静态路由

2013-09-12 10:41:39

VDI部署

2022-07-02 00:05:21

漏洞Debriked依赖树

2010-07-30 15:32:23

2015-06-01 06:39:18

JavaJava比C++

2020-11-18 09:26:52

@property装饰器代码

2010-10-22 17:26:55

SQL Server删

2010-04-25 17:34:30

负载均衡实现

2022-09-05 10:01:19

VueReact

2022-08-24 15:08:19

模型数据技术
点赞
收藏

51CTO技术栈公众号