浅谈代理模式及其在Java中的实现

代理模式是常用的结构型设计模式之一,当无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口。在Java中代理实现分为静态代理和动态代理,本文将简要描述代理模式及其在Java中的实现。

代理模式

定义

给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。

结构

代理模式UML图

注意:上图的注释部分是针对Proxy类的request方法。

由图可知,代理模式存在以下3种角色:

  • Subject:被代理类和代理类要实现的公共接口(request())
  • ConcreteSubject:要被代理的类,又叫委托类,它定义了代理角色所代表的真实对象
  • Proxy:代理类,代理对象扮演着中介的角色,增加被代理类的某些功能或去掉了某些服务

简单实现

Subject接口:

<pre>
public interface Subject {
void request();
}
</pre>

ConcreteSubject类:
<pre>
public class ConcreteSubject implements Subject {
@Override
public void request() {
System.out.println("=======具体子类的request=======");
}
}
</pre>

Proxy类:
<pre>
public class Proxy implements Subject {
private ConcreteSubject subject;//被代理对象

public Proxy(ConcreteSubject subject){
    this.subject=subject;
}


public void preRequest(){
    System.out.println("=======代理的preRequest=======");
}

@Override
public void request() {
    preRequest();
    subject.request();//调用被代理对象的方法
    postRequest();
}

public void postRequest(){
    System.out.println("=======代理的postRequest=======");
}

}
</pre>

客户端测试类:
<pre>
public class Test {

public static void main(String[] args) {
    //1.创建被代理类对象
    ConcreteSubject subject=new ConcreteSubject();
    //2.创建代理对象,使用构造注入把被代理类对象注入到代理对象中
    Proxy proxy=new Proxy(subject);
    //3.调用代理对象的相应方法
    proxy.request();
}

}
</pre>

运行测试类,应该看到如下结果:

以上,就实现了一个最简单的代理模式。接下来看一下常见的几种代理模式及其应用场景。

常见代理模式及应用场景

  1. 远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一个本地的代理对象,这
    个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又称为大使
    (Ambassador)。
  2. 虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较
    小的对象来表示,真实对象只在需要时才会被真正创建。
  3. 保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
  4. 缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端
    可以共享这些结果。
  5. 智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。

其中,智能引用代理是使用得最广泛的一种代理,适合的应用如如:日志处理、权限管理、事务处理等。

代理的实现

代理模式的实现分为静态代理和动态代理。

静态代理

概念

由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类>的字节码文件,代理类和委托类的关系在运行前就确定了。Java编译完成后代理类是一个实际的 class 文件。

前面举的例子就是一个简单的静态代理。

静态代理的实现可分为继承和聚合,其中继承容易导致类膨胀,因此推荐的实现方式是聚合。

静态代理优缺点

优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。

缺点:

  1. 代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
  2. 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

另外,如果要按照上述的方法使用代理模式,那么真实角色(委托类)必须是事先已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色(委托类),该如何使用代理呢?这个问题可以通过Java的动态代理类来解决。

动态代理

概念

动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。

实现方式

Java中实现动态代理主要有两种典型方法:

  1. 使用JDK实现动态代理
  2. 使用CGLIB实现动态代理

我们主要来看一下第一种方法。

JDK实现动态代理

JDK实现动态代理
步骤
  1. 创建被代理的类及接口
  2. 创建一个接口实现InvocationHandler的类(调用处理器),它必须实现invoke方法
  3. 调用Proxy的静态方法,动态创建一个代理类的对象
newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
  1. 通过代理调用方法
代码举例

看一个最简单的例子,大部分步骤已注释说明。

Subject接口:
<pre>
/**

  • 被代理类和代理类要实现的公共接口
    */
    public interface Subject {
    void request();
    }
    </pre>

要被代理的类:
<pre>
/**

  • Subject接口实现
    */
    public class ConcreteSubject implements Subject{

    @Override
    public void request() {
    System.out.println("==========被代理对象的request方法==========");
    }

}
</pre>

InvocationHandler实现类:
<pre>
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**

  • 代理类的调用处理器
    */
    public class ProxyHandler implements InvocationHandler {
    //被代理对象
    private Subject target;

    public ProxyHandler(Subject target) {
    this.target=target;
    }

    /*

    • 参数:
    • proxy:表示最终生成的代理类对象,注意不是被代理的对象
    • method:被代理对象的方法
    • args:方法的参数
    • 返回值:
    • Object方法的返回值
      */
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.println("=========before===========");
      Object object=method.invoke(target);
      System.out.println("=========after===========");
      return object;

    }

}
</pre>
测试类:
<pre>
import java.lang.reflect.Proxy;

public class ProxyTest {

public static void main(String[] args) {
    //1.创建被代理对象
    ConcreteSubject subject=new ConcreteSubject();
    //2.创建调用处理器对象
    ProxyHandler proxyHandler=new ProxyHandler(subject);
    //3.动态生成代理对象
    Class<?> cls=ConcreteSubject.class;//获得被代理类的Class对象
    Subject proxySubject=(Subject)Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), proxyHandler);
    //4.通过代理对象调用方法
    proxySubject.request();
    
}

}
</pre>

运行测试类,可以得到以下结果:


至此,用JDK实现了最简单的动态代理。
其中需要注意的地方是:

  1. 需要创建一个java.lang.reflect.InvocationHandler的实现,作为代理类调用处理器。该接口只有一个方法:
Object invoke(Object proxy, Method method, Object[] args)
*注:参数说明见上文代码*
  1. 调用
    java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
    获得代理实例

参数说明:

loader:定义代理类的类加载器

interfaces:代理类需要实现的接口列表

h:调用处理器,代理对象将会将方法调用转发给它

返回值:

Object:一个由上述参数所指定的代理实例

动态代理实现思路

实现功能:通过Proxy的newProxyInstance返回代理对象

  1. 声明一段源码(动态代理)
  2. 编译源码(JDK Compiler API),产生新的类(代理类)
  3. 将这个类load到内存当中,产生一个新的对象(代理对象)
  4. return代理对象

注:可通过慕课网:模拟JDK动态代理实现思路分析及简单实现了解

CGLIB

JDK提供的实现动态代理的方法十分强大,但是也有一定的限制,主要的限制是:

只能代理实现接口的类,没有实现接口的类不能实现JDK动态代理

可以使用CGLIB来解决该问题。CGLIB的特点是:

  • 针对类来实现代理
  • 对指定目标类生成一个子类,通过方法拦截技术拦截所有父类方法的调用
  • 由于使用继承实现,因此不能为final修饰的类或方法实现代理

动态代理的应用

AOP(面向切面编程),即在不改变原有类方法的基础上,增加一些额外的业务逻辑。

Spring的AOP就是通过JDK动态代理跟CGLIB动态代理来实现的。

默认的策略是如果目标类是接口,则使用JDK动态代理技术,如果目标对象没有实现接口,则默认会采用CGLIB代理。

参考:

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

推荐阅读更多精彩内容