那些年背过的题目:Spring ApplicationContext-AOP机制

504 阅读8分钟

在Spring框架中,AOP(面向切面编程)是一个非常重要的特性,它主要包括以下几个组成部分:

  1. 切面(Aspect)

    • 切面是一个模块化的关注点,它通常横切多个对象。切面在Spring AOP中可以通过普通的类(使用@Aspect注解)来实现。
  2. 连接点(Join Point)

    • 连接点是在程序执行过程中明确的点,比如方法调用或异常抛出。在Spring AOP中,连接点是指应用程序中能够被切面插入的点,例如方法执行。
  3. 通知/增强(Advice)

    • 通知定义了在特定的连接点处采取的行动。有几种类型的通知,包括前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)。
  4. 切入点(Pointcut)

    • 切入点是一组连接点的集合,通过它指定在哪些连接点上应用通知。切入点通常使用表达式语言定义,以便灵活地选择连接点。
  5. 引入(Introduction)

    • 引入允许我们向现有的类添加新的方法或属性,而不需要修改原始类。Spring AOP支持通过代理模式来实现引入。
  6. 织入(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接口来标识一个通知类型。常见的通知类型有:

  • MethodBeforeAdvice
  • AfterReturningAdvice
  • ThrowsAdvice
  • AroundAdvice

例如,前置通知(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 提供了具体的实现类如 ReflectiveMethodInvocationCglibMethodInvocation

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的底层实现主要依赖于以下几部分:

  1. 切面(Aspect) :通过注解标识并管理。

    • 使用@Aspect注解来定义切面。
    • 在Spring启动时由AnnotationAwareAspectJAutoProxyCreator进行处理。
  2. 连接点(Join Point)切入点(Pointcut) :通过AspectJ表达式进行定义。

    • 表达式解析由AspectJExpressionPointcut类完成。
    • 切入点决定了哪些连接点需要应用通知。
  3. 通知/增强(Advice) :定义了在连接点处执行的操作。

    • 实现类如MethodBeforeAdviceInterceptor,通过拦截器模式执行通知。
  4. AOP代理(Proxy) :生成代理对象以应用切面逻辑。

    • JDK动态代理:通过JdkDynamicAopProxy实现。
    • CGLIB代理:通过CglibAopProxy实现。
  5. 方法调用链(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初始化或销毁时执行自定义逻辑。