在软件设计中,使用代理模式的例子也很多,例如,要访问的远程对象比较大(如视频或大图像等),其下载要花很多时间。还有因为安全原因需要屏蔽客户端直接访问真实对象,如某单位的内部数据库等。
代理模式属于结构型模式,是23种设计模式中较为重要的一种,代理模式分为静态代理和动态代理,动态代理又分为基于接口实现的动态代理和基于子类实现的动态代理;在jdk源码中,很多底层的实现也是基于代理模式的,例如创建线程的方式之一实现Runnable接口就使用了静态代理模式,而Spring框架最为重要的AOP的实现是基于动态代理模式,可见,学习代理模式是我们能看懂底层源码实现原理的一个基础。
代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
代理模式的主要优点有:
其主要缺点是:
那么如何解决以上提到的缺点呢? 答案是可以使用动态代理方式
代理模式的结构比较简单,主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问,下面来分析其基本结构和实现方法。
代理模式的主要角色如下。
在代码中,一般代理会被理解为代码增强,实际上就是在原代码逻辑前后增加一些代码逻辑,而使调用者无感知。
根据代理的创建时期,代理模式分为静态代理和动态代理。
代理模式的实现代码如下:
package proxy;
public class ProxyTest {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.Request();
}
}
//抽象主题
interface Subject {
void Request();
}
//真实主题
class RealSubject implements Subject {
public void Request() {
System.out.println("访问真实主题方法...");
}
}
//代理
class Proxy implements Subject {
private RealSubject realSubject;
public void Request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
preRequest();
realSubject.Request();
postRequest();
}
public void preRequest() {
System.out.println("访问真实主题之前的预处理。");
}
public void postRequest() {
System.out.println("访问真实主题之后的后续处理。");
}
}
程序运行的结果如下:
访问真实主题之前的预处理。
访问真实主题方法...
访问真实主题之后的后续处理。
当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。
前面分析了代理模式的结构与特点,现在来分析以下的应用场景。
注意事项:
在前面介绍的代理模式中,代理类中包含了对真实主题的引用,这种方式存在两个缺点。
关于静态代理,我们以小明结婚为例,看以下代码:
//代理对象和真实对象共同实现的接口,里面有结婚方法
interface A{
void marry();
}
//真实对象
class Person implements A{
@Override
public void marry() {
System.out.println("我要结婚啦~~~");
}
}
//代理对象
class Proxy implements A{
//代理类聚合了真实类
private A a;
public Proxy(A a){
this.a = a;
}
@Override
public void marry() {
before();
//真实对象去结婚
a.marry();
after();
}
//增强方法:对真实对象进行一个增强
public void before(){
System.out.println("结婚前,布置现场");
}
public void after(){
System.out.println("结婚后,收拾现场");
}
}
/**
* 使用生活中的结婚例子来理解静态代理:小明要结婚,会去找婚庆公司帮忙准备结婚的一些事宜
* 小明是真实对象,婚庆公司是代理对象,代理了真实对象,帮助小明去结婚,真正结婚的是小明
*
* 静态代理模式通过代理对象可以做很多真实对象做不了的事情,实现对真实对象的一个增强
* 同时真实对象也可以专注的去做一件事情
*
* 就好比婚庆公司可以在小明结婚之前帮他布置现场,结婚之后收拾现场,而小明只需要专心结婚即可
*
* 静态代理模式要求代理对象和真实对象实现同一个接口
* @author cj_chen
*
*/
public class Client {
public static void main(String[] args) {
//创建一个真实对象
Person xiaoming = new Person();
//创建一个代理对象,代理了小明这个真实对象
Proxy proxy = new Proxy(xiaoming);
//通过代理对象去执行真实对象的方法,实现一个增强
proxy.marry();
}
}
关于静态代理模式:
静态代理模式在jdk源码中的应用:
实现Runnable接口创建线程的方式用到了静态代理模式;回顾一下步骤:先创建一个Runnable接口的实现类对象,然后创建一个Thread类,并将Runnable接口的实现类作为参数传递进去,然后调用Thread类对象的start方法开启多线程;
Runnable接口的实现类对象是真实对象,Thread类对象是代理对象,静态代理模式要求代理对象和真实对象继承同一个接口,Runnable接口的实现类对象和Thread类对象都继承了Runnable接口,所以完全符合静态代理模式
关于动态代理,我们以生产者生产手机和销售手机为例,看以下代码:
//被代理角色/真实角色实现的接口
public interface IProducer {
//生产手机
void produce();
//销售手机
void sale();
}
//被代理角色
public class Producer implements IProducer{
@Override
public void produce() {
System.out.println("生产者生产手机啦~~~");
}
@Override
public void sale() {
System.out.println("生产者销售手机啦~~~");
}
}
public class Client {
public static void main(String[] args) {
IProducer producer = new Producer();
producer.produce();
}
}
public class Client {
public static void main(String[] args) {
//先创建一个真实角色
IProducer producer = new Producer();
//基于接口方式创建动态代理角色
IProducer proxy = (IProducer)Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法
* 方法参数的含义
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//增强真实角色的代码
System.out.println("打印日志lalala~~~");
return method.invoke(producer, args);
}
});
proxy.produce();
}
}
一般来说,客户端可以直接调用真实对象的方法;现在我们有这么一个需求:想要在生产者(真实对象)执行生产方法和销售方法之前打印一句日志,并且是要在不修改原有代码的前提下完成,这也是为了满足设计模式的开闭原则,这时候就可以使用动态代理模式
关于动态代理:
动态代理模式底层使用的就是java的反射,要想真正理解动态代理模式,建议先看一下反射相关的知识!如果对于动态代理模式“一个动态代理类可以代理多个类,只要实现的是同一个接口即可”这个优点不够理解的话,
动态代理的应用:
Spring的AOP(面向切面编程)就运用到了动态代理模式,换言之,AOP的实现方式就是使用动态代理技术,AOP就是在不改变原有代码的基础上,可以将要增强的代码横切进去,例如要给原来的业务逻辑代码增加打印日志或者事务管理等;简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的 基础上,对我们的已有方法进行增强
更多请移驾。。。
🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽
本文作者:Java技术债务
原文链接:https://www.cuizb.top/myblog/article/1656592442
版权声明: 本博客所有文章除特别声明外,均采用 CC BY 3.0 CN协议进行许可。转载请署名作者且注明文章出处。
🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽
JVM内存泄漏和内存溢出的原因
JVM常用监控工具解释以及使用
Redis 常见面试题(一)
ClickHouse之MaterializeMySQL引擎(十)
三种实现分布式锁的实现与区别
线程池的理解以及使用
号外!号外!
最近面试BAT,整理一份面试资料,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。想获取吗?如果你想提升自己,并且想和优秀的人一起进步,感兴趣的朋友,可以在扫码关注下方公众号。资料在公众号里静静的躺着呢。。。
一键四连,你的offer也四连
————————————————————————————————————————————————————————————————