基本介绍

自定义注解结合环绕通知是Spring AOP中一种强大的模式,它允许我们为特定的行为或逻辑创建一个明确的标记,然后在这些被标记的方法上应用通知。这种方法在创建清晰、可维护的代码方面非常有用,尤其是在处理跨越多个组件或服务的关注点时。

创建自定义注解

首先,我们需要定义一个自定义注解。这个注解将被用于标记那些需要应用特定逻辑的方法。例如,我们可能想要创建一个用于性能监控的注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MonitorPerformance {
    // 可以在这里定义注解的属性
}

编写环绕通知

接下来,编写一个环绕通知,它将检测到方法上的这个注解,并在这些方法被调用时执行一些逻辑。例如,我们可以在调用这些方法之前和之后记录时间,以监控它们的性能:

@Aspect
@Component
public class PerformanceMonitorAdvice {

    @Around("@annotation(MonitorPerformance)")
    public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();

        try {
            // 执行目标方法
            return joinPoint.proceed();
        } finally {
            long totalTime = System.currentTimeMillis() - startTime;
            System.out.println("执行 " + joinPoint.getSignature() + " 耗时:" + totalTime + "ms");
        }
    }
}

使用自定义注解

最后,我们可以在任何方法上使用这个自定义注解来启用性能监控:

public class SomeService {

    @MonitorPerformance
    public void someMethodToMonitor() {
        // 方法实现
    }
}

注意事项

  • 注解的保留策略:确保自定义注解的保留策略是RUNTIME,因为只有运行时注解才能被AOP框架识别。
  • 目标方法的返回类型:环绕通知应该返回一个对象,这通常是目标方法的返回值。如果目标方法是void类型,环绕通知应返回null
  • 异常处理:确保环绕通知正确处理了异常情况。如果有必要,应该重新抛出异常。
  • 性能考虑:环绕通知会稍微增加方法调用的开销,特别是在高性能要求的应用中应当注意这一点。

通过这种方式,可以创建清晰、灵活且易于维护的代码,将特定的逻辑(如日志记录、性能监控、事务管理等)与业务逻辑分离。


处理方法参数、修改返回值的例子

下面是一个包含对方法参数的处理和修改返回值的例子。假设我们有一个方法,它接受一个字符串参数并返回一个字符串。环绕通知将修改这个参数,并在方法执行后修改返回值。

示例场景

假设我们有一个服务方法,它将用户的姓名转换为大写。我们的环绕通知将在方法执行前后添加一些自定义逻辑:

  1. 在方法执行前:修改传入的姓名(比如添加一些前缀)。
  2. 在方法执行后:修改方法的返回值(比如添加一些后缀)。

示例代码

首先,我们定义一个简单的服务方法:

public class NameService {
    public String transformName(String name) {
        return name.toUpperCase();
    }
}

接下来,我们编写环绕通知:

@Aspect
@Component
public class NameTransformAdvice {

    @Around("execution(* NameService.transformName(..)) && args(name)")
    public Object aroundTransformName(ProceedingJoinPoint joinPoint, String name) throws Throwable {
        // 方法执行前:修改传入的参数
        String modifiedName = "Mr. " + name;

        // 执行目标方法,并获取返回值
        Object result = joinPoint.proceed(new Object[]{modifiedName});

        // 方法执行后:修改返回值
        if (result instanceof String) {
            result = result + "!";
        }

        return result;
    }
}

在这个例子中:

  • 我们使用execution(* NameService.transformName(..))指定了要拦截NameService类中的transformName方法。
  • && args(name)表示拦截的方法必须有一个名为name的参数。
  • proceed方法中,我们传入了一个新的参数数组,其中包含了修改后的name参数。
  • 在方法执行后,我们修改了方法的返回值。

注意事项

  • 确保环绕通知的异常处理适当,以防止潜在的问题。
  • 修改参数和返回值应谨慎进行,确保不违反业务逻辑和应用的整体行为。
  • 这种类型的通知会增加方法调用的复杂性,因此请在确实需要时才使用。
  • 确保@Aspect@Component注解添加到了环绕通知类上,以便Spring能够将其注册为一个切面。

在AspectJ框架中,使用环绕通知时,ProceedingJoinPoint.proceed() 方法的参数(有的话)必须是一个对象数组(Object[])。这是因为proceed() 方法需要能够处理任意数量的参数,而将参数作为一个数组传递是处理这种可变参数情况的一种通用方式。

当调用一个方法时,这个方法可能有零个、一个或多个参数。为了能够通用地处理这些情况,ProceedingJoinPoint.proceed() 方法接受一个对象数组作为参数。这样,无论原始方法有多少参数,都可以通过一个数组传递给proceed() 方法。

在上述示例中,transformName 方法接受一个单一的字符串参数,所以当我们修改这个参数并准备将其传递给proceed() 方法时,我们需要将它放入一个对象数组中。即使是单一的参数也需要这样做,因为proceed() 方法总是期望一个对象数组。这就是为什么我们需要使用 new Object[]{modifiedName} 而不是直接传递 modifiedName

示例代码中的这一行:

Object result = joinPoint.proceed(new Object[]{modifiedName});

意味着我们正在创建一个只有一个元素的新数组,该元素是修改后的modifiedName,然后将这个数组作为参数传递给proceed() 方法。这确保了方法调用可以正确地带着新的参数执行。


proceed()方法的两种调用方式

在AspectJ框架中,使用环绕通知时,ProceedingJoinPoint.proceed() 方法可以以两种方式调用:带参数和不带参数。

  1. 不带参数的调用:如果调用 proceed() 方法而不传递任何参数,那么它将使用原始的参数(即目标方法被调用时传入的参数)来执行目标方法。这是最常见的调用方式,特别是当我们不需要修改传递给目标方法的参数时。
result = joinPoint.proceed();

这种方式相当于简单地让目标方法以原始的参数执行。

  1. 带参数的调用:另一方面,我们也可以传递一个 Object[] 类型的参数数组给 proceed() 方法。这在需要修改传递给目标方法的参数时非常有用。通过这种方式,可以改变传入目标方法的参数值。
Object[] modifiedArguments = ...; // 修改后的参数
result = joinPoint.proceed(modifiedArguments);

在这种情况下,传递给 proceed() 的参数将用于替换原始的方法参数。

何时使用不同的调用方式

  • 当不需要修改目标方法的参数,只是想在方法执行前后添加一些逻辑时,可以使用不带参数的 proceed() 调用。
  • 当需要修改方法的参数,或者基于某些条件改变它们时,应该使用带参数的 proceed() 调用。

总之,ProceedingJoinPoint.proceed() 方法提供了灵活性,既可以让目标方法以原始参数执行,也可以让我们改变这些参数。这是环绕通知的一个强大特性,使得它在Spring AOP中非常有用。