深入理解Spring AOP的实战与应用
在Java的世界里,我们正在探索一种强大、灵活且在业务逻辑中非常常见的概念------面向切面编程(AOP)。特别是在Spring框架中,这种编程范式无疑是我们构建高效、可维护代码的关键。在今天的讨论中,我们将一起了解如何在Spring中利用AOP。
切面编程的介绍
面向切面编程(AOP)是一种编程范式,其目的是提高模块化关注点(比如事务管理和日志)的能力。我们定义切面,应用到特定的代码部分(即连接点),并决定何时执行(即切点)。这种功能使得我们可以将通用的任务集中管理,从而使代码更加清晰、简洁。
一个简单的例子:衡量方法执行时间
让我们从一个简单的例子开始。假设我们有一个查询在线用户数的方法,我们想知道这个方法的执行时间,以便我们可以监控和优化它。这正是AOP的用武之地。 首先,让我们先定义一个方法:
java
public class UserService {
public List<User> getOnlineUsers() {
// 模拟耗时操作
Thread.sleep(1000);
return Arrays.asList(new User("Alice"), new User("Bob"));
}
}
现在,我们希望在调用此方法时记录其执行时间。我们可以使用Spring提供的面向切面编程(AOP)功能来帮助我们实现这个目标,这样我们可以把性能度量的逻辑和业务逻辑分离开来。
在Spring中实现AOP的方式是定义一个标注有@Aspect
的类,并在此类中定义通知(advice)方法。这些通知方法会在被切点(pointcut)选择的方法周围运行。在我们的例子中,我们定义一个名为PerformanceAspect
的切面,如下:
java
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class PerformanceAspect {
@Around("execution(* com.example.service.UserService.getOnlineUsers())")
public Object measureMethodExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.nanoTime();
Object proceed = joinPoint.proceed(); //调用目标方法
long end = System.nanoTime();
String methodName = joinPoint.getSignature().getName();
System.out.println("Execution time of " + methodName + " :: " + ((end - start)/1e6) + " ms");
return proceed;
}
}
在这个例子中,我们使用@Around
通知来包围getOnlineUsers
方法的执行。ProceedingJoinPoint
参数允许你在切面方法中调用目标方法。通过 joinPoint.proceed()
方法,你可以执行目标方法,这是一个关键的步骤,因为它实际上会触发目标方法的执行。 在调用 proceed() 方法之前
,你可以在目标方法执行前进行一些操作(如记录开始时间), 而在调用 proceed() 方法之后
,你可以在目标方法执行后进行一些操作(如记录结束时间)
接下来,让我们进一步理解这个概念,回到之前的UserService类,并创建一个主类来测试:
java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.CommandLineRunner;
import org.springframework.beans.factory.annotation.Autowired;
import com.example.service.UserService;
@SpringBootApplication
public class Main implements CommandLineRunner {
@Autowired
private UserService userService;
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Override
public void run(String... args) {
// 这里调用的userService是一个代理对象
userService.getOnlineUsers();
}
}
UserService类是由Spring进行注入的;当Spring框架创建一个Bean,并准备将其注入到其他Bean时,它会首先查看该Bean是否需要被代理。如果一个Bean被标识为需要创建代理(例如,它可能被定义为一个切面,或者被@Transactional等注解标记),Spring将创建一个新的代理对象,用来代替原始Bean。 所以这个地方的UserService
其实在注入的时候被换成代理对象
,而不是原始的UserService对象
。
对待被增强的方法一探究竟
如果一个类被标记为需要被增强,但只有其中的一部分方法需要被增强,那会发生什么呢?实际上,在代理类中所有的方法都会被保留。而需要被增强的方法在调用前后将执行切面逻辑,而不需要被增强的方法则会直接调用原始目标对象的方法。
一个类是否存在多个增强类?
在典型的Spring AOP中,一个类通常只会有一个代理类。这个代理类包含了所有与该类相关的切面逻辑。但是,如果一个类被增强的切面不止一个,那会怎样呢?Spring会创建一个代理对象,然后将所有切面逻辑应用到这个代理对象上。所以,无论切面的数量,都只会有一个代理对象。
结论
Spring的面向切面编程提供了一种强大的方式来管理在多个方法和对象中重复出现的代码。通过将这些代码提取到切面中,我们可以更好地维护代码的清晰度和简洁性,使得我们在处理核心业务逻辑时更加专注。