Java常用设计模式-代理模式

简介: Java常用设计模式-代理模式

代理模式是一种设计模式,其意义在于生成一个占位,来代替真实的对象,从而控制对真实对象的访问。 其实在现实生活中,代理很容易理解。假设这样一个场景:你的公司是一家服装公司,客户来定制服装,肯定不是直接找公司的裁缝谈的,而是去找商务谈,这时客户就会认为商务就代表公司。

网络异常,图片无法展示
|

可见这是一个间接的过程,那么商务(代理对象)的意义何在?商务可以进行一些额外逻辑例如谈判、价格等等,也就是说,代理的作用是:在委托对象访问之前或者之后加入对应逻辑,或者根据其它条件判断是否要访问委托对象,这个场景很显然商务控制了客户对裁缝的访问。

那么我们的代码示例也将以此为场景,一一讲解各个代理模式。

在讲解之前,我们先定义衣服实体类衣服制作接口裁缝类(实现衣服制作接口) 这几个基本类:

衣服实体类:

packagecom.example.javaproxy.model;
importlombok.Getter;
importlombok.Setter;
/*** 衣服类*/@Getter@SetterpublicclassClothing {
/*** 衣服名*/privateStringname;
/*** 尺码(S,M,L,XL,XXL)*/privateStringsize;
}

衣服制作接口:

packagecom.example.javaproxy.service;
importcom.example.javaproxy.model.Clothing;
/*** 衣服制作接口*/publicinterfaceClothesMaking {
/*** 衣服制作* @param name 衣服名* @param size 衣服大小* @return 衣服实例*/ClothingmakeClothing(Stringname, Stringsize);
}

裁缝类(实现衣服接口,为委托对象):

packagecom.example.javaproxy.service.impl;
importcom.example.javaproxy.model.Clothing;
importcom.example.javaproxy.service.ClothesMaking;
/*** 裁缝(委托类)*/publicclassTailorimplementsClothesMaking {
@OverridepublicClothingmakeClothing(Stringname, Stringsize) {
Clothingclothing=newClothing();
clothing.setName(name);
clothing.setSize(size);
returnclothing;
   }
}

1,静态代理

静态代理其实很简单,相当于直接创建代理人类,间接地访问裁缝类。

网络异常,图片无法展示
|

我们直接上代码,代理人(类):

packagecom.example.javaproxy.proxy.staticproxy;
importcom.example.javaproxy.model.Clothing;
importcom.example.javaproxy.service.ClothesMaking;
importcom.example.javaproxy.service.impl.Tailor;
/*** 裁缝代理人(代理类,静态代理)*/publicclassTailorStaticAgentimplementsClothesMaking {
/*** 代理人的裁缝实例(委托对象)*/privateClothesMakingtailor=newTailor();
/*** 向代理人定制衣服** @param name   衣服名字* @param size 客户身高* @return 衣服*/@OverridepublicClothingmakeClothing(Stringname, Stringsize) {
System.out.println("[进入静态代理逻辑]");
System.out.println("裁缝代理人已接到需求!");
returntailor.makeClothing(name, size);
   }
}

然后在主方法测试:

ClothesMakingagent=newTailorStaticAgent();
ClothingcustomClothing=agent.makeClothing("衬衫", "XL");
System.out.println("得到衣服名:"+customClothing.getName() +";得到衣服尺码:"+customClothing.getSize());

结果:

网络异常,图片无法展示
|

可见静态代理非常容易理解,就是新建一个代理类,利用这个代理类(裁缝的代理人) 去访问 委托类(裁缝),客户只需要找代理人即可间接地访问裁缝。

可见静态代理中,代理类和委托类需要实现同一个接口,且静态代理是一种编译后再代理的模式,不太利于扩展。

2,动态代理

上述例子可见,静态代理的代理类编译后也变为class文件,而动态代理是在运行时动态生成代理对象的,在运行时动态生成类字节码,并加载到JVM中。

(1) JDK动态代理

利用JDK自带的API实现在内存中创建动态的代理对象,我们无需向上面静态代理一样建立代理类,只需要建立一个代理逻辑类,这个逻辑类需要使用InvocationHandler接口,在逻辑类里面写下代理方法逻辑,并将代理对象和委托对象建立联系即可,然后这个代理逻辑类就可以生成动态代理对象。

网络异常,图片无法展示
|

这里贴上代理逻辑类的代码:

