什么是 Java 中的动态代理?

2024-08-26 08:33:26 332
**动态代理** 是 Java 中的一种设计模式,它允许你在运行时动态创建代理对象,并在不修改目标对象代码的情况下增强其功能。动态代理广泛用于框架和库中,如 Spring AOP 和 Hibernate,来实现面向切面编程、事务管理、懒加载等功能。

1. 什么是代理模式?

在代理模式中,一个代理对象控制对另一个对象的访问。代理可以在访问目标对象时添加额外的操作,例如日志记录、权限检查、延迟加载等。

代理模式有两种常见的实现方式:

  • 静态代理:代理类在编译时生成,需要为每个接口或类手动编写代理类。
  • 动态代理:代理类在运行时生成,可以处理任意数量的接口或类,而无需手动编写代理类。

2. 动态代理的实现方式

Java 中的动态代理主要有两种实现方式:

  • JDK 动态代理:基于 java.lang.reflect.Proxy 类和 InvocationHandler 接口,只能代理实现了接口的类。
  • CGLIB 动态代理:使用第三方库(如 CGLIB),基于生成目标类的子类进行代理,因此可以代理没有实现接口的类。

JDK 动态代理

JDK 动态代理是通过 Proxy 类和 InvocationHandler 接口来实现的。它只能代理实现了接口的类。

关键组件

  • InvocationHandler 接口:定义了 invoke 方法,代理对象的所有方法调用都会被转发到这个方法中。
  • Proxy:提供了创建代理对象的静态方法 newProxyInstance

示例

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 定义一个接口
interface MyService {
    void doSomething();
}

// 实现接口的具体类
class MyServiceImpl implements MyService {
    @Override
    public void doSomething() {
        System.out.println("Doing something in MyServiceImpl");
    }
}

// 自定义 InvocationHandler
class MyInvocationHandler implements InvocationHandler {
    private final Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method: " + method.getName());
        Object result = method.invoke(target, args); // 调用目标对象的方法
        System.out.println("After method: " + method.getName());
        return result;
    }
}

public class DynamicProxyExample {
    public static void main(String[] args) {
        // 创建目标对象
        MyService target = new MyServiceImpl();

        // 创建代理对象
        MyService proxy = (MyService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new MyInvocationHandler(target)
        );

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

输出

Before method: doSomething
Doing something in MyServiceImpl
After method: doSomething

步骤解析

  1. 接口定义:定义一个接口 MyService 和其实现类 MyServiceImpl
  2. 自定义 InvocationHandler:实现 InvocationHandler 接口,并在 invoke 方法中增加额外的操作(如方法前后的日志打印)。
  3. 创建代理对象:通过 Proxy.newProxyInstance 创建代理对象,传入目标对象的类加载器、接口列表和自定义的 InvocationHandler
  4. 使用代理对象:调用代理对象的方法,InvocationHandler 中的 invoke 方法会被触发,执行额外操作后,再调用目标对象的方法。

3. 动态代理的优点

  • 代码复用:可以将通用的功能(如日志、权限控制)抽象到代理中,不需要修改原有类的代码。
  • 灵活性高:动态代理可以在运行时生成代理类,适用于大多数接口或类,减少了手动编写代理类的工作量。
  • 扩展性好:通过代理可以很容易地在不修改原有代码的情况下增强功能,尤其是在框架和中间件开发中非常有用。

4. CGLIB 动态代理

CGLIB 动态代理基于生成目标类的子类进行代理,因此可以代理没有实现接口的类。CGLIB 在 Spring 中也被广泛使用,特别是用来代理非接口类。

CGLIB 代理的简单示例(使用 Maven 引入 CGLIB 依赖):

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

// 定义一个普通类
class MyClass {
    public void doSomething() {
        System.out.println("Doing something in MyClass");
    }
}

// 自定义 MethodInterceptor
class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method: " + method.getName());
        Object result = proxy.invokeSuper(obj, args); // 调用父类的方法
        System.out.println("After method: " + method.getName());
        return result;
    }
}

public class CglibProxyExample {
    public static void main(String[] args) {
        // 创建 Enhancer 对象
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MyClass.class);
        enhancer.setCallback(new MyMethodInterceptor());

        // 创建代理对象
        MyClass proxy = (MyClass) enhancer.create();
        proxy.doSomething();
    }
}

输出

Before method: doSomething
Doing something in MyClass
After method: doSomething

注意

  • CGLIB 动态代理比 JDK 动态代理稍微复杂一些,但它可以代理没有实现接口的类。
  • 如果目标类是 final,CGLIB 代理无法创建代理对象,因为它不能继承 final 类。

总结

  • JDK 动态代理 适用于代理实现了接口的类,基于 ProxyInvocationHandler 实现,简单灵活。
  • CGLIB 动态代理 适用于代理没有实现接口的类,基于字节码生成技术实现代理类,更加通用,但稍复杂。
  • 动态代理为 Java 提供了强大的扩展能力,特别是在框架开发中,广泛用于实现切面编程、事务管理等功能。