Java基础之代理模式及动态代理

  1. 代理模式
  2. 代理模式角色定义
  3. 静态代理
    3.1 静态代理实例
    3.2 静态代理的缺点
  4. 动态代理
    4.1 基于JDK原生动态代理实现

1. 代理模式

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

通过代理模式可以做到:

  1. 隐藏委托类的具体实现。
  2. 实现客户与委托类的解耦,在不改变委托类代码的情况下添加一些额外的功能(日志、权限)等。

2. 代理模式角色定义

三类对象:

  • Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法。
  • RealSubject(真实主题角色):真正实现业务逻辑的类。
  • Proxy(代理主题角色):用来代理和封装真实主题。
代理模式角色

3. 静态代理

静态代理是指代理类在程序运行前就已经存在。

3.1 静态代理实例

首先定义一组接口Sell,用来提供广告和销售等功能。然后提供Vendor类(厂商,被代理对象)和Shop(超市,代理类),它们分别实现了Sell接口。

Sell接口定义如下:

package javaBasic.proxy;

/**
 * 首先定义一组接口Sell,用来提供广告和销售等功能。
 */
public interface Sell {

    /**
     * 出售
     */
    void sell();

    /**
     * 广告
     */
    void ad();

}

Vendor类定义如下:

package javaBasic.proxy;

public class Vendor implements Sell {
    @Override
    public void sell() {
        System.out.println("Shop sell goods");
    }

    @Override
    public void ad() {
        System.out.println("Shop advert goods");
    }
}

Shop类定义如下:

package javaBasic.proxy;

/**
 * 其中代理类Shop通过聚合的方式持有了被代理类Vendor的引用,
 * 并在对应的方法中调用Vendor对应的方法。
 * 在Shop类中我们可以新增一些额外的处理,比如筛选购买用户、记录日志等操作。
 */
public class Shop implements Sell{

    private Sell sell;

    public Shop(Sell sell){
        this.sell = sell;
    }

    @Override
    public void sell() {
        System.out.println("代理类Shop, 处理sell");
        sell.sell();
    }

    @Override
    public void ad() {
        System.out.println("代理类Shop, 处理ad");
        sell.ad();
    }
}

其中代理类Shop通过聚合的方式持有了被代理类Vendor的引用,并在对应的方法中调用Vendor对应的方法。在Shop类中我们可以新增一些额外的处理,比如筛选购买用户、记录日志等操作。

客户端使用代理类的方式:

package javaBasic.proxy;

/**
 * 针对客户看到的是Sell接口提供了功能,而功能又是由Shop提供的。
 * 我们可以在Shop中修改或新增一些内容,而不影响被代理类Vendor。
 */
public class StaticProxy {

    public static void main(String[] args) {
        // 供应商---被代理类
        Vendor vendor = new Vendor();

        // 创建供应商的代理类Shop
        Sell sell = new Shop(vendor);

        // 客户端使用时面向的是代理类Shop。
        sell.ad();
        sell.sell();

    }

}

在上述代码中,针对客户看到的是Sell接口提供了功能,而功能又是由Shop提供的。我们可以在Shop中修改或新增一些内容,而不影响被代理类Vendor。

3.2 静态代理的缺点

  1. 当需要代理多个类时,代理对象要实现与目标对象一致的接口。要么,只维护一个代理类来实现多个接口,但这样会导致代理类过于庞大。要么,新建多个代理类,但这样会产生过多的代理类。
  2. 当接口需要增加、删除、修改方法时,目标对象与代理类都要同时修改,不易维护。

4. 动态代理

动态代理是指代理类在程序运行时进行创建的代理方式。这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据Java代码中的“指示”动态生成的。
相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。

4.1 基于JDK原生动态代理实现

实现动态代理通常有两种方式:JDK原生动态代理和CGLIB动态代理。这里,我们以JDK原生动态代理为例。

JDK动态代理主要涉及两个类:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。
InvocationHandler接口定义了如下方法:

/**
 * 调用处理程序
 */
public interface InvocationHandler { 
    Object invoke(Object proxy, Method method, Object[] args); 
}

实现了该接口的中介类用做“调用处理器”,当调用代理类对象的方法时,这个调用会直接跳转到invoke方法之中,代理类对象作为proxy参数传入,参数method标识了具体调用的是代理类的哪个方法,args为该方法的参数。这样对代理类中的所有方法的调用都会变为对invoke的调用,可以在invoke方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)。

Proxy类用来实例化指定代理对象所关联的调用处理器。

下面以添加日志为例演示动态代理。
首先是建立LogHandler类:

package javaBasic.proxy;

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

public class LogHandler implements InvocationHandler {

    Object target; //被代理的对象, 实际的方法执行者

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target,args);
        after();
        return result;
    }


    //调用invoke方法之前执行
    private void before() {
        System.out.println(String.format("log start time [%s] ", new Date()));
    }

    //调用invoke方法之后执行
    private void after() {
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}

客户端编写程序使用动态代理代码如下:

package javaBasic.proxy;

import java.lang.reflect.Proxy;

public class DynamicProxyMain {

    public static void main(String[] args) {
        //创建中介类实例
        LogHandler logHandler = new LogHandler(new Vendor());

        //获取代理类实例
        Sell sell = (Sell) Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[]{Sell.class}, logHandler);

        //通过代理类对象调用代理类方法,实际上会转到invoke方法(LogHandler之中的sell方法)调用
        //这个代理类之中的方法调用是通过反射实现的
        sell.sell();
        sell.ad();
    }

}

执行后结果(已经成功为我们的被代理类统一添加了执行方法之前和执行方法之后的日志):

log start time [Fri Aug 21 09:39:04 AEST 2020] 
Shop sell goods
log end time [Fri Aug 21 09:39:04 AEST 2020] 
log start time [Fri Aug 21 09:39:04 AEST 2020] 
Shop advert goods
log end time [Fri Aug 21 09:39:04 AEST 2020] 

动态代理的原理:

执行上述方法后会生成一个$Proxy0.class类文件。该类继承了Proxy类,并且实现了被代理的所有接口,以及equals、hashCode、toString等方法。

由于动态代理类继承了Proxy类,所以每个代理类都会关联一个InvocationHandler方法调用处理器。

类和所有方法都被public final修饰,所以代理类只可被使用,不可以再被继承。

每个方法都有一个Method对象来描述,Method对象在static静态代码块中创建,以“m+数字”的格式命名。

调用方法的时候通过super.h.invoke(this,m1,(Object[])null);调用。其中的super.h.invoke实际上是在创建代理的时候传递给Proxy.newProxyInstance的LogHandler对象,它继承InvocationHandler类,负责实际的调用处理逻辑。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335