代理模式和简单实现

代理模式和简单实现

  • 代理模式
  • 动态代理和静态代理
    • 静态代理
      • 代码
    • 动态代理
      • JDK动态代理
      • CGLIB动态代理
      • 两种动态代理的区别
      • 动态代理的应用
    • 动态和静态代理的区别

代理模式

代理模式是一种结构型设计模式,其目的是通过创建一个代理对象来控制对另一个对象的访问。代理对象充当了被代理对象的中间人,客户端通过代理对象来间接访问被代理对象,从而可以在访问被代理对象前后进行一些额外的操作。

代理模式通常涉及三种角色:

  1. 抽象接口(Subject):定义了被代理对象和代理对象的共同接口,客户端通过这个接口来访问被代理对象。

  2. 被代理对象(Real Subject):实际执行业务逻辑的对象,客户端最终想要访问的对象。

  3. 代理对象(Proxy):实现了抽象接口,并在内部维护了一个被代理对象的引用。代理对象对客户端的请求进行一些额外处理,然后将请求委派给被代理对象。

代理模式可以应用于多种场景,包括:

  • 远程代理(Remote Proxy):在客户端和远程对象之间建立一个代理对象,客户端通过代理对象来访问远程对象,实现远程调用的功能。

  • 虚拟代理(Virtual Proxy):用于延迟加载对象,代理对象在真正需要时才创建和加载被代理对象,从而节省系统资源。

  • 保护代理(Protection Proxy):控制对敏感对象的访问,代理对象在访问被代理对象前进行权限检查,以确保客户端具有足够的权限。

  • 缓存代理(Cache Proxy):在访问对象时添加缓存机制,代理对象在访问被代理对象前检查缓存,如果存在缓存则直接返回缓存结果,从而提高系统性能。

代理模式可以提高系统的灵活性、安全性和性能,但同时也会增加系统的复杂性。因此,在设计中需要根据实际需求和场景来选择是否使用代理模式。

动态代理和静态代理

动态代理和静态代理都是代理模式的不同实现方式,它们在实现上有一些区别。

静态代理

静态代理是在编译时就已经确定了代理类和被代理类的关系。在静态代理中,代理类是直接编码实现的,它和被代理类实现了相同的接口或继承了相同的父类,从而可以实现相同的方法。静态代理中的代理类需要对被代理类的方法进行封装,并且在方法调用前后可以执行一些额外的操作。

静态代理的优点是实现简单,易于理解和控制。但缺点是代理类和被代理类之间的关系在编译时就已经确定,因此不够灵活,如果需要代理多个类的多个方法,就需要编写大量的代理类。

代码

package com.test.test_proxy;

//静态代理
public class StaticProxyDemo {
    public static void main(String[] args) {
        RealMovie realmovie = new RealMovie();
        Movie movie = new Cinema(realmovie);
        movie.play();
    }
}

interface Movie {
    void play();
}

//创建被代理类
class RealMovie implements Movie {
    @Override
    public void play() {
        System.out.println("您正在观看电影 《肖申克的救赎》");
    }
}

//创建代理类
class Cinema implements Movie {

    RealMovie movie;

    public Cinema(RealMovie movie) {
        this.movie = movie;
    }

    @Override
    public void play() {
        System.out.println("电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!");
        movie.play();
        System.out.println("电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!");
    }

}

动态代理

动态代理是在运行时动态生成代理类的一种代理方式。Java 中的动态代理是通过反射机制来实现的,它可以在运行时动态地创建代理类和对象,并在代理类中动态地处理被代理类的方法调用。

动态代理的优点是减少了编码工作量,提高了代码的灵活性。缺点是由于动态代理使用了反射机制,因此性能可能相对较低。

JDK动态代理

JDK 动态代理是 Java 提供的一种动态代理实现方式,它允许在运行时动态生成代理类和对象,并通过代理类来间接调用目标对象的方法。JDK 动态代理是基于接口的代理,它要求目标对象实现一个或多个接口。

JDK 动态代理的核心类和接口包括:

  1. java.lang.reflect.Proxy:代理类的主要类,用于生成代理类和对象。
  2. java.lang.reflect.InvocationHandler:代理对象的调用处理程序接口,定义了代理对象中的方法调用处理逻辑。
  3. java.lang.reflect.Method:代表目标对象的方法,被动态代理的接口中的方法都将被转发给 InvocationHandler 接口中的 invoke 方法进行处理。

动态代理的基本步骤如下:

  1. 创建 InvocationHandler 接口的实现类,实现 invoke() 方法,定义代理对象的方法调用逻辑。
  2. 使用 Proxy 类的静态方法 newProxyInstance() 创建代理对象,传入目标对象的类加载器、目标对象实现的接口列表和 InvocationHandler 对象。
  3. 使用返回的代理对象调用方法,方法调用会被转发给 InvocationHandler 的 invoke() 方法进行处理。

下面是一个简单的示例代码:

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

// 定义接口
interface Hello {
    void sayHello();
}

// 实现 InvocationHandler(调用处理器) 接口
class MyInvocationHandler implements InvocationHandler {
    private 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 invocation");
        Object result = method.invoke(target, args);
        System.out.println("After method invocation");
        return result;
    }
}

