Spring AOP(面向切面编程)利用动态代理机制为应用提供横切关注点的模块化。它主要使用两种类型的代理:JDK动态代理和CGLIB代理。下面是Spring AOP动态代理过程的概述:
首先,你需要在Spring配置中启用AOP支持。这通常通过@EnableAspectJAutoProxy
注解或者XML配置中的
元素完成。
切面是包含通知(Advice)和切入点(Pointcut)定义的类。通知是你要应用到连接点(Join Point)的行为,如方法执行前后。切入点则是你指定的通知应该应用的位置。
@Aspect
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
System.out.println("Method is about to be called");
}
}
切面需要被Spring容器管理,可以通过组件扫描或XML配置来注册你的切面类。
当Spring容器创建一个Bean时,如果该Bean符合AOP代理的要求(例如匹配了某个切入点),则Spring会为其创建代理对象。
java.lang.reflect.Proxy
来创建代理对象。
代理对象的创建:通过Proxy.newProxyInstance()
方法创建代理实例。该方法需要三个参数:目标类使用的类加载器、目标类实现的所有接口以及一个InvocationHandler
实例。
调用拦截:每次调用代理对象上的方法时,都会经过InvocationHandler
的invoke()
方法。在这个方法中,可以执行前置逻辑、调用实际的目标方法(通过反射)、以及后置逻辑。
代理对象的创建:CGLIB通过继承目标类的方式来创建代理对象,即为代理对象生成一个目标类的子类,并且覆盖目标类中的方法。因此,代理对象实际上是一个目标类的子类实例。
方法拦截:在CGLIB中,通过重写MethodInterceptor
接口的intercept()
方法来拦截方法调用。这个方法接收四个参数:代理对象、被调用的方法对象、方法参数以及方法代理对象。
如果想要强制使用CGLIB代理,即使目标对象实现了接口,也可以通过配置proxy-target-class="true"
来实现。相反地,若想明确指定使用JDK动态代理,则应保证目标对象实现了必要的业务接口。
一旦代理对象被创建,Spring会在适当的时间将通知应用到连接点上。比如,如果你定义了一个前置通知(@Before
),那么在目标方法执行之前,Spring会自动调用这个通知的方法。
最终,当你通过代理对象调用目标方法时,根据配置的通知类型(前置、后置、环绕等),相应的通知逻辑会被触发,然后才会执行目标方法本身。
final
,Spring无法使用CGLIB为其实现代理,因为Java不允许继承final
类。通过这种方式,Spring AOP允许开发者以声明式的方式分离业务逻辑与系统服务(如日志记录、事务管理等),从而提高代码的清晰度和可维护性。
@Before
@Before
注解用于定义前置通知,它在目标方法执行之前运行。如果前置通知中抛出了异常,目标方法将不会被执行。
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Method " + joinPoint.getSignature().getName() + " is about to be called");
}
@After
@After
注解用于定义后置通知,无论目标方法是否正常返回或者抛出异常,它都会在目标方法之后执行。
@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
System.out.println("Method " + joinPoint.getSignature().getName() + " has been executed");
}
@AfterReturning
@AfterReturning
用于定义返回后通知,只有当目标方法成功返回时才会执行。
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("Method " + joinPoint.getSignature().getName() + " returned with: " + result);
}
@AfterThrowing
@AfterThrowing
用于定义异常后通知,仅在目标方法抛出异常时执行。
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
System.out.println("Method " + joinPoint.getSignature().getName() + " threw exception: " + ex.getMessage());
}
@Around
@Around
是最强大的通知类型,可以完全控制目标方法的执行流程,包括决定何时以及是否执行目标方法。
@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Method " + joinPoint.getSignature().getName() + " is about to be called");
try {
Object result = joinPoint.proceed();
System.out.println("Method " + joinPoint.getSignature().getName() + " successfully returned");
return result;
} catch (IllegalArgumentException e) {
System.out.println("Illegal argument for method " + joinPoint.getSignature().getName());
throw e;
}
}
通过上述例子,我们可以看到不同类型的AOP通知如何影响方法的执行流程,并根据需要进行日志记录、异常处理等操作。这使得代码更加清晰且易于维护,同时减少了重复代码的数量。