Spring框架是我们使用比较多的一个框架,而AOP又是Spring的核心特性之一,本篇文章将介绍一下AOP的切点表达式、通知等特性及如何使用Spring AOP。
AOP(Aspect-Oriented Programming,面向切面编程) 是一种编程范式,旨在将横切关注点与核心业务逻辑相分离,以提高代码的模块化性、可维护性和复用性。
在传统的面向对象编程中,程序的功能被模块化为类和方法,但某些功能可能会跨越多个类和方法,如日志记录、事务管理、安全控制等,这些功能不属于核心业务逻辑,但又必须在多个地方重复使用,导致代码重复和耦合性增加。
AOP提供了一种机制,可以将这些横切关注点单独定义,并在需要的地方插入到应用程序中,而不必修改核心业务逻辑。
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP(面向切面编程) 语法,并拥有一个专门的编译器,用于生成遵守Java字节编码规范的Class文件。
AspectJ可以单独使用,也可以整合到其他框架中。当单独使用AspectJ时,需要使用专门的编译器ajc。AspectJ属于静态织入,通过修改代码来实现,包括编译期织入等多种织入时机。
Spring集成AspectJ,可以在Spring中方便的使用AOP。
Spring AOP核心概念主要包括以下几个方面:
这些核心概念共同构成了AOP的基础,使得我们能够模块化地处理横切关注点,从而提高代码的可维护性和可重用性。
Pointcut 表达式 是用来定义切入点的规则,它决定了哪些连接点(方法调用或方法执行)将会被通知所影响。在 Spring AOP 中,Pointcut 表达式通常由以下几种规则和通配符组成:
带有 @ 符的切点表达式都是需要指定注解的连接点。
这些规则可以通过逻辑运算符(如 &&、||、! )进行组合,以实现更复杂的 Pointcut 匹配规则。我们可以根据自己的需求,灵活地使用这些规则来定义切入点表达式,实现对目标方法的精确匹配和监控。
execution() 表达式使用的比较多,最复杂的一个表达式,这里重点介绍一下。
execution() 表达式的语法结构如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
其中,各部分的含义如下:
execution(public * *(..))
execution(* set*(..))
execution(* com.xyz.service.AccountService.*(..))
execution(* com.xyz.service.*.*(..))
execution(* com.xyz.service..*.*(..))
execution(* com.example.service.MyService.myMethod(String, int))
总的来说,execution() 方法提供了一种灵活且强大的方式来定义切点表达式,从而精确定位需要添加通知的目标方法。
图片
在 Spring AOP 中,通知(Advice)是在切入点(Pointcut)上执行的代码。Spring 提供了几种类型的通知,每种类型都对应着在连接点执行前、执行后或抛出异常时执行的不同代码逻辑。这些通知对应着不同的注解,常用的通知注解包括:
value:要绑定的切点或者切点表达式。
argNames: 用于指定连接点表达式中方法参数的名称,以便在通知方法中通过参数名来获取方法参数的值。这样可以在前置通知中访问和处理方法参数的具体数值。该属性即使不指定也能获取参数。
pointcut:作用和value属性一样,当指定pointcut时,会覆盖value属性的值。
returning:方法返回的结果将被绑定到此参数名,可以在通知中访问方法的返回值。
@AfterThrowing: 在方法抛出异常后执行的通知。它的属性前3个和 @AfterReturning注解一样,多了1个属性:
throwing:指定方法抛出的异常将被绑定到此参数名,可以在通知中访问方法抛出的异常。
@After: 在方法执行后(无论成功或失败)执行的通知。属性同 @Before 注解。
@Around: 环绕通知,能够在方法执行前后都可以进行操作,具有最大的灵活性。属性同 @Before 注解。
通知的执行顺序为: @Around -> @Before -> @AfterReturning(不抛异常情况) 或者 @AfterThrowing(抛异常情况) -> @After
这些通知注解可以与 Pointcut 表达式结合使用,实现对目标方法的拦截和处理。通过选择合适的通知类型,开发者可以根据需求在不同的时间点插入自定义的逻辑,实现对方法调用的控制和增强。
讲了那么多概念性的东西,下面来看怎么使用Spring AOP。
在Spring 中使用AOP也很简单,主要分3步:
我这里使用的是Springboot 3.1.5、jdk 17,如果是Springboot低版本的可能需要引入 spring-boot-starter-aop 依赖,高版本的AOP已经包含在spring-boot-starter-web依赖中了:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId></dependency>
Spring官网中介绍,使用Spring AOP要在启动类或者配置类中加上 @EnableAspectJAutoProxy 注解开启 AspectJ 注解的支持,在我使用的这个版本中并不需要,如果你的项目中切面未生效可以尝试使用该注解。
定义一个接口,下面用于对这个接口及其实现类进行拦截:
public interface AopService { /** * 两数除法 * @param a * @param b * @return */ BigDecimal divide(BigDecimal a, BigDecimal b); /** * 两数加法 * @param a * @param b * @return */ BigDecimal add(BigDecimal a, BigDecimal b);}
@Servicepublic class MyAopServiceImpl implements AopService{ /** * 两数除法 * * @param a * @param b * @return */ @Override public BigDecimal divide(BigDecimal a, BigDecimal b) { return a.divide(b , RoundingMode.UP); } /** * 两数加法 * * @param a * @param b * @return */ @Override public BigDecimal add(BigDecimal a, BigDecimal b) { return a.add(b); }}
新建一个类,在类上加上@Aspect 注解,标记该类为切面。
@Component@Aspectpublic class AspectComponent {}
在切面中使用@Pointcut注解定义切点表达式,然后在通知注解中使用定义好的切点。在该示例中主要对AopService#divide()方法进行拦截。
@Component@Aspectpublic class AspectComponent { /** * 匹配AopService接口的divide方法 */ @Pointcut("execution(* site.suncodernote.aop.AopService.divide(..))") void dividePointCut(){ } /** * 匹配AopService接口的divide方法 */ @Pointcut("within(site.suncodernote.aop.AopService+)") void withinPointCut(){ } /** * 匹配AopService接口的add方法 或者 divide方法 */ @Pointcut("execution(* site.suncodernote.aop.AopService.add(..)) || execution(* site.suncodernote.aop.AopService.divide(..))") void addOrDividePointCut(){ } @Before("dividePointCut()") public void beforeDivide(JoinPoint joinPoint){ System.out.println("---------------------@Before----------------"); printJoinPoint(joinPoint); } @After("dividePointCut()") public void afterDivide(JoinPoint joinPoint){ System.out.println("---------------------@After----------------"); printJoinPoint(joinPoint); } @AfterReturning(pointcut = "dividePointCut()" , returning = "result") public void afterReturningDivide(JoinPoint joinPoint , BigDecimal result){ System.out.println("---------------------@AfterReturning----------------"); System.out.println("返回结果="+result); printJoinPoint(joinPoint); } @AfterThrowing(pointcut = "dividePointCut()" , throwing = "e") public void afterThrowingDivide(JoinPoint joinPoint ,Exception e){ System.out.println("---------------------@AfterThrowing----------------"); System.out.println("异常:"+e.getMessage()); printJoinPoint(joinPoint); } @Around("dividePointCut()") public Object aroundDivide(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("---------------------@Around----------------"); printJoinPoint(joinPoint); Object[] args = joinPoint.getArgs(); Object result = null; try { //执行方法 result = joinPoint.proceed(args); } catch (Throwable throwable) { throwable.printStackTrace(); } System.out.println("返回值:"+result); return result; } private void printJoinPoint(JoinPoint joinPoint){ Object[] args = joinPoint.getArgs(); Signature signature = joinPoint.getSignature(); System.out.println("方法名:"+signature.getName()); System.out.println("方法参数:"+ Arrays.toString(args)); System.out.println(); }}
写个简单的单元测试,调用AopService#divide()方法,然后看一下输出结果。
@SpringBootTestpublic class AOPTest { @Resource private AopService aopService; @Test public void testAOP() { BigDecimal a = new BigDecimal(1); BigDecimal b = new BigDecimal(2);// aopService.add(a, b); aopService.divide(a, b); }}
测试结果:
---------------------@Around----------------方法名:divide方法参数:[1, 2]---------------------@Before----------------方法名:divide方法参数:[1, 2]---------------------@AfterReturning----------------返回结果=1方法名:divide方法参数:[1, 2]---------------------@After----------------方法名:divide方法参数:[1, 2]返回值:1
从测试结果中通知执行的顺序是按照我们上面所说的执行顺序执行的。
本文介绍了Spring AOP的常用的切点表达式、通知注解等,我们可以利用AOP对业务逻辑的各个部分进行隔离,使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发的效率。
本文链接:http://www.28at.com/showinfo-26-82760-0.html轻松上手Spring AOP,掌握切面编程的核心技巧
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com