在Spring框架中,AOP(面向切面编程)是一个非常重要的特性,它主要包括以下几个组成部分:
-
切面(Aspect) :
- 切面是一个模块化的关注点,它通常横切多个对象。切面在Spring AOP中可以通过普通的类(使用
@Aspect注解)来实现。
- 切面是一个模块化的关注点,它通常横切多个对象。切面在Spring AOP中可以通过普通的类(使用
-
连接点(Join Point) :
- 连接点是在程序执行过程中明确的点,比如方法调用或异常抛出。在Spring AOP中,连接点是指应用程序中能够被切面插入的点,例如方法执行。
-
通知/增强(Advice) :
- 通知定义了在特定的连接点处采取的行动。有几种类型的通知,包括前置通知(
Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)。
- 通知定义了在特定的连接点处采取的行动。有几种类型的通知,包括前置通知(
-
切入点(Pointcut) :
- 切入点是一组连接点的集合,通过它指定在哪些连接点上应用通知。切入点通常使用表达式语言定义,以便灵活地选择连接点。
-
引入(Introduction) :
- 引入允许我们向现有的类添加新的方法或属性,而不需要修改原始类。Spring AOP支持通过代理模式来实现引入。
-
织入(Weaving) :
- 织入是将切面应用到目标对象以创建代理对象的过程。Spring AOP在运行时通过动态代理进行织入。
示例
下面是一个简单的示例,说明如何在Spring中使用AOP:
// 1. 定义一个切面
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class LoggingAspect {
// 2. 定义切入点和通知
@Before("execution(* com.example.service.*.*(..))")
public void logBeforeMethod() {
System.out.println("Method execution started");
}
}
// 3. 将切面配置为Spring Bean
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
@Bean
public LoggingAspect loggingAspect() {
return new LoggingAspect();
}
// 配置其他Bean...
}
在这个例子中:
LoggingAspect类是一个切面,它包含一个前置通知logBeforeMethod。@Before注解中的表达式execution(* com.example.service.*.*(..))定义了切入点,这个切入点匹配com.example.service包中所有类的所有方法。AppConfig类是Spring的Java配置类,通过@EnableAspectJAutoProxy启用AOP功能,并注册了切面LoggingAspect。
通过这种方式,当com.example.service包中的任何方法被调用时,都会触发前置通知logBeforeMethod,输出日志信息。
不同部分底层源码实现
Spring AOP的核心实现是基于动态代理和字节码操作。这部分内容相对复杂,下面我们就主要组成部分的底层源码进行简要分析。
1. 切面(Aspect)
切面的定义通常是在类上使用@Aspect注解。Spring在扫描bean时会注意到这个注解,并将其处理为切面。
@Aspect
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBeforeMethod() {
System.out.println("Method execution started");
}
}
Spring在启动时会扫描所有使用了@Aspect注解的类,并通过AnnotationAwareAspectJAutoProxyCreator来创建这些切面。
2. 连接点(Join Point)和 Pointcut
Pointcut表达式定义了在哪些连接点上执行通知。底层实现使用的是AspectJ框架。
@Before("execution(* com.example.service.*.*(..))")
public void logBeforeMethod() {
System.out.println("Method execution started");
}
execution(* com.example.service.*.*(..)) 是典型的Pointcut表达式。在Spring AOP中,这个表达式由AspectJExpressionPointcut类解析:
public class AspectJExpressionPointcut implements Pointcut, MethodMatcher {
private String expression;
public void setExpression(String expression) {
this.expression = expression;
try {
// 使用AspectJ框架解析表达式
pointcutExpression = buildPointcutExpression(this.expression);
} catch (Exception ex) {
throw new IllegalArgumentException("Invalid aspectj expression: " + this.expression, ex);
}
}
private PointcutExpression buildPointcutExpression(String expressionToParse) {
return getPointcutParser().parsePointcutExpression(expressionToParse);
}
}
3. 通知/增强(Advice)
通知是具体执行的操作,如前置通知、后置通知等。Spring AOP通过Advice接口来标识一个通知类型。常见的通知类型有:
MethodBeforeAdviceAfterReturningAdviceThrowsAdviceAroundAdvice
例如,前置通知(Before Advice)的实现:
public class MethodBeforeAdviceInterceptor implements MethodInterceptor {
private final MethodBeforeAdvice advice;
public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
this.advice = advice;
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
return mi.proceed();
}
}
4. AOP代理(Proxy)
AOP代理有两种方式:JDK动态代理和CGLIB代理。Spring AOP会根据目标对象是否实现了接口来选择代理方式。
JDK动态代理
如果目标对象实现了接口,Spring AOP会使用JDK动态代理:
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
private final AdvisedSupport advised;
public JdkDynamicAopProxy(AdvisedSupport config) {
this.advised = config;
}
@Override
public Object getProxy() {
return Proxy.newProxyInstance(
this.advised.getTargetClass().getClassLoader(),
this.advised.getProxiedInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, this.advised.getTarget(), method, args, this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, this.advised.getTargetClass()));
return invocation.proceed();
}
}
CGLIB代理
在使用CGLIB生成子类代理时,Spring AOP会通过Enhancer类来创建代理对象:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibAopProxy implements AopProxy {
private final AdvisedSupport advised;
public CglibAopProxy(AdvisedSupport config) {
this.advised = config;
}
@Override
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.advised.getTargetClass());
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setCallback(new DynamicAdvisedInterceptor(this.advised));
return enhancer.create();
}
private static class DynamicAdvisedInterceptor implements MethodInterceptor {
private final AdvisedSupport advised;
public DynamicAdvisedInterceptor(AdvisedSupport advised) {
this.advised = advised;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
MethodInvocation invocation = new CglibMethodInvocation(obj, this.advised.getTarget(), method, args, proxy, this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, this.advised.getTargetClass()));
return invocation.proceed();
}
}
}
在这个过程中,DynamicAdvisedInterceptor 类实现了CGLIB的 MethodInterceptor 接口,并拦截方法调用,创建并执行 MethodInvocation 对象。
5. MethodInvocation
无论是JDK动态代理还是CGLIB代理,核心都是通过 MethodInvocation 来管理通知链的执行。Spring AOP 提供了具体的实现类如 ReflectiveMethodInvocation 和 CglibMethodInvocation。
ReflectiveMethodInvocation
这是一个用于JDK动态代理的实现类:
public class ReflectiveMethodInvocation implements MethodInvocation {
protected final Object proxy;
protected final Object target;
protected final Method method;
protected final Object[] arguments;
protected final Class<?> targetClass;
private final List<Object> interceptorsAndDynamicMethodMatchers;
private int currentInterceptorIndex = -1;
public ReflectiveMethodInvocation(
Object proxy, Object target, Method method, Object[] arguments,
Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {
this.proxy = proxy;
this.target = target;
this.method = method;
this.arguments = arguments;
this.targetClass = targetClass;
this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
}
@Override
public Object proceed() throws Throwable {
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return this.method.invoke(this.target, this.arguments);
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof MethodInterceptor) {
MethodInterceptor mi = (MethodInterceptor) interceptorOrInterceptionAdvice;
return mi.invoke(this);
} else {
return proceed();
}
}
// 其他getter和toString方法省略
}
CglibMethodInvocation
类似于 ReflectiveMethodInvocation,但是用于CGLIB代理:
public class CglibMethodInvocation extends ReflectiveMethodInvocation {
private final MethodProxy methodProxy;
public CglibMethodInvocation(Object proxy, Object target, Method method, Object[] arguments,
MethodProxy methodProxy, List<Object> interceptorsAndDynamicMethodMatchers) {
super(proxy, target, method, arguments, target.getClass(), interceptorsAndDynamicMethodMatchers);
this.methodProxy = methodProxy;
}
@Override
public Object proceed() throws Throwable {
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return this.methodProxy.invoke(this.target, this.arguments);
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof MethodInterceptor) {
MethodInterceptor mi = (MethodInterceptor) interceptorOrInterceptionAdvice;
return mi.invoke(this);
} else {
return proceed();
}
}
}
总结
通过上述源码,我们可以看到Spring AOP的底层实现主要依赖于以下几部分:
-
切面(Aspect) :通过注解标识并管理。
- 使用
@Aspect注解来定义切面。 - 在Spring启动时由
AnnotationAwareAspectJAutoProxyCreator进行处理。
- 使用
-
连接点(Join Point)和切入点(Pointcut) :通过AspectJ表达式进行定义。
- 表达式解析由
AspectJExpressionPointcut类完成。 - 切入点决定了哪些连接点需要应用通知。
- 表达式解析由
-
通知/增强(Advice) :定义了在连接点处执行的操作。
- 实现类如
MethodBeforeAdviceInterceptor,通过拦截器模式执行通知。
- 实现类如
-
AOP代理(Proxy) :生成代理对象以应用切面逻辑。
- JDK动态代理:通过
JdkDynamicAopProxy实现。 - CGLIB代理:通过
CglibAopProxy实现。
- JDK动态代理:通过
-
方法调用链(MethodInvocation) :管理通知的执行顺序。
ReflectiveMethodInvocation用于JDK动态代理。CglibMethodInvocation用于CGLIB代理。
通过这些组件,Spring AOP能够在运行时动态地为目标对象创建代理,并将切面逻辑织入到指定的连接点上,从而实现横切关注点的模块化管理。
Spring Aop应用场景
Spring框架本身在其内部实现中广泛使用了AOP来提供各种功能和特性。以下是Spring源码中一些使用AOP的典型场景:
1. 事务管理
Spring的声明式事务管理是一个经典的AOP应用场景。通过AOP,Spring可以在不修改业务代码的情况下,自动为方法添加事务功能。
示例: Spring的@Transactional注解以及相关的事务处理逻辑就是通过AOP实现的。当我们在一个方法上标记@Transactional时,Spring会使用AOP为这个方法创建代理对象,在方法执行前后自动地开始、提交或回滚事务。
@Transactional
public void someTransactionalMethod() {
// 业务逻辑代码
}
2. 缓存管理
Spring提供的缓存抽象(如@Cacheable, @CacheEvict等)也是通过AOP实现的。这些注解允许开发者在方法上声明缓存逻辑,Spring会通过AOP在方法调用时自动处理缓存的读取和写入。
示例:
@Cacheable("books")
public Book findBook(String isbn) {
// 查找书籍的业务逻辑
}
3. 安全控制
Spring Security使用AOP来拦截方法调用并进行安全检查。通过@PreAuthorize, @PostAuthorize, @Secured等注解,Spring Security可以在方法执行前后进行权限验证。
示例:
@PreAuthorize("hasRole('ROLE_ADMIN')")
public void adminOnlyMethod() {
// 管理员才能执行的业务逻辑
}
4. 异常处理
Spring的异常处理机制也可以利用AOP来实现。例如,通过@ControllerAdvice和@ExceptionHandler注解,Spring MVC可以统一处理控制器中的异常。
示例:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = Exception.class)
public ResponseEntity<Object> handleException(Exception ex) {
// 全局异常处理逻辑
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage());
}
}
5. 事件监听
Spring的事件发布和监听机制也通过AOP进行了增强。通过@EventListener注解,Spring可以在某些事件发生时触发相应的方法。
示例:
@Component
public class MyEventListener {
@EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
// 上下文刷新事件处理逻辑
}
}
6. 数据绑定与验证
Spring在处理数据绑定和验证时,也可以使用AOP技术。例如,在Spring MVC中,表单数据绑定和验证逻辑可以通过切面在控制器方法之前自动执行。
示例:
@PostMapping("/submit")
public String handleFormSubmit(@Valid @ModelAttribute("form") MyForm form, BindingResult result) {
if (result.hasErrors()) {
return "errorPage";
}
return "successPage";
}
7. 审计日志
Spring Data JPA提供的审计功能(如实体的创建时间、更新时间记录)也是通过AOP实现的。通过@EnableJpaAuditing和@CreatedDate, @LastModifiedDate注解,Spring Data JPA可以自动地在实体保存或更新时记录时间戳。
示例:
@Entity
@EntityListeners(AuditingEntityListener.class)
public class AuditableEntity {
@CreatedDate
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime lastModifiedDate;
// 其他字段和方法
}
8. 灵活的扩展机制
Spring还提供了各种扩展机制,如Bean的生命周期管理(InitializingBean, DisposableBean)和自定义的Bean后处理器(BeanPostProcessor),这些也可通过AOP来增强,以便在Bean初始化或销毁时执行自定义逻辑。