packagecom.example.javaproxy.proxy.jdkproxy;
importcom.example.javaproxy.service.ClothesMaking;
importjava.lang.reflect.InvocationHandler;
importjava.lang.reflect.Method;
importjava.lang.reflect.Proxy;
/*** JDK动态代理逻辑实现*/publicclassJDKProxyBindimplementsInvocationHandler {
/*** 代理人的裁缝代理实例(委托对象)*/privateClothesMakingtailor;
/*** 将委托对象和代理对象建立关系** @param origin 委托对象* @return 代理对象*/publicObjectbind(ClothesMakingorigin) {
this.tailor=origin;
returnProxy.newProxyInstance(tailor.getClass().getClassLoader(), tailor.getClass().getInterfaces(), this);
   }
/*** 代理逻辑方法** @param proxy  代理对象* @param method 当前调度方法* @param args   当前方法参数* @return 代理结果* @throws Throwable 异常*/@OverridepublicObjectinvoke(Objectproxy, Methodmethod, Object[] args) throwsThrowable {
System.out.println("[进入JDK动态代理逻辑]");
System.out.println("裁缝代理人已接到需求!");
// 调用裁缝(委托类)的方法Objectresult=method.invoke(tailor, args);
System.out.println("衣服定制完成!");
returnresult;
   }
}

然后去主方法测试:

// 首先创建JDK代理逻辑实现类的实例,用于将代理对象和委托对象联系起来JDKProxyBindjdkProxyBind=newJDKProxyBind();
// 新建裁缝实例,绑定关系ClothesMakingtailor=newTailor();
ClothesMakingagent= (ClothesMaking) jdkProxyBind.bind(tailor);
// 这个时候agent对象已经是一个代理对象了,它会进入代理的逻辑方法invoke中去ClothingcustomClothing=agent.makeClothing("衬衫", "XL");
System.out.println("得到衣服名:"+customClothing.getName() +";得到衣服尺码:"+customClothing.getSize());

结果:

网络异常,图片无法展示
|

可见JDK动态代理,并没有手动建立代理类,只是写了代理人的代理逻辑部分。其主要分为两大步:

  1. 建立代理对象和委托对象的关系:在上述我们建立了代理逻辑类,其中定义了bind方法,方法中首先用类的属性tailor保存了委托对象,然后通过Proxy类的newProxyInstance方法,建立代理对象。这个方法有三个参数:
  • 第一个是类加载器,使用委托类的加载器
  • 第二个是要把生成的动态代理对象挂载至哪个接口之下,这里很显然就是要挂在委托类的接口下
  • 第三个是定义实现方法逻辑的代理类,this表示当前对象。代理逻辑类必须实现InvocationHandler接口的invoke方法,这个方法就是代理逻辑方法的实现方法。 我们就将代理逻辑放在本类
  1. 实现代理方法:在代理逻辑类的invoke方法中实现代理逻辑,该方法三个参数意义如下:
  • proxy 代理对象,就是bind方法生成的对象
  • method 当前调度的方法
  • args 调度方法的参数

我们使用了代理方法调度方法之后,就会进入到invoke方法中去。

实现了代理逻辑类,在主方法中使用时,可见只需要使用代理逻辑类将裁缝(委托类)绑定关系,就可以直接生成代理对象,执行代理逻辑。

(2) CGLib动态代理

我们发现,无论是静态代理还是JDK动态代理,都需要提供接口。若在不能提供接口的环境中,我们可以使用CGLib代理。它只需要提供一个委托类就能实现动态生成代理对象实现代理。

首先我们需要在Maven中引入CGLib的依赖:

<!-- CGLib代理 --><dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency>

然后也是创建一个代理逻辑类,这个和JDK动态代理有点类似。

网络异常,图片无法展示
|

还是贴上代理逻辑类的代码:

packagecom.example.javaproxy.proxy.cglibproxy;
importnet.sf.cglib.proxy.Enhancer;
importnet.sf.cglib.proxy.MethodInterceptor;
importnet.sf.cglib.proxy.MethodProxy;
importjava.lang.reflect.Method;
/*** CGLib动态代理逻辑实现*/publicclassCGLibProxyBindimplementsMethodInterceptor {
/*** 生成CGLib代理对象** @param cls 真实对象的类* @return 代理对象*/publicObjectgetProxy(Classcls) {
// CGLib增强类型对象Enhancerenhancer=newEnhancer();
// 设定委托类为父类enhancer.setSuperclass(cls);
// 设定代理逻辑类为本类enhancer.setCallback(this);
// 生成并返回代理对象returnenhancer.create();
   }
/*** 代理逻辑方法** @param proxy       代理对象* @param method      方法* @param args        方法参数* @param methodProxy 方法代理* @return 代理逻辑返回* @throws Throwable 异常*/@OverridepublicObjectintercept(Objectproxy, Methodmethod, Object[] args, MethodProxymethodProxy) throwsThrowable {
System.out.println("[进入CGLib动态代理逻辑]");
System.out.println("裁缝代理人已接到需求!");
// 调用裁缝(委托类)的方法Objectresult=methodProxy.invokeSuper(proxy, args);
System.out.println("衣服定制完成!");
returnresult;
   }
}