public class Main {
    public static void main(String[] args) {
        //创建被代理对象
        Hello realSubject = new Hello() {
            @Override
            public void sayHello() {
                System.out.println("Hello World");
            }
        };

        //创建 InvocationHandler(调用处理器)对象
        MyInvocationHandler handler = new MyInvocationHandler(realSubject);

        // 创建代理对象
        /*param1:被代理类的类加载器
        * param2:被代理类的接口,如果有多个,就是数组形式传入
        * param3:InvocationHandler(调用处理器)对象
        * */
        Hello proxy = (Hello) Proxy.newProxyInstance(
                realSubject.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(),
                handler
        );

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

输出:

Before method invocation
Hello World
After method invocation

在这个示例中,Hello 接口代表目标对象,MyInvocationHandler 实现了 InvocationHandler 接口,定义了对目标对象方法的调用处理逻辑。通过 Proxy.newProxyInstance() 方法创建代理对象,该方法需要传入目标对象的类加载器、目标对象实现的接口列表和 InvocationHandler 对象。最后,通过代理对象调用方法时,方法调用会被转发给 InvocationHandler 的 invoke() 方法进行处理。

CGLIB动态代理

CGLIB(Code Generation Library)是一个基于ASM开源项目,通过字节码生成技术来动态生成代理类的库。相比于JDK动态代理,CGLIB可以代理没有实现接口的类,它能够生成目标类的子类作为代理类。

CGLIB 动态代理的基本原理是:在运行时动态生成一个目标类的子类,并重写其中的方法来实现代理逻辑。被代理的方法在子类中被重写,并在重写的方法中调用代理逻辑。因此,使用 CGLIB 动态代理时,目标类无需实现接口,而代理类将会成为目标类的子类。

下面是一个简单的示例代码,演示了如何使用 CGLIB 动态代理:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

// 目标类
class RealSubject {
    public void sayHello() {
        System.out.println("Hello World");
    }
}

// 方法拦截器
class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method invocation");
        Object result = proxy.invokeSuper(obj, args); // 调用目标类的方法
        System.out.println("After method invocation");
        return result;
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建 Enhancer 对象
        Enhancer enhancer = new Enhancer();
        // 设置目标类
        enhancer.setSuperclass(RealSubject.class);
        // 设置回调对象
        enhancer.setCallback(new MyMethodInterceptor());

        // 创建代理对象
        RealSubject proxy = (RealSubject) enhancer.create();

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

输出:

Before method invocation
Hello World
After method invocation

在这个示例中,我们首先定义了一个目标类 RealSubject,然后通过 Enhancer 类创建一个代理类。通过 setSuperclass() 方法设置目标类,通过 setCallback() 方法设置方法拦截器。最后,通过 create() 方法创建代理对象。当调用代理对象的方法时,方法调用会被转发给方法拦截器进行处理。

需要注意的是,CGLIB 动态代理会生成目标类的子类作为代理类,因此目标类和代理类之间是继承关系,而不是实现了相同的接口关系。

两种动态代理的区别

JDK 动态代理和 CGLIB 动态代理是两种不同的动态代理实现方式,它们在实现上有一些区别,主要包括以下几点:

  1. 代理对象的类型

    • JDK 动态代理要求目标类必须实现一个或多个接口,代理对象通过实现相同的接口来代理目标类。
    • CGLIB 动态代理可以代理没有实现接口的类,它通过生成目标类的子类来实现代理。
  2. 实现原理

    • JDK 动态代理是通过 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口实现的,它在运行时动态生成代理对象,并通过反射机制来调用目标对象的方法。
    • CGLIB 动态代理是通过字节码生成技术(ASM)实现的,它在运行时动态生成目标类的子类,并在子类中重写被代理的方法,从而实现代理逻辑。
  3. 性能

    • 由于 JDK 动态代理是基于接口的代理,它需要使用反射机制来调用目标对象的方法,因此性能可能相对较低。
    • CGLIB 动态代理是基于继承的代理,它不需要使用反射机制,性能通常比 JDK 动态代理略高。
  4. 应用场景

    • JDK 动态代理适用于需要代理的类实现了接口的情况,它更适合于对接口进行代理、AOP(面向切面编程)等场景。
    • CGLIB 动态代理适用于不需要目标类实现接口的情况,它更适合于代理类对目标类的扩展、延迟加载等场景。

综上所述,JDK 动态代理和 CGLIB 动态代理各有优劣,适用于不同的场景。在选择时,可以根据具体的需求和情况来决定使用哪种动态代理方式。

动态代理的应用

框架中,像servlet的filter、包括spring提供的aop以及struts2的拦截器都使用了动态代理功能。我们日常看到的mybatis分页插件,以及日志拦截、事务拦截、权限拦截这些几乎全部由动态代理的身影。

动态和静态代理的区别

  • 静态代理是在编译时就确定了代理类和被代理类的关系,而动态代理是在运行时动态生成代理类的。
  • 静态代理需要为每一个被代理类编写一个代理类,而动态代理可以代理任意一个实现了接口的类。
  • 静态代理相对简单,易于理解和控制,而动态代理更加灵活,但性能相对较低。

你可能感兴趣的:(代理模式)