前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java 代理模式详解

java 代理模式详解

作者头像
程序员徐公
发布2018-09-17 16:02:01
6550
发布2018-09-17 16:02:01
举报

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1340323

简介

代理是什么?

代理也称“委托”,分为静态代理和动态代理,代理模式也是常用的设计模式之一,具有方法增强、高扩展性的设计优势。

代理的设计理念是限制对象的直接访问,即不能通过 new 的方式得到想要的对象,而是访问该对象的代理类。

这样的话,我们就保护了内部对象,如果有一天内部对象因为某个原因换了个名或者换了个方法字段等等,那对访问者来说一点不影响,因为他拿到的只是代理类而已,从而使该访问对象具有高扩展性。

然而,代理类可以实现拦截方法,修改原方法的参数和返回值,满足了代理自身需求和目的,也就是是代理的方法增强性。

其实代理,就好比是我们日常生活中的海外代购人员一样.


代理模式 UML

说到代理,我们先来复习一下代理设计模式 UML:如下图

  • Subject(抽象角色):声明真实对象和代理对象的共同接口;
  • ProxySubject(代理角色):代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
  • (RealSubject)真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。调用 RealSubject 的方法,都要经过 ProxySubject 进行代理。

静态代理模式的实现

一般来说,主要有以下几个步骤

  • 抽象一个接口 ISubject
  • 实现该接口 RealSubject
  • 创建代理对象类 ProxySubject
  • 客户端发起调用

抽象一个接口 ISubject

代码语言:javascript
复制
public interface ISubject {

    void doAction(String action);

}

实现该接口 RealSubject

代码语言:javascript
复制
public class RealSubject implements ISubject {


    public void doAction(String action) {
        // TODO Auto-generated method stub
        System.out.println("I am RealSubject, do action "+ action);
    }

}

创建代理对象类 ProxySubject

代码语言:javascript
复制
public class ProxySubject implements ISubject {

    ISubject mRealSubject;

    public ProxySubject(ISubject mRealSubject) {
        super();
        this.mRealSubject = mRealSubject;
    }



    public void doAction(String action) {
        // TODO Auto-generated method stub
        preRequest();
        mRealSubject.doAction(action);
        postRequest();
    }



    protected void postRequest() {
        // TODO Auto-generated method stub
        System.out.println("postRequest");

    }

    protected void preRequest() {
        // TODO Auto-generated method stub
        System.out.println("preRequest");

    }

}

客户端发起调用

代码语言:javascript
复制
    private static void testStatical() {
        RealSubject realSubject = new RealSubject();
        ProxySubject proxySubject = new ProxySubject(realSubject);
        proxySubject.doAction("play");

    }

将会看到以下 log

代码语言:javascript
复制
preRequest
I am RealSubject, do action play
postRequest

java 动态代理的实现

动态代理是指在运行时动态生成代理类。不需要我们像静态代理那个去手动写一个个的代理类。生成动态代理类有很多方式:Java动态代理,CGLIB,Javassist,ASM库等。这里主要说一下 Java 动态代理的实现。

相关类介绍

java 动态代理的实现,主要涉及到几个类

  • java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
代码语言:javascript
复制
// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy) 

// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces) 

// 方法 3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl) 

// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, 
    InvocationHandler h)
  • java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。
代码语言:javascript
复制
// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象
// 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
Object invoke(Object proxy, Method method, Object[] args)

java.lang.ClassLoader:这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。

实现步骤

  • 实现 InvocationHandler 接口,创建自己的调用处理器;
  • 为 Proxy 类指定 ClassLoader 对象和一组 interface ,从而来创建动态代理类;
  • 反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
  • 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
代码语言:javascript
复制
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
// 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
InvocationHandler handler = new InvocationHandlerImpl(..); 

// 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); 

// 通过反射从生成的类对象获得构造函数对象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); 

// 通过构造函数对象创建动态代理类实例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

实际使用过程更加简单,因为 Proxy 的静态方法 newProxyInstance 已经为我们封装了步骤 2 到步骤 4 的过程,所以简化后的过程如下:

  • 实现 InvocationHandler 接口,创建自己的调用处理器;
  • 通过 Proxy.newProxyInstance 生成动态代理类实例
代码语言:javascript
复制
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
InvocationHandler handler = new InvocationHandlerImpl(..); 

// 通过 Proxy 直接创建动态代理类实例
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, 
     new Class[] { Interface.class }, 
     handler );

实现步骤

  • 创建 ISubject
  • 实现 RealSubject
  • 通过 Proxy.newInstance 生成动态代理对象
代码语言:javascript
复制
public class DynamicProxyHandler implements InvocationHandler {

    private Object target;

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

    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }

    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("办事之前先收取点费用");
        System.out.println("开始办事");
        Object result = null;
        try {
            result = method.invoke(target, args);
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("办完了");
        return result;
    }



}
  • 客户端调用
代码语言:javascript
复制
ISubject subject = new RealSubject();
ISubject proxy = new DynamicProxyHandler(subject).getProxy();
proxy.doAction("play");

最终将输出以下 log

代码语言:javascript
复制
办事之前先收取点费用
开始办事
I am RealSubject, do action play
办完了

优缺点

优点

1)良好的扩展性。修改被代理角色并不影响调用者使用代理,对于调用者,被代理角色是透明的。

2)隔离,降低耦合度。代理角色协调调用者和被代理角色,被代理角色只需实现本身关心的业务,非自己本职的业务通过代理处理和隔离。

缺点

1)增加了代理类,实现需要经过代理,因此请求速度会变慢。


代理模式与装饰者模式的区别

UML类图基本没区别,都是实现同一个接口,一个类包装另一 个类。 两者的定义:

装饰器模式:能动态的新增或组合对象的行为

在不改变接口的前提下,动态扩展对象的功能。关于装饰者模式的,可以参考我的这一篇博客 装饰者模式及其应用

代理模式:为其他对象提供一种代理以控制对这个对象的访问

在不改变接口的前提下,控制对象的访问

装饰模式是“新增行为”,而代理模式是“控制访问”。关键就是我们如何判断是“新增行 为”还是“控制访问”。你在一个地方写装饰,大家就知道这是在增加功能,你写代理,大家就知道是在限制。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年08月03日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
    • 代理是什么?
    • 代理模式 UML
    • 静态代理模式的实现
      • 抽象一个接口 ISubject
        • 实现该接口 RealSubject
          • 创建代理对象类 ProxySubject
            • 客户端发起调用
            • java 动态代理的实现
              • 相关类介绍
                • 实现步骤
                • 优缺点
                  • 优点
                    • 缺点
                    • 代理模式与装饰者模式的区别
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档