mini-ssm Lab5 - AOP

3 阅读4分钟

mini-ssm Lab5 - AOP

mini-ssm github

在上一节Lab4中,我们讨论了依赖注入(DI),并解决了其中的核心问题。本节Lab5将实现AOP(面向切面编程)的基础功能,并在下一节实现AOP与Spring的集成。

实现动态代理的两种方式

  • JDK代理(适用于接口)
  • CGLIB代理(适用于没有接口的类)

JDK proxy

JDK代理(Java Dynamic Proxy)是Java内置的代理机制,基于Java反射API实现,主要用于接口代理。通过JDK代理,可以在运行时动态生成代理对象。

工作原理

JDK代理依赖java.lang.reflect.Proxy类和InvocationHandler接口。代理对象的方法调用会被invoke()方法拦截,用于在方法调用前后增加通用功能(如日志、事务管理、权限控制等)。

主要组成
  • Proxy:用于生成代理实例,使用Proxy.newProxyInstance()方法创建代理对象。
  • InvocationHandler接口:定义了invoke(Object proxy, Method method, Object[] args)方法,用于处理方法调用。每次代理方法被调用时,invoke()会被触发。
使用场景

JDK代理适用于以下场景:

  • AOP编程:如Spring中的AOP,通过动态代理实现日志、权限验证等横切关注点。
  • 事务管理:在方法调用前后自动开启和提交事务。
注意事项
  • JDK代理要求代理对象必须实现接口。
  • 对于未实现接口的类,需使用CGLIB等其他代理方式。

CGLIB代理

CGLIB代理(Code Generation Library)基于字节码生成技术,不依赖接口,而是通过生成子类实现代理。它常用于没有实现接口的类,广泛用于Spring AOP。

工作原理

CGLIB通过生成目标类的子类并覆盖其中的方法,来实现代理功能。方法调用会触发代理子类的同名方法,该方法会调用原方法并加入增强逻辑。

主要组成
  • Enhancer:CGLIB的核心类,用于生成代理对象,通过设置父类、回调等信息,生成子类代理对象。
  • MethodInterceptor接口:定义了intercept()方法,类似JDK代理的InvocationHandler,用于拦截目标对象的方法调用。
使用场景

CGLIB适用于以下场景:

  • 没有接口的类:当类未实现接口时,CGLIB是适合的选择。
  • AOP编程:为方法添加横切关注点,如日志、事务等。
注意事项
  • CGLIB无法代理final类和final方法。
  • CGLIB代理基于字节码生成,性能通常优于JDK代理,但依赖额外库(如Spring环境下自动包含CGLIB)。

CGLIB与JDK代理对比

  • 实现方式:JDK代理基于接口,CGLIB基于继承。
  • 适用范围:JDK代理适用于实现接口的类;CGLIB适用于没有接口的类。

Spring AOP 核心概念

  • 切面(Aspect):封装横切关注点的模块,包含一组增强逻辑。
  • 连接点(Join Point):程序执行过程中的某个点(如方法调用、异常抛出)。
  • 切入点(Pointcut):指定在哪些方法或连接点上应用增强。
  • 通知(Advice):在连接点执行的增强逻辑,根据时机分为@Before@After@AfterReturning@AfterThrowing@Around等。
  • 目标对象(Target Object):被代理的对象,即需要应用增强的对象。
public Object invoke(Object proxy, Method method, Object[] args) {
    Object result;
    try {
        //@Before
        result = method.invoke(target, args);
        //@AfterReturning
        return result;
    } catch (InvocationTargetException e) {
        Throwable targetException = e.getTargetException();
        //@AfterThrowing
        throw targetException;
    } finally {
        //@After
    }
}

本节目标

解析Aspect类生成Advice,并通过JDK/CGLIB生成代理。

定义注解

Spring AOP中的切点表达式功能强大,在mini-ssm中,我们实现常用的两种注解:

  • @annotation():匹配被指定注解修饰的方法。
  • @within():匹配被指定注解修饰的类的所有方法。

以下是注解的定义:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Before {
    Class<? extends Annotation>[] annotation() default {};
    Class<? extends Annotation>[] within() default {};
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterThrowing {
    Class<? extends Annotation>[] annotation() default {};
    Class<? extends Annotation>[] within() default {};
    Class<? extends Throwable>[] throwable() default Throwable.class;
}

其他注解如@Around@After@AfterReturning的实现方式类似。

AOP实现

代理流程

  1. 解析Aspect类,根据注解类型生成不同的Advice
  2. 获取匹配的通知并构建拦截器链。
  3. 生成代理类。

AspectParser

解析Aspect类,并生成通知:

public abstract class AspectParser {
    static final Map<Class<? extends Annotation>, List<MethodInterceptor>> withinMap = new HashMap<>();
    static final Map<Class<? extends Annotation>, List<MethodInterceptor>> annotationMap = new HashMap<>();
    private static final Map<Class<?>, BiFunction<Method,Object,MethodInterceptor>> supplierMap=new HashMap<>();

    static {
        supplierMap.put(After.class,AfterAdviceInterceptor::new);
        supplierMap.put(AfterReturning.class,AfterReturningAdviceInterceptor::new);
        supplierMap.put(Around.class,AroundAdviceInterceptor::new);
        supplierMap.put(Before.class,BeforeAdviceInterceptor::new);
    }

    public static void parse(Object aspect) throws Exception {
        Method[] methods = aspect.getClass().getDeclaredMethods();
        for (Method m : methods) {
            if(m.getAnnotations().length==0) continue;
            parsing(After.class,m,aspect);
            parsing(AfterReturning.class,m,aspect);
            parsing(AfterThrowing.class,m,aspect);
            parsing(Around.class,m,aspect);
            parsing(Before.class,m,aspect);
        }
    }
}

AdvisedSupport

代理类需要的属性支持:

public class AdvisedSupport {
    private Class<?> targetClass;
    private Object target;
    private final Map<Method, List<MethodInterceptor>> methodCache=new HashMap<>();
}

MethodInterceptor 实现

  • BeforeAdviceInterceptor:在方法执行前调用通知。
  • AroundAdviceInterceptor:将方法执行权交给通知。
  • AfterThrowingAdviceInterceptor:捕获异常并执行通知方法。

JDK 动态代理实现

public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
    private final AdvisedSupport advised;

    public JdkDynamicAopProxy(AdvisedSupport config) {
        this.advised = config;
    }

    @Override
    public Object getProxy() {
        return getProxy(this.advised.getTargetClass().getClassLoader());
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        return Proxy.newProxyInstance(classLoader, this.advised.getTargetClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //获取拦截器链
        List<MethodInterceptor> interceptorsAndDynamicMethodMatchers =
                this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, this.advised.getTargetClass());

这样,我们实现了AOP基础代理和注解支持。下一步将结合Spring AOP,将此实现与Spring框架整合。