阅读 190

DesignPattern - 代理模式【结构型】

欢迎关注微信公众号:FSA全栈行动 👋

一、代理模式介绍

代理模式(Proxy Pattern)属于结构模式,为其他对象提供一种代理以控制对这个对象的访问,客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。

核心作用:保护目标对象,增强目标对象(与装饰器模式类似)

  • 核心组成

    • Subject:抽象接口,目标对象类和代理类都要实现的一个抽象接口
    • Proxy:代理类,包含了对目标对象类的引用,从而可以随意的操作目标对象的方法
    • RealSubject:目标对象类,也叫委托类,或被代理类
  • 应用场景

    • 各大数据专营店,代理厂商进行销售对应的产品,代理商持有真正的授权代理书
    • 客户端不想直接访问实际的对象,或者访问实际的对象存在困难,通过一个代理对象来完成间接的访问
    • 想在访问一个类时做一些控制,或者增强功能
  • 分类

    • 静态代理:由程序员创建或工具生成代理类的源码,再编译代理类,即代理类和委托类的关系在程序运行前就已经存在
    • 动态代理:在运行期间使用动态生成字节码形式,动态创建代理类,常见的动态代理工具有 jdkproxy(基于反射原理,性能低)、cglib(基于 ASM 机制,生成被代理类的子类,高性能)
  • 优点

    • 可以在访问一个类时做一些控制,或增加功能
    • 操作代理类无须修改原本的源代码,符合开闭原则,系统具有较好的灵活性和可扩展性
  • 缺点

    • 增加系统复杂性和调用链路

二、代理模式代码实现

以下以模拟文件上传为案例,分别使用 静态代理 与 动态代理 测算上传耗时。

创建抽象接口:

说明:该接口是必须的,代理类与被代理类都需要实现

/**
 * Subject:抽象接口
 *
 * @author GitLqr
 */
public interface IUploader {
	void upload(File file);
}
复制代码

创建目标对象类,即被代理类:

/**
 * RealSubject:目标对象类
 *
 * @author GitLqr
 */
public class HttpUploader implements IUploader {
	@Override
	public void upload(File file) {
		try {
			Thread.sleep(500);
			System.out.println("使用 http 上传文件中...");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
复制代码

1、静态代理

  • 静态代理在代码编译时就确定了被代理的类
  • 静态代理的代理类必须要实现和被代理类相同的接口
  • 静态代理可以隐藏被代理类的具体实现;可以在不改变代理类的情况下增加额外的操作

创建静态代理类:

说明:静态代理类持有被代理对象引用,在对应的接口方法中,使用被代理对象来执行方法,并在方法执行前后记录时间戳,从而计算出方法耗时。

/**
 * 代理类:静态代理
 *
 * @author GitLqr
 */
public class StaticProxy implements IUploader {

	IUploader uploader;

	public StaticProxy(IUploader uploader) {
		super();
		this.uploader = uploader;
	}

	@Override
	public void upload(File file) {
		long start = System.currentTimeMillis();
		uploader.upload(file);
		long end = System.currentTimeMillis();
		System.out.println("耗时:" + (end - start) + "ms");
	}
}
复制代码

使用:

说明:被代理对象可以直接在代理类中创建,从而做到更好的隐蔽性,同时扩展性也会降低,看实际需要决定

public static void main(String[] args) {
    IUploader uploader = new HttpUploader();
    IUploader uploaderProxy = new StaticProxy(uploader);
    File file = new File("/");
    uploaderProxy.upload(file);
}
复制代码

2、动态代理

  • 动态代理在代码运行时才动态创建代理类
  • 动态代理相对于静态代理类,可以避免生成大量的代理类造成的冗余

动态代理可以通过 jdkproxy、cglib 等方式实现,以下以 jdkproxy(又称 JDK 动态代理) 为例

说明: jdkproxy 是 jdk 自带的,基于接口设计实现,只能代理接口中有的方法,且通过反射执行方法,性能低;cglib 需要依赖第三方库,但基于 ASM 机制,不依赖于接口,因为是 ASM 生成子类的方式(继承),性能也高一些

jdkproxy 涉及 2 个重要的类或接口:

  • java.lang.reflect.Proxy:动态创建出来的代理对象的类,它提供了许多方法,用的最多的是 Proxy#newProxyInstance() ,用于得到一个动态的代理对象
  • java.lang.reflect.InvocationHandler:是动态代理类要实现的接口,我们通过代理对象调用一个方法时,该方法会被转发给 InvocationHandlerinvoke() 方法来调用

创建动态代理对象:

public static void main(String[] args) {
    // 被代理对象
    IUploader uploader = new HttpUploader();

    ClassLoader classLoader = uploader.getClass().getClassLoader();
    Class<?>[] interfaces = uploader.getClass().getInterfaces();
    // 动态代理
    IUploader uploaderProxy = (IUploader) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            long start = System.currentTimeMillis();
            Object result = method.invoke(uploader, args); // 注意:方法的执行者是被代理对象!!
            long end = System.currentTimeMillis();
            System.out.println("耗时:" + (end - start) + "ms");
            return result;
        }
    });

    // 使用
    File file = new File("/");
    uploaderProxy.upload(file);
}
复制代码

如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有Android技术, 还有iOS, Python等文章, 可能有你想要了解的技能知识点哦~

文章分类
代码人生
文章标签