然后在主方法测试:

// 首先创建CGLib代理逻辑类实例,把代理对象和委托对象联系起来CGLibProxyBindcgLibProxyBind=newCGLibProxyBind();
// 生成裁缝的代理对象Tailoragent= (Tailor) cgLibProxyBind.getProxy(Tailor.class);
// 这个时候agent已经是一个代理对象了,尽管它是Tailor类ClothingcustomClothing=agent.makeClothing("衬衫", "XL");
System.out.println("得到衣服名:"+customClothing.getName() +";得到衣服尺码:"+customClothing.getSize());

结果:

网络异常,图片无法展示
|

可见这里用了CGLib的加强者Enhancer,通过设定超类为委托类,并设定本类为代理逻辑类(代理逻辑类需要实现MethodInterceptor接口),来生成动态代理对象。

可见在使用上面,CGLib动态代理和JDK动态代理还是很相似的。

不过这里需要提醒一下,使用JDK9及其以上版本执行CGLib动态代理会抛出异常java.lang.ExceptionInInitializerError,因为在JDK高版本中,--illegal-access选项默认为deny,这会导致深度反射失败。

可以使用JDK8版本来实现CGLib代理。

若要使用JDK9及其以上版本,那么就修改运行JVM参数--illegal-access的值为warn(不过这样还是会弹出警告)。

命令行运行加上参数即可:

java --illegal-access=warn -jar xxx.jar

使用IDEA的话,在运行配置中设定运行参数:

网络异常,图片无法展示
|

网络异常,图片无法展示
|

网络异常,图片无法展示
|

网络异常,图片无法展示
|

最后应用、确定即可!

3,总结

代理模式就是一种间接访问的模式,在很多地方其实都有着应用。

静态代理很简单,不过需要手动定义代理类,不利于业务扩展。

动态代理只需要实现代理方法逻辑即可,在运行过程中就会动态地生成代理对象,常用的就是JDK动态代理和CGLib动态代理。

而JDK动态代理和CGLib动态代理最大的区别就是,前者需要依赖接口而后者不需要。JDK动态代理的实质是通过反射代理方法,而CGLib是通过生成类字节码实现代理。

示例程序仓库地址

