GoF之代理模式

发布于:2024-11-28 ⋅ 阅读:(14) ⋅ 点赞:(0)

JDK 代理:让 Java 对象变得灵活有趣

想象一下,您在一家餐厅里点了一份美味的牛排,而服务员就像是我们编程中的代理。他们负责传达您的需求,并将美食送到您的桌前。在 Java 编程中,JDK 代理就是这样一种角色,它帮助我们在运行时动态地处理方法调用。

什么是 JDK 代理?

JDK 代理是一种设计模式,它允许我们在运行时创建一个代理对象来控制对目标对象的访问。简单来说,它能够在调用真实对象的方法时,添加一些额外的功能,比如日志记录、权限检查等,而无需修改原始对象的代码。代理模式可以分为两种类型:

  1. 静态代理:代理类在编译时就已经确定,适合功能较为简单的情况。
  2. 动态代理:代理类在运行时生成,更为灵活,适合需要大量代理对象的场景。
    假设存在一个
    JDK 代理属于动态代理的范畴,它通过 Java 的反射机制来实现。

静态代理

静态代理的定义

静态代理是一种代理设计模式,通过在代理类中控制对目标类的访问来实现功能的增强。在静态代理中,代理类与目标类需要实现相同的接口,因此在编译阶段就确定了代理的结构。

静态代理的结构

静态代理通常包含以下角色:

  • 接口(Subject):定义目标对象和代理对象的公共方法。
  • 目标对象(RealSubject):实现接口的具体类,包含核心业务逻辑。
  • 代理对象(Proxy):同样实现接口,并持有目标对象的引用,通过调用目标对象的方法来完成具体的功能,通常会在目标方法的前后进行一些操作增强。

静态代理的实现步骤

定义公共接口:声明目标对象和代理对象的公共方法。
实现目标类:目标类实现公共接口,包含实际的业务逻辑。
实现代理类:代理类也实现公共接口,并持有目标对象的引用,通过构造方法将目标对象传入,并在调用方法时进行增强操作。

示例代码

以下是一个静态代理的示例代码,展示如何使用静态代理来对方法进行增强:

// 1. 公共接口
interface Subject {
    void doAction();
}

// 2. 目标类
class RealSubject implements Subject {
    @Override
    public void doAction() {
        System.out.println("执行目标类的核心方法");
    }
}

// 3. 代理类(必须手动编写代理类,并且代理类与目标类实现相同的接口。)
class ProxySubject implements Subject {
    private RealSubject realSubject;

    public ProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void doAction() {
        System.out.println("在执行目标方法前进行一些操作...");
        realSubject.doAction();  // 调用目标对象的方法
        System.out.println("在执行目标方法后进行一些操作...");
    }
}

// 4. 测试类
public class StaticProxyTest {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        ProxySubject proxy = new ProxySubject(realSubject);
        proxy.doAction();  // 通过代理类调用目标方法
    }
}

JDK的动态代理

JDK 代理的工作原理

JDK 代理的核心在于 Proxy 类和 InvocationHandler 接口。我们可以通过 Proxy 类创建一个代理对象,Proxy 是 Java 自带的一个类,属于 JDK 标准库,位于 java.lang.reflect 包中。因此,你不需要自己实现这个类,只需要使用它来动态生成代理对象。并通过 InvocationHandler 接口来定义当调用代理对象的方法时应该执行的操作。

前提代码:

// 定义一个接口
interface HelloService {
    void sayHello(String name);
}

// 实现该接口的真实对象
class HelloServiceImpl implements HelloService {
    @Override
    public void sayHello(String name) {
        System.out.println("Hello, " + name + "!");
    }
}

我们在静态代理的时候,除了以上一个接口和一个实现类之外,是不是要写一个代理类呀!在动态代理中代理类是可以动态生成的。这个类不需要写。我们直接写客户端程序即可:

