Spring-aop介绍

时间:2024-10-16 11:12:46

1、核心术语介绍

AOP 切面编程核心术语如下:

术语 含义
目标(Target) 被通知的对象
代理(Proxy) 向目标对象应用通知之后创建的代理对象
连接点(JoinPoint) 目标对象的所属类中,定义的所有方法均为连接点
切入点(Pointcut) 被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点)
通知(Advice) 增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情
切面(Aspect) 切入点(Pointcut)+通知(Advice)
Weaving(织入) 将通知应用到目标对象,进而生成代理对象的过程动作

上述描述比较抽象,上述术语间的关系如下图所示:

2、spring 实现方式

对于有接口的bean,采用JDK Proxy去创建代理对象。对于没有实现接口的对象,使用 Cglib 生成一个被代理对象的子类来作为代理。

2.1 JDK Proxy

采用java反射机制实现。一个简单的示例如下所示:

// 定义接口
public interface TenantService {
    void doSomething(String tenantId);
}

// 定义实现
public class TenantServiceImpl implements TenantService {
    @Override
    public void doSomething(String tenantId) {
        System.out.println("Doing something for tenant: " + tenantId);
    }
}



public class JdkProxyExample {

    public static void main(String[] args) {
        // 创建真实对象
        TenantService realTenantService = new TenantServiceImpl();

        // 创建 InvocationHandler
        InvocationHandler handler = new InvocationHandler() {
            private final TenantService tenantService = realTenantService;

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String tenantId = (String) args[0];
                System.out.println("Before method call - Tenant ID: " + tenantId);

                // 调用真实对象的方法
                method.invoke(tenantService, args);

                System.out.println("After method call - Tenant ID: " + tenantId);
                return null; // 无返回值方法返回 null
            }
        };

        // 创建代理对象
        TenantService proxyTenantService = (TenantService) Proxy.newProxyInstance(
                TenantService.class.getClassLoader(),
                new Class[]{TenantService.class},
                handler
        );

        // 调用代理对象的方法
        proxyTenantService.doSomething("tenant1");
    }
}

2.2 Cglib

Cglib 通过字节码技术为现有类生成子类,并在子类中增强逻辑,从而实现AOP功能。

CGLIB 实现 AOP 的基本原理

  • 生成子类: CGLIB 通过生成现有类的子类来实现增强逻辑。这意味着原始类的所有方法都会在子类中存在,并且可以在子类中添加新的方法或覆盖父类的方法。
  • 方法拦截: 在子类中,CGLIB 可以通过拦截器(Interceptor)来控制方法的调用。拦截器可以在方法调用前后执行自定义逻辑。
  • 动态代理: CGLIB 使用动态代理机制来创建增强的对象。这种代理机制允许在运行时动态地生成增强的类实例。

CGLIB 的核心组件

  • Enhancer:负责生成增强类的工具类。
  • MethodInterceptor:方法拦截器,用于在方法调用前后执行自定义逻辑。
  • MethodProxy:方法代理,提供了对方法的代理调用。

代码示例:

// 业务实现,不实现接口
public class TargetClass {
    public void doSomething() {
        System.out.println("TargetClass doing something...");
    }
}


// 拦截器,在方法执行前后织入逻辑
public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method call");
        Object result = proxy.invokeSuper(obj, args); // 调用原始方法
        System.out.println("After method call");
        return result;
    }
}


public class CglibAopExample {
    public static void main(String[] args) {
        // 创建 Enhancer 实例
        Enhancer enhancer = new Enhancer();
        
        // 设置父类
        enhancer.setSuperclass(TargetClass.class);
        
        // 设置方法拦截器
        enhancer.setCallback(new MyMethodInterceptor());
        
        // 创建增强对象
        TargetClass enhancedObject = (TargetClass) enhancer.create();
        
        // 调用增强对象的方法
        enhancedObject.doSomething();
    }
}

2.3 JDK Proxy VS Cglib

对2种方式的优缺点进行对比。

特性

JDK Proxy CGLIB
适用范围 仅适用于实现了接口的类 适用于所有类(包括未实现接口的类)
实现方式 通过 Proxy.newProxyInstance 方法创建代理对象 通过 Enhancer 类生成子类
性能 通常比 CGLIB 略慢 性能较好,因为避免了接口查找和反射调用
灵活性 需要实现接口,限制较大 更加灵活,可以代理任意类
兼容性 兼容性较好,广泛支持 兼容性良好,但在某些旧版本 JVM 上可能需要额外配置
代理类型 动态代理 动态代理
代码侵入性 低,只需要实现接口 较高,需要生成子类
调试难度 较低,因为不需要生成额外的类 较高,因为生成了额外的子类
扩展性 扩展性较好,可以通过接口进行扩展 扩展性一般,需要修改子类的生成逻辑
安全性 较好,因为基于接口 一般,因为涉及子类生成
应用场景 适合于需要代理实现了接口的类 适合于需要代理任意类,包括未实现接口的类

从上述分析来看,JDK proxy的方式并没有明显的优点,那么为什么spring 会选择JDK proxy作为默认aop实现呢?

我认为有以下几个原因:

  • 兼容性:JDK proxy 是jdk自身的实现,兼容性更高
  • 安全性:基于接口实现,安全性更高
  • 历史原因:因为spring最开始就是选择的JDK proxy作为实现方式,一脉相承。