相关文章
|
1天前
|
设计模式 存储 Java
【十】设计模式~~~结构型模式~~~享元模式(Java)
文章详细介绍了享元模式(Flyweight Pattern),这是一种对象结构型模式,通过共享技术实现大量细粒度对象的重用,区分内部状态和外部状态来减少内存中对象的数量,提高系统性能。通过围棋棋子的设计案例,展示了享元模式的动机、定义、结构、优点、缺点以及适用场景,并探讨了单纯享元模式和复合享元模式以及与其他模式的联用。
【十】设计模式~~~结构型模式~~~享元模式(Java)
|
1天前
|
设计模式 存储 Java
【九】设计模式~~~结构型模式~~~外观模式(Java)
文章详细介绍了外观模式(Facade Pattern),这是一种对象结构型模式,通过引入一个外观类来简化客户端与多个子系统之间的交互,降低系统的耦合度,并提供一个统一的高层接口来使用子系统。通过文件加密模块的实例,展示了外观模式的动机、定义、结构、优点、缺点以及适用场景,并讨论了如何通过引入抽象外观类来提高系统的可扩展性。
【九】设计模式~~~结构型模式~~~外观模式(Java)
|
1天前
|
设计模式 Java
【八】设计模式~~~结构型模式~~~装饰模式(Java)
文章详细介绍了装饰模式(Decorator Pattern),这是一种对象结构型模式,用于在不使用继承的情况下动态地给对象添加额外的职责。装饰模式通过关联机制,使用装饰器类来包装原有对象,并在运行时通过组合的方式扩展对象的行为。文章通过图形界面构件库的设计案例,展示了装饰模式的动机、定义、结构、优点、缺点以及适用场景,并提供了Java代码实现和应用示例。装饰模式提高了系统的灵活性和可扩展性,适用于需要动态、透明地扩展对象功能的情况。
【八】设计模式~~~结构型模式~~~装饰模式(Java)
|
1天前
|
设计模式 XML 存储
【七】设计模式~~~结构型模式~~~桥接模式(Java)
文章详细介绍了桥接模式(Bridge Pattern),这是一种对象结构型模式,用于将抽象部分与实现部分分离,使它们可以独立地变化。通过实际的软件开发案例,如跨平台视频播放器的设计,文章阐述了桥接模式的动机、定义、结构、优点、缺点以及适用场景,并提供了完整的代码实现和测试结果。桥接模式适用于存在两个独立变化维度的系统,可以提高系统的可扩展性和灵活性。
【七】设计模式~~~结构型模式~~~桥接模式(Java)
|
1天前
|
设计模式 存储 负载均衡
【五】设计模式~~~创建型模式~~~单例模式(Java)
文章详细介绍了单例模式(Singleton Pattern),这是一种确保一个类只有一个实例,并提供全局访问点的设计模式。文中通过Windows任务管理器的例子阐述了单例模式的动机,解释了如何通过私有构造函数、静态私有成员变量和公有静态方法实现单例模式。接着,通过负载均衡器的案例展示了单例模式的应用,并讨论了单例模式的优点、缺点以及适用场景。最后,文章还探讨了饿汉式和懒汉式单例的实现方式及其比较。
【五】设计模式~~~创建型模式~~~单例模式(Java)
|
1天前
|
设计模式 算法 安全
Java编程中的设计模式:提升代码的可维护性和扩展性
【8月更文挑战第19天】在软件开发的世界里,设计模式是解决常见问题的一种优雅方式。本文将深入探讨Java编程语言中常用的几种设计模式,并解释如何通过这些模式来提高代码的可维护性和扩展性。文章不涉及具体的代码实现,而是侧重于理论和实践相结合的方式,为读者提供一种思考和改善现有项目的新视角。
|
1天前
|
设计模式 XML 存储
【六】设计模式~~~结构型模式~~~适配器模式(Java)
文章详细介绍了适配器模式(Adapter Pattern),这是一种结构型设计模式,用于将一个类的接口转换成客户期望的另一个接口,使原本不兼容的接口能够一起工作,提高了类的复用性和系统的灵活性。通过对象适配器和类适配器两种实现方式,展示了适配器模式的代码应用,并讨论了其优点、缺点以及适用场景。
|
1天前
|
设计模式 Java
常用设计模式介绍~~~ Java实现 【概念+案例+代码】
文章提供了一份常用设计模式的全面介绍,包括创建型模式、结构型模式和行为型模式。每种设计模式都有详细的概念讲解、案例说明、代码实例以及运行截图。作者通过这些模式的介绍,旨在帮助读者更好地理解源码、编写更优雅的代码,并进行系统重构。同时,文章还提供了GitHub上的源码地址,方便读者直接访问和学习。
常用设计模式介绍~~~ Java实现 【概念+案例+代码】
|
1天前
|
设计模式 算法 Java
【十六】设计模式~~~行为型模式~~~策略模式(Java)
文章详细介绍了策略模式(Strategy Pattern),这是一种对象行为型模式,用于定义一系列算法,将每个算法封装起来,并使它们可以相互替换。策略模式让算法独立于使用它的客户而变化,提高了系统的灵活性和可扩展性。通过电影院售票系统中不同类型用户的打折策略案例,展示了策略模式的动机、定义、结构、优点、缺点以及适用场景,并提供了Java代码实现和测试结果。
【十六】设计模式~~~行为型模式~~~策略模式(Java)
|
1天前
|
设计模式 网络协议 Java
【十五】设计模式~~~行为型模式~~~状态模式(Java)
文章详细介绍了状态模式(State Pattern),这是一种对象行为型模式,用于处理对象在其内部状态改变时的行为变化。文中通过案例分析,如银行账户状态管理和屏幕放大镜工具,展示了状态模式的应用场景和设计方法。文章阐述了状态模式的动机、定义、结构、优点、缺点以及适用情况,并提供了Java代码实现和测试结果。状态模式通过将对象的状态和行为封装在独立的状态类中,提高了系统的可扩展性和可维护性。
【十五】设计模式~~~行为型模式~~~状态模式(Java)