// 使用代理(客户端)
public class Main {
    public static void main(String[] args) {
        // 创建真实对象
        HelloService helloService = new HelloServiceImpl();
        // 创建代理对象 进行了强制转换因为需要调用代理对象的方法
        HelloService proxy = (HelloService) Proxy.newProxyInstance(
            helloService.getClass().getClassLoader(),
            helloService.getClass().getInterfaces(),
            new DynamicProxy(helloService)
        );

        // 调用代理对象的方法
        proxy.sayHello("Alice");
    }
}
 HelloService proxy = (HelloService) Proxy.newProxyInstance(
            helloService.getClass().getClassLoader(),
            helloService.getClass().getInterfaces(),
            new DynamicProxy(helloService)
        );

这行代码做了两件事:

  • 第一件事:在内存中生成了代理类的字节码
  • 第二件事:创建代理对象

Proxy类全名:java.lang.reflect.Proxy。这是JDK提供的一个类(所以称为JDK动态代理)。主要是通过这个类在内存中生成代理类的字节码。
其中newProxyInstance()方法有三个参数:

  • 第一个参数:类加载器。在内存中生成了字节码,要想执行这个字节码,也是需要先把这个字节码加载到内存当中的。所以要指定使用哪个类加载器加载。
  • 第二个参数:接口类型。代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口。
  • 第三个参数:调用处理器。这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler。显然这是一个回调接口,也就是说调用这个接口中方法的程序已经写好了,就差这个接口的实现类了。
// 代理类 实现了接口
class DynamicProxy implements InvocationHandler {
    private Object target; // 真实对象

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

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

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

InvocationHandler接口中有一个方法invoke,这个invoke方法上有三个参数:

  • 第一个参数:Object proxy。代理对象。设计这个参数只是为了后期的方便,如果想在invoke方法中使用代理对象的话,尽管通过这个参数来使用。
  • 第二个参数:Method method。目标方法。
  • 第三个参数:Object[] args。目标方法调用时要传的参数。

代码示例

下面是一个更详细的 JDK 代理示例:

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

// 定义一个接口
interface HelloService {
    void sayHello(String name);
}

// 实现该接口的真实对象
class HelloServiceImpl implements HelloService {
    @Override
    public void sayHello(String name) {
        System.out.println("Hello, " + name + "!");
    }
}

// 代理类
class DynamicProxy implements InvocationHandler {
//  有了目标对象我们就可以在invoke()方法中调用目标方法了
    private Object target; // 真实对象

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

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

// 使用代理(客户端)
public class Main {
    public static void main(String[] args) {
        // 创建真实对象
        HelloService helloService = new HelloServiceImpl();
        // 创建代理对象
        HelloService proxy = (HelloService) Proxy.newProxyInstance(
            helloService.getClass().getClassLoader(),
            helloService.getClass().getInterfaces(),
            new DynamicProxy(helloService)
        );

        // 调用代理对象的方法
        String result = proxy.invoke("Proxy!");
        System.out.println("Result: " + result);
    }
}

代理类在运行时生成,通常不需要手动编写。
Java 动态代理可以通过 JDK 自带的 Proxy 类实现,或者通过第三方库(如 CGLIB)实现。
代理类是在运行时根据目标类自动生成的,因此比静态代理更为灵活。

CGLIB动态代理

CGLIB 动态代理:让你轻松理解的代理魔法

在 Java 编程中,我们经常需要对对象的方法做一些“额外操作”。比如,在调用一个方法前后加上日志、计时,或者对某些操作进行权限控制。要实现这种“增强”功能,我们可以用“代理”来帮忙。今天我带大家一起了解一种非常有趣的代理方式——CGLIB 动态代理

什么是 CGLIB?

CGLIB 是一个字节码生成库,简单来说,它能在运行时生成类的字节码(就是类的代码),并创建这些类的对象。相比于 JDK 动态代理需要实现接口的限制,CGLIB 可以直接代理一个没有实现接口的类。这就给我们带来了更多灵活性。

CGLIB 动态代理和 JDK 动态代理的区别

在介绍 CGLIB 前,我们先简单对比一下它和 JDK 动态代理的区别:

