AOP为Aspect Oriented Programming的缩写,意思是面向切面编程。它是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。AOP通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP(面向切面编程)是一种编程范式,用于封装横切关注点(cross-cutting concerns)。这些关注点是应用程序中散布在多个模块中的功能需求,如日志记录、性能统计、安全性等。AOP 允许开发人员定义这些关注点,并在需要的位置将它们应用到应用程序中的特定方法、类或模块上,而不需要修改这些目标的源代码。
在 AOP 中,切面(Aspect)是一个模块化单元,它包含了与横切关注点相关的行为。切面可以通过“通知”(Advice)来描述横切关注点在目标对象上的执行时机(如方法执行前、执行后等),还可以定义“切点”(Pointcut)来指定何处应用这些通知。
AOP 有助于提高代码的模块化程度,减少重复代码,并使关注点的管理更加方便。在 Java 中,Spring 框架提供了对 AOP 的支持,使得开发人员能够更容易地使用 AOP 来管理横切关注点。
当代码中存在横切关注点时,可以对比有和无 AOP 的情况来看到 AOP 的优势。
假设我们有一个简单的应用程序,其中包含了一些业务逻辑代码,并且需要在每个方法的执行前后记录日志。
无 AOP 的情况:
public class UserService {
public void addUser(User user) {
// 业务逻辑代码
// 记录日志
Logger.log("Adding user: " + user.getName());
}
public void deleteUser(String userId) {
// 业务逻辑代码
// 记录日志
Logger.log("Deleting user: " + userId);
}
}
在这种情况下,我们需要在每个方法中手动添加日志记录的代码,这样会导致代码冗余和维护困难。
有 AOP 的情况:
public aspect LoggingAspect {
before() : execution(* UserService.*(..)) {
Logger.log("Method execution started");
}
after() : execution(* UserService.*(..)) {
Logger.log("Method execution completed");
}
}
public class UserService {
public void addUser(User user) {
// 业务逻辑代码
}
public void deleteUser(String userId) {
// 业务逻辑代码
}
}
在这里,我们定义了一个名为 LoggingAspect
的切面,它包含了在目标类 UserService
的所有方法执行前后的日志记录逻辑。通过 AOP,我们将横切关注点与业务逻辑代码分离,使代码更加清晰和模块化。
通过对比可以看到,有 AOP 的情况下,我们不需要在每个方法中手动添加日志记录的代码,而是通过切面将日志记录逻辑应用到目标类的方法上。这样能够提高代码的可读性、可维护性和重用性,减少了代码冗余。
当应用程序中存在更多的横切关注点时,AOP 的优势会更加明显,可以更好地管理和组织关注点的逻辑,提高代码质量和开发效率。
以下是一个相对复杂的AOP使用示例,涉及到在方法执行前后添加日志记录、异常处理和性能统计的功能。
首先,我们定义一个接口和实现类,用于演示业务逻辑:
public interface MyService {
void doSomething();
}
@Service
public class MyServiceImpl implements MyService {
@Override
public void doSomething() {
// 模拟业务逻辑处理
System.out.println("执行业务逻辑...");
}
}
接下来,我们定义一个切面类,实现方法执行前后的日志记录、异常处理和性能统计:
java
@Aspect
@Component
public class MyAspect {
private static final Logger logger = LoggerFactory.getLogger(MyAspect.class);
@Pointcut("execution(* com.example.service.*.*(..))") // 定义切入点表达式,匹配 com.example.service 包下的所有方法
public void servicePointcut() { }
@Before("servicePointcut()") // 在方法执行前执行
public void beforeAdvice(JoinPoint joinPoint) {
logger.info("开始执行方法: " + joinPoint.getSignature().getName());
long startTime = System.currentTimeMillis();
// 记录开始时间到 ThreadLocal 中,方便后续计算耗时
TimeHolder.setTime(startTime);
}
@AfterReturning(pointcut = "servicePointcut()", returning = "result") // 在方法正常返回后执行
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
long endTime = System.currentTimeMillis();
logger.info("方法执行完毕: " + joinPoint.getSignature().getName() + ", 耗时: " + (endTime - TimeHolder.getTime()) + "ms");
logger.info("方法返回结果: " + result);
}
@AfterThrowing(pointcut = "servicePointcut()", throwing = "ex") // 在方法抛出异常后执行
public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
long endTime = System.currentTimeMillis();
logger.error("方法执行出错: " + joinPoint.getSignature().getName() + ", 耗时: " + (endTime - TimeHolder.getTime()) + "ms");
logger.error("异常信息: ", ex);
}
}
在上述代码中,我们使用了 @Pointcut 注解定义了一个切入点表达式,用于匹配 com.example.service 包下的所有方法。然后,我们使用了 @Before、@AfterReturning 和 @AfterThrowing 注解分别实现了方法执行前、正常返回后和抛出异常后的通知。在每个通知中,我们使用 JoinPoint 参数获取了当前方法的签名信息,并使用 ThreadLocal 存储了方法的开始时间,以便后续计算耗时。在 afterReturningAdvice 和 afterThrowingAdvice 方法中,我们还分别记录了方法的返回结果和异常信息。
最后,我们可以在业务逻辑中调用 MyService 的 doSomething 方法,并在控制台中看到 AOP 切面的效果:
java
@Service
public class MyBusinessLogic {
private final MyService myService;
@Autowired
public MyBusinessLogic(MyService myService) {
this.myService = myService;
}
public void execute() {
myService.doSomething(); // 调用业务逻辑方法,触发 AOP 切面效果
}
}
# aop中什么是环绕通知(Around Advice)
在 AOP(面向切面编程)中,环绕通知(Around Advice)是一种切面的类型,它可以在目标方法执行前和执行后都插入自定义的逻辑。
环绕通知是 AOP 中最强大和最灵活的通知类型,它能够完全控制目标方法的执行过程。与其他类型的通知(如前置通知、后置通知)不同,环绕通知可以决定是否执行目标方法以及在何时执行。
具体来说,环绕通知可以在目标方法执行之前做一些准备工作,可以替代目标方法的执行,也可以在目标方法执行之后做一些收尾工作。这使得环绕通知非常适合于实现横切关注点(如日志记录、性能统计、事务管理等)。
在 AOP 中,环绕通知通常由一个带有特殊注解或者配置的方法表示。这个方法在切面中定义,并且通过切点表达式将其与目标方法关联起来。当目标方法被调用时,环绕通知会拦截并执行它自己的逻辑,可以选择是否继续执行目标方法。
下面是一个简单的环绕通知的示例代码(使用 Spring AOP 的方式):
```java
@Aspect
public class LoggingAspect {
@Around("execution(* com.example.service.UserService.*(..))")
public Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable {
// 执行目标方法之前的逻辑
System.out.println("Before method execution");
// 执行目标方法
Object result = joinPoint.proceed();
// 执行目标方法之后的逻辑
System.out.println("After method execution");
// 返回目标方法的结果
return result;
}
}
在上面的示例中,logMethod
方法是一个环绕通知。它使用 @Around
注解标记,并且通过切点表达式指定了要拦截的目标方法。在方法内部,我们可以在目标方法执行前后添加自定义的逻辑。
需要注意的是,在环绕通知中,我们需要调用 joinPoint.proceed()
方法来显式地执行目标方法。这个方法的调用可以控制是否执行目标方法以及何时执行。
总之,环绕通知是 AOP 中非常重要和强大的一种通知类型,它允许我们在目标方法执行前后插入自定义的逻辑,并且可以完全控制目标方法的执行过程。
环绕通知、前置通知和后置通知是 AOP 中三种不同类型的通知,它们在功能和执行时机上有一些相似之处,也有一些显著的差异点。
相同点:
差异点:
执行时机:
对目标方法的影响:
方法签名:
ProceedingJoinPoint
类型的参数,因为它们无法控制目标方法的执行。ProceedingJoinPoint
类型的参数,通过它来控制目标方法的执行。总之,虽然这三种通知类型都能对目标方法进行增强,但它们的执行时机和对目标方法的影响有所不同。环绕通知相比前置通知和后置通知更加灵活,因为它可以完全控制目标方法的执行过程。
虽然环绕通知比前置和后置通知更加灵活,但并不意味着它可以完全取代前置和后置通知。这是因为每种通知类型都有自己的适用场景,不能一概而论。
下面是为什么不应该使用环绕通知来代替前置和后置通知的原因:
1. 环绕通知的性能开销较大: 环绕通知需要在目标方法之前和之后执行,而且还需要通过 ProceedingJoinPoint
参数显式地调用目标方法。相比之下,前置和后置通知的性能开销较小,因此在一些简单的场景中,使用前置和后置通知可以更加高效。
2. 需要更加细粒度的控制: 在一些场景中,我们需要对目标方法的执行过程进行更加细粒度的控制,例如在目标方法执行之前校验参数、在目标方法执行之后处理返回值等。虽然在一些场景中可以通过环绕通知实现这些功能,但在一些复杂的场景中,可能需要使用前置和后置通知来实现更加细粒度的控制。
3. 不需要修改目标方法的执行过程: 在一些场景中,我们只需要在目标方法的执行前或执行后插入一些自定义的逻辑,而不需要改变目标方法的执行过程。例如记录日志、处理异常等,这些场景通常使用前置和后置通知实现更加合适。
综上所述,虽然环绕通知比前置和后置通知更加灵活,但在实际应用中,需要根据具体的场景选择不同的通知类型来实现。
AOP(Aspect-Oriented Programming,面向切面编程)中的“横向切面”是相对于“纵向关注点”的概念而言的。
横向切面(Cross-Cutting Concerns):
横向切面是指那些存在于应用程序多个模块中、并且影响整个应用程序的功能的代码,例如日志记录、性能监控、安全性、事务管理等。这些横向切面的关注点会横跨应用程序的多个模块,因此称为横向切面。
纵向关注点(Vertical Concerns):
相对而言,纵向关注点是指与特定模块或对象本身的功能直接相关的代码,例如业务逻辑、数据持久化等。这些关注点是垂直地贯穿于特定模块或对象的功能之中,因此称为纵向关注点。
AOP 的目的就是解决横向切面的问题,通过将横向切面的关注点从各个纵向关注点中剥离出来,实现关注点的分离和重用。
至于为什么不称之为“竖向切面”,一方面可能是为了和“横向切面”形成对比,更加突出AOP所要解决的问题;另一方面,可能也是为了强调AOP的横向影响力,即跨越多个模块和对象,对整个应用程序产生影响。
因此,“横向切面”和“纵向关注点”这样的命名反映了AOP的核心思想,即通过横向的切面关注点来解决应用程序中横跨多个模块的共同关注点,实现关注点的分离和集中处理。