java 代理模式 3/23

代理模式

1. 代理模式介绍

代理模式也称为委托模式,它是一项基本技巧。许多其他的模式(状态模式、策略模式、访问者模式)本质实在更特殊的场合采用了代理模式。代理模式可以提供非常好的访问控制

1.1 代理模式结构
  • [x] Subject(抽象主题角色):抽象主题类可以是抽象类也可以是接口,定义业务;
  • [x] RealSubject(具体主题角色):被委托角色、被代理角色、业务逻辑的具体执行;
  • [x] Proxy(代理主题角色):委托类、代理类、负责对真是角色的应用,把所有抽象主题类定义的业务执行限制委托给真实主题角色来实现,并在真实主题角色执行业务前后做预处理和善后处理工作;

Subject抽象主题类

public interface Subject{
    public void request();
}

RealSubject具体主题角色,正常的业务实现类

public class RealSubject implement Subject{
    //实现方法
    public void request(){
        
    }
}

Proxy代理类,代理模式的核心

public class Proxy implements Subject{
    //要代理哪个实现类
    private Subject subject = null;
    //默认被代理者
    pubilc Proxy(){
        this.subject = new Proxy();
    }
    //使用构造方法传递代理者
    pulbic Proxy(Object... object){
        
    }
    //实现
    public void request(){
        this.before();
        this.subject.request();
        this.after();
    }
    //预处理
    private void before(){
        //do something;
    }
    //善后处理
    private void after(){
        //do something;
    }

一个代理类可以代理多个被委托者(被代理者),这是由具体场景来决定的。最简单的就是一个主题类一个代理类。通常情况下一个主题类一个代理类也足够了,具体代理哪个实现类由搞成模块来决定,也就是在构造方法中指派传递;我们上述代理类Proxy中可以添加如下构造函数:

public  Proxy(Subject subject){
    this.subject = subject;
}
1.2 静态代理&动态代理
1.2.1 静态代理

由程序员创建或者工具生成代理类的源码,在编译代理类。亦就是在程序运行前已经存在了代理累的字节码文件了,代理类和委托类的关系在运行前就确定了,如下:

  1. 抽象主题角色
/**
 * 描述:代理接口,处理给定名字的任务
 *
 * @author biguodong
 * Create time 2018-12-16 下午1:08
 **/
public interface Subject {

    /**
     * 执行给定任务的名字
     * @param taskName
     */
    void dealTask(String taskName);
}
  1. 具体处理业务的委托类
/**
 * 描述:真正执行任务的类,实现了代理接口
 * @author biguodong
 * Create time 2018-12-16 下午4:34
 **/
public class RealSubject implements Subject{