  • JDK 动态代理:只能代理实现了接口的类。使用的是 Java 自带的反射机制。
  • CGLIB 动态代理:不需要类实现接口,通过继承的方式来创建代理对象,所以它对那些没有接口的类也能发挥作用。

CGLIB 动态代理的工作原理

CGLIB 动态代理的工作原理是:通过继承目标类生成一个子类,重写目标类的方法来实现增强功能。可以把它想象成你有一个神奇的机器,可以直接复制一个类,然后在这个副本里修改一些方法的实现方式。

由于它是通过继承来实现的,所以目标类不能是 final 的(因为 final 类无法被继承)。另外,目标类里的 final 方法也不能被增强,因为这些方法不能被重写。

CGLIB 动态代理的使用示例

来看一个简单的代码示例,看看如何用 CGLIB 来代理一个类。

首先,我们要引入 CGLIB 库(可以通过 Maven 引入):

<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 net.sf.cglib.proxy.Enhancer;

public class CglibProxyExample {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        // 设置要代理的目标类
        enhancer.setSuperclass(TargetClass.class);
        // 设置回调函数,当目标方法被调用时,执行拦截逻辑
        enhancer.setCallback(new CustomMethodInterceptor());
        
        // 创建代理对象
        TargetClass proxy = (TargetClass) enhancer.create();
        // 调用代理对象的方法
        proxy.someMethod();
    }
}

// 目标类
class TargetClass {
    public void someMethod() {
        System.out.println("Executing target method");
    }
}

// 单独设置一个类来继承 MethodInterceptor
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

class CustomMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // 前增强逻辑
        long begin = System.currentTimeMillis();
        System.out.println("Before method execution");

        // 调用目标方法
        Object retValue = methodProxy.invokeSuper(target, args);

        // 后增强逻辑
        long end = System.currentTimeMillis();
        System.out.println("After method execution");
        System.out.println("耗时: " + (end - begin) + " 毫秒");

        // 返回目标方法的结果
        return retValue;
    }
}


class TargetClass {
    // 目标类的方法
    public void someMethod() {
        System.out.println("Executing target method");
    }
}

代码解析

  1. Enhancer:这是 CGLIB 提供的一个工具类,用来生成代理对象。

    • enhancer.setSuperclass(TargetClass.class):告诉 CGLIB 我们要代理哪个类,这里是 TargetClass
    • enhancer.setCallback(...):设置回调,也就是当代理对象的方法被调用时,我们要做什么。
  2. MethodInterceptor:这个接口可以让我们拦截方法调用,在方法执行前后加上一些自定义逻辑。

    • intercept 方法是我们增强逻辑的核心,比如这里我们在方法执行前后打印了一些日志。
  3. 代理效果:运行这个代码时,我们可以看到:

    • 在调用 someMethod() 之前打印了 Before method execution
    • 调用 someMethod() 时执行了目标方法的内容:Executing target method
    • 最后又打印了 After method execution

这就是 CGLIB 动态代理的增强效果——它让我们能够在不修改原有类代码的情况下,灵活地为方法添加“前后钩子”。

CGLIB 动态代理的应用场景

  • AOP(面向切面编程):在 Spring 中,CGLIB 经常被用来实现 AOP,当你想要给没有接口的类添加切面(例如日志、事务管理)时,CGLIB 就会派上用场。
  • 无需接口的场景:当我们想要代理的类没有实现接口时,可以考虑使用 CGLIB 来创建代理对象。

注意事项

  • 由于 CGLIB 是基于继承的,目标类不能是 final 的。
  • 如果目标类的方法是 final,那么这些方法将不能被代理增强。
  • 生成代理对象是有一定性能开销的,但在频繁调用时,CGLIB 的性能通常优于 JDK 动态代理。

总结

CGLIB 动态代理是 Java 中实现动态代理的一种强大工具,尤其是在类没有实现接口时,它的作用尤为明显。通过 CGLIB,我们可以在运行时生成类的代理,并在方法前后添加自定义逻辑,极大地提升了代码的灵活性和可扩展性。


网站公告

今日签到

点亮在社区的每一天
去签到