1. 自定义注释是基于SpringAOP实现的
Spring AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的一个强大功能模块,它实现了AOP编程模型,允许开发者将横切关注点(如日志记录、事务管理、安全性检查、性能监控等)从业务逻辑中分离出来,以提高代码的模块化程度、可维护性和可重用性。
核心概念
- 切面(Aspect):切面是跨越多个对象的关注点模块化方式的实现。它封装了横切关注点,比如事务管理就是企业级应用中的一个关注点,它可能会影响到多个对象的操作。
- 连接点(Joinpoint):在程序执行过程中的某个特定点,如方法调用或异常抛出等,其中可以插入切面代码。Spring AOP只支持方法执行作为连接点。
切入点(Pointcut):切入点定义了切面在何处应用,即匹配连接点的一组规则。通过表达式来指定哪些方法或类应该被切面影响。 - 通知(Advice):在切面识别到特定的连接点时执行的动作。有五种类型的通知:
- 前置通知(Before):在目标方法被调用之前执行。
- 后置通知(After):在目标方法执行完毕后(无论是否发生异常)执行。
- 返回通知(AfterReturning):在目标方法成功执行后执行。
- 异常通知(AfterThrowing):在目标方法抛出异常后执行。
- 环绕通知(Around):围绕着目标方法执行,在方法调用前后都可以进行自定义操作,还可以决定是否继续执行目标方法。
- 织入(Weaving):将切面代码插入到应用程序代码中的过程。Spring AOP支持两种织入方式:编译期织入和运行时织入,Spring采用的是运行时织入,即在应用运行时通过动态代理来实现。
实现方式
Spring AOP提供了两种代理方式来实现切面逻辑的织入:
- JDK动态代理:当目标对象实现了至少一个接口时,Spring会使用JDK动态代理技术创建代理对象。这种方式的代理对象需要与目标对象实现相同的接口。
- CGLIB代理:如果目标对象没有实现接口,Spring会使用CGLIB库来创建目标对象的子类代理。这种方式对于没有接口的类同样适用,但要求目标类不能是final的,且必须有默认构造函数。
使用Spring AOP
在Spring中使用AOP,通常涉及定义切面类(使用@Aspect注解标记)、定义切入点(使用@Pointcut注解)、以及在切点上应用通知(使用如@Before、@After等注解)。通过这些配置,Spring框架会在运行时自动创建代理对象,将切面逻辑编织进目标对象的方法调用流程中,从而实现非侵入式的横切关注点管理。
2. 使用自定义注释
步骤1: 定义自定义注解
首先,我们定义一个自定义注解@LogExecutionTime,用于标记需要记录执行时间的方法。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}
步骤2: 创建切面类
接着,我们创建一个切面类LoggingAspect,使用@Aspect注解标记,并在其中定义切点和通知逻辑。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;@Aspect
@Component
public class LoggingAspect {private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);@Around("@annotation(LogExecutionTime)")public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {long start = System.currentTimeMillis();Object proceed = joinPoint.proceed(); // 执行原方法long elapsedTime = System.currentTimeMillis() - start;logger.info("Method {} executed in {} ms", joinPoint.getSignature().getName(), elapsedTime);return proceed;}
}
步骤3: 应用自定义注解
现在,我们可以在任何想要记录执行时间的方法上使用@LogExecutionTime注解。
@Service
public class MyService {@LogExecutionTimepublic String performTask() {// 模拟耗时操作try {Thread.sleep(1000);} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException(e);}return "Task completed!";}
}
步骤4: 配置Spring启用AOP
确保Spring知道要使用AOP。
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {// 其他配置...
}