    /**
     * 执行给定任务的名字
     * @param taskName
     */
    @Override
    public void dealTask(String taskName) {
        System.out.println("正在执行任务:" + taskName);
        try{
            TimeUnit.SECONDS.sleep(5);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
  1. 代理类
/**
 * 描述:静态代理类的演示代码
 * @author biguodong
 * Create time 2018-12-16 下午4:38
 **/
public class ProxySubject implements Subject{

    /**
     * 代理类持有一个委托类的对象引用
     */
    private Subject delegate;

    public ProxySubject(Subject delegate) {
        this.delegate = delegate;
    }

    /**
     * 将请求分派给委托类执行,记录任务执行时间
     * @param taskName
     */
    @Override
    public void dealTask(String taskName) {
        long beginTime = System.currentTimeMillis();
        delegate.dealTask(taskName);
        long finishTime = System.currentTimeMillis();
        System.out.println("执行任务耗时:" + (finishTime - beginTime) + "毫秒");
    }
}
  1. 代理类工厂
/**
 * 描述:静态代理工厂
 * @author biguodong
 * Create time 2018-12-16 下午4:42
 **/
public class SubjectStaticFactory {
    /**
     * 客户端调用此方法获取代理对象
     * 客户端并不知道获取到的是代理类对象还是为拖类对象
     * @return
     */
    public static Subject getInstance(){
        return new ProxySubject(new RealSubject());
    }
}
  1. 调用客户端
/**
 * 描述:调用客户端
 * @author biguodong
 * Create time 2018-12-16 下午4:45
 **/
public class Client {
    public static void main(String[] args) {
        Subject subject = SubjectStaticFactory.getInstance();
        subject.dealTask("some task");
    }
}
  1. 执行结果
正在执行任务:some task
执行任务耗时:5008毫秒

静态代理优缺点

优点:

  • [x] 业务类只需要关心业务逻辑本身,保证了业务类的重用性,这是代理的共同优点;

缺点:

  • [x] 代理对象的一个接口只服务于一种类型的对象,如果代理对象很多,势必要为每一种方法都进行代理,在规模大时就无法胜任了;
  • [x] 代理接口方法的变更,不仅所有实现类需要重写实现,所有的代理类业务也需要变更,增加了代码维护的复杂度;
1.2.2 动态代理

动态代理类的源码在JVM runtime时期根据反射等机制动态地生成,代理类和委托类的关系也是在程序运行时确定的;

1.java.lang.relect.Proxy:java生成所有动态代理类的父类,提供一组静态方法来为一组接口动态的生成代理类及其对象。

//获取指定代理对象所关联的调用处理器
public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException
//指定接口列表和类装载器的代理类
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
//是否为动态代理类
public static boolean isProxyClass(Class<?> cl)
//指定类加载器、一组接口以及调用处理器生成动态代理类实例
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

2. java.lang.reflect.InvocationHandler: 调用处理器接口,自定义了invoke方法,用来集中处理动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。

/**
* proxy 代理类实例
* method 被调用的方法对象
* args 调用方法参数
* 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
*/
public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

3. java.lang.ClassLoader:类加载器,负责把类的字节码装在到JVM 并为其定义类对象,然后才能被使用,它与普通类的唯一区别就是其字节码是由JVM在运行时动态生成的,而不是预存在任何一个.class文件中

4. 实现动态代理的步骤

  • [x] 实现InvocationHandler接口,创建自己的调用处理器;
  • [x] 给Proxy类提供ClassLoader、代理接口数组,创建动态代理类;
  • [x] 利用反射机制使用调用处理器对象得到代理类的构造函数;
  • [x] 利用代理类的构造函数创建动态代理类对象

Proxy类的静态方法newInstance 对上述步骤作了封装,简化了动态代理对象的获取过程

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);
        final Class<?>[] intfs = interfaces.clone();
        /*
         * 缓存中查找或生成代理类
         */
        Class<?> cl = getProxyClass0(loader, intfs);
        /*
         * 使用特定的调用处理器invoke构造方法
         */
        try {
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            //生成代理类实例
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

5. 创建自己的调用处理器实现动态代理

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

/**
 * 描述:自定义调用处理器
 * @author biguodong
 * Create time 2018-12-16 下午6:05
 **/
public class SubjectInvocationHandler implements InvocationHandler {
    private Object delegate;

    public SubjectInvocationHandler(Object delegate) {
        this.delegate = delegate;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long beginTime = System.currentTimeMillis();
        //利用反射将请求分派给为拖类对象
        method.invoke(delegate, args);
        long finishTime = System.currentTimeMillis();
        System.out.println("动态代理-执行任务耗时:" + (finishTime - beginTime) + "毫秒");
        return null;
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * 描述:动态代理对象的工厂
 *
 * @author biguodong
 * Create time 2018-12-16 下午6:08
 **/
public class DynProxyFactory {

    public static Subject getInstance(){
        Subject delegate = new RealSubject();
        InvocationHandler handler = new SubjectInvocationHandler(delegate);
        Subject proxy;
        proxy = (Subject) Proxy.newProxyInstance(delegate.getClass().getClassLoader(),
                delegate.getClass().getInterfaces(),
                handler);
        return proxy;
    }
}
/**
 * 描述:动态代理调用客户端
 * @author biguodong
 * Create time 2018-12-16 下午6:10
 **/
public class Client {

    public static void main(String[] args) {
        Subject proxy = DynProxyFactory.getInstance();
        proxy.dealTask("动态代理任务");
    }
}

结果

正在执行任务:动态代理任务
动态代理-执行任务耗时:5008毫秒

6.动态代理特点

6.1动态生成的代理类本身特点:

  • [x] 包: 如果所代理接口都是public的,那么它将被定义在顶层包(包路径为空),如果所代理接口中有非public的接口(因为接口不能被定义为protectprivate,只能是publicpackage访问级别),那么将被定义在接口所在包,这样最大程度的确保动态代理类不会因为包管理问题导致不能成功被访问;
  • [x] 类修饰符: 代理类有finalpublic ,可以被所有类访问,但是不能被继承;
  • [x] 类名: 格式为"$ProxyN",如果对同一组接口(包括接口的排列顺序相通)试图重复创建动态代理类,会返回之前已经创建好的代理类的类对象;
  • [x] 类继承关系: Proxy是所有代理类的父类,这个规则适用于所有由Proxy创建的动态代理类,而且代理类还实现了其所代理的一组接口,这也是为什么它能够被安全地类型转化为其所代理的某一接口的原因;

6.2动态代理类实例的特点:

  • [x] 因为每个实例都会关联一个调用处理器对象,可以通过Proxy的静态方法getInvocationHandler来获取。在代理类实例上调用其代理的接口中声明的方法时,这些方法最终都会由调用处理器的invoke方法来执行。此外值得注意的是,代理类的根类java.lang.Object中有三个方法也同样会被分派到调用处理器的invoke方法中执行,它们分别是hashCodeequalstoString可能原因有:

  • [x] 1. 这些方法是public且非final的,能够被代理覆盖;

  • [x] 2. 这些方法呈现出一个类的某些特殊属性,具有一定区分度,为了保证代理类与委托类对外的一致性,这三个方法也应该被分派到为拖类执行。

6.3被代理的接口特点:

  • [x] 不能有重复的接口,否则会编译错误;
  • [x] 这些接口对类装载器必须可见,否则无法链接它们,导致类定义失败;
  • [x] 被代理的所有非public的接口必须在同一个包中,否则代理类生成会失败;
  • [x] 接口数不能超过65536 jvm 规定;

6.4异常处理方面的特点:

  • [x] 异常抛出,调用处理器接口上理论上可以抛出任何类型的异常。但是必须要知道的是,当子类覆盖父类或实现父接口的方法时,抛出的异常必须在源方法支持的异常列表内,除非父接口中的方法支持抛出Throwable异常。如果在invoke方法中的确产生了接口方法声明中不支持的异常,会抛出
    UndeclaredThrowableExeption异常,是RuntimeException

7. 动态代理的有点和缺点

优点:

  • [x] 接口中声明的方法都会被转移到调用处理器(InvocationHandler.invoke)方法执行,在方法较多时,可以灵活处理配置;不需要像静态代理那样对每一个方法进行中转;

缺点:

  • [x] 无法摆脱仅支持interface桎梏;

2. 代理模式总结

优点:

  • [x] 职责清晰,真实的角色就是实现实际的业务逻辑,无需关心其他非本职责的事务,通过后期的代理完成一件事务,编程更加简洁清晰;
  • [x] 高扩展性,具体主题角色是随时都会发生变化的,只要实现了接口,崩管它如何变化,都逃脱不了接口的控制。代理类可以完全再不做任何修改的情况下使用;
  • [x] 智能化,Struts是把表单元素映射到对象上;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,085评论 1 32
  • 设计模式概述 在学习面向对象七大设计原则时需要注意以下几点:a) 高内聚、低耦合和单一职能的“冲突”实际上,这两者...
    彦帧阅读 3,734评论 0 14
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,182评论 11 349
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,865评论 6 13
  • 抬头相思低头泪 佳人何时归 镜花水月 美了世人眼 痛我一人心 如若你好 我愿化作佛前一粒芥子 终日为你...
    与寂寞为伍阅读 206评论 0 0