当前位置:首页 > CN2资讯 > 正文内容

【Java】代理模式代理模式 java

2天前CN2资讯


滴滴面试遇到的问题,现在整理一次。

代理模式是Java设计模式中很重要的一个。要想充分理解,必须搞清楚什么是代理模式,什么是代理,为什么要用代理模式这些最基础的问题。

什么是代理?

这个可以从现实的角度理解,代理就是另一件事物的代替品,使得我们可以不直接与原本的事物打交道,而是转为同原本事物的代理打交道,从而完成一定的任务或者目标。

什么是代理模式?

把代理的现实意义搬到程序设计的世界就是代理模式了。概括地讲,代理模式可以使我们不和实际对象直接交互,而是与其代理交互,从而得到实际对象的服务。这一步很好理解。但是我觉得必须搞清楚为什么不与实际对象直接交互,而偏要绕远路与一个代理接触呢?

为什么使用代理?

有两个原因。第一个是没法交互,代价高。举个现实的例子,买火车票,原本只能是本人去火车站买票的,那么假设有个人很急,春节回家,如果他现在敢去火车站买票可能就没票了。但是在现代,我们可以通过网络订票,这样节省了跑去火车站的成本,就可以在第一时间买票。网络app就是火车站售票点的代理。票很急,导致我们无法与火车站直接交互,只能通过app这个代理来买票。这就是为什么用代理的原因了。第二个是主动不想让用户直接与实际对象打交道,比如有权限控制,过滤等要求。举个软件里面的例子。论坛系统是一个大类,提供了论坛的管理功能接口。但是并不是每一个人或者对象都可以调用,只有管理员角色才可以。那么这是就需要创建一个代理,这个代理比原本的论坛系统多了一些功能,首先检测权限,如果是管理员,再调用论坛的接口来操作,否则直接返回。这就是主观上控制不想让任意用户调用全部的功能,相当于加上了一个过滤器。

至此,代理模式的概念性问题就搞清楚了。下面是实现部分。

静态代理:

这种方式有两种实现,一种是基于聚合的,一种是基于继承,考虑到减少侵入式的设计理念,比较推荐聚合方式。如下:

服务接口:

public interface Service {

public void f1();

}

具体的实现类:

public class RealObj implements Service{

@Override
public void f1() {
System.out.println("RealObj service...");
}

}

静态代理:

public class StaticProxy implements Service{

private Service realObj;

public StaticProxy(Service service) {
this.realObj = service;
}

@Override
public void f1() {
System.out.println("Proxy pre-handle...");
realObj.f1();
System.out.println("Proxy post-handle...");
}

}

客户端:

public class Main {

public static void main(String args[]){
Service realObj = new RealObj();
StaticProxy proxy = new StaticProxy(realObj);
proxy.f1();
}
}


结果:

这里其实是针对过滤的需求或者预处理。所谓静态实现指的是这种代理机制在编译期就已经决定,因为这是通过一个具体的编写的代理类实现的,所以只要代码写好就板上钉钉。无法再改变。下面是动态代理:

动态代理:

为什么需要动态代理?静态代理的缺点在于,假设现在我有一百个业务逻辑类,每一个类的业务逻辑都需要在执行前做一次日志记录,执行后做一次日志记录,这样就需要为每一个业务类写一个代理,而这些代理类的代码几乎相同,都是记录日志,所以,造成了类的爆炸以及公共代码的充斥。最好是只写一次相同的代码,然后和每一个业务类组合使用。也就是说,这里的代理类是动态生成的,而非编译期就可以知道。在程序已经编译好运行起来,这时我想为业务一做记录日志的动态代理,代码会自动生成一个这样的代理类,然后加载到虚拟机运行。我需要做的只是告诉编译器,这段公共的日志记录代码是什么样子。
要让程序自动生成动态代理,那么就需要告诉它业务的接口方法是什么,业务的实现类是什么,然后需要在业务类上做什么处理,比方日志记录,权限检查。这样,程序就知道如何自动生成这个代理类了。这段程序java类库已经为我们实现了,我们只需要传入刚才的参数即可。实现方式肯定是反射。

下面再看看java里的动态代理,代理的本质是让所有的方法都有一个统一的处理方法,比如前面讲的日志,那么实现这一模式的时候,我们只需要知道有哪些方法就可以,然后再定义一个统一的处理模板,最后每一个方法都会按照该模板来。在这个模板中间,我们可以添加被代理对象的调用逻辑。

下面是生成代理对象的代码:

import java.lang.reflect.Proxy;

public class Main {

public static void main(String args[]){
//静态代理
/*
Service realObj = new RealObj();
StaticProxy proxy = new StaticProxy(realObj);
proxy.f1();
*/
//动态代理
Service realObj = new RealObj();
Service proxy = (Service) Proxy.newProxyInstance(Service.class.getClassLoader(),
new Class[]{Service.class}, new ProxyHandler(realObj));
proxy.f1();
}
}

这是调用处的代码。

我们需要使用Proxy的静态方法来构造一个代理对象,该方法接受三个参数:

(1)类加载器:加载器是JVM很重要的一部分,因为它不仅仅用于加载一个class文件,而且还和全限定类名一起表标识了类的唯一性,所以这里必须指定;

(2)接口:可以看到这是一个数组,为什么是数组呢?因为一个类可以实现多个接口,所以生成的代理类必须包含所有接口的方法的实现,因此就需要传入接口的数组;

(3)Handler:接口中每一个方法的处理逻辑。

下面是Handler的定义:

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

public class ProxyHandler implements InvocationHandler{

private Object realObj;

public ProxyHandler(Object obj) {
this.realObj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Common pre-handle...");

Object result = method.invoke(realObj, args);

System.out.println("Common pre-handle...");
return result;
}

}

这个Handler就是我们编写公共处理逻辑的地方,即invoke方法。method参数就是接口方法,args是方法的参数,invoke的返回值是object,因为不知道是什么类型的。我们可以在invoke方法的前后做公共处理逻辑。这样生成的代理类的接口方法实现其实就是这里的invoke方法。那么Object proxy是什么?这个是对代理对象的引用,相当于this,注意它并不是真正的被代理对象,所以我们这里使用proxy的invode方法就会产生死循环,必须使用真正的被代理对象。所以,在Handler接口中并没有管理被代理对象,这部分留给了用户自己处理,很灵活,我们甚至可以不用被代理对象,听起来很玄乎,我们使用代理模式本身的意图就是创建一个真实对象的代理,为什么会出现不使用真实对象的情况?

这个问题还是要回归java动态代理的本质,它就是根据模板为接口中的每一个方法生成代码,然后返回实例,并没有要求我们一定代理一个真实对象。一个典型的例子是rpc调用时的clien端代码,client会把方法名和参数通过tcp传递,然后再用tcp读取返回值,由于client并不是实现端,所以这里不需要真实对象,只需要知道方法的信息即可,再做一些网络和协议相关的处理,java的动态代理给了用户足够的灵活度,如果我们确实需要使用被代理对象,只需要自己在Handler类内部加一个实例即可。所以这种情况下,我们就不能使用handler的匿名内部类方式定义了,匿名内部类无法提供构造方法传入参数。

Proxy是如何动态生成字节码呢?以下网址写的不错:



下面是执行结果:


有人可能会说这个和适配器模式很像,尤其是静态代理,确实!如果只看代码结构,都是采用聚合,要想区分其实也很简单,还是从概念或者用途,适配器用在接口不统一的情况下,而代理之前也说了,一是得不到原本对象,二是做预处理,过滤或者日志。到底用哪个完全取决于需求。不能在意形式,而应该搞明白原理!

    你可能想看:

    扫描二维码推送至手机访问。

    版权声明:本文由皇冠云发布,如需转载请注明出处。

    本文链接:https://www.idchg.com/info/24636.html

    分享给朋友:

    “【Java】代理模式代理模式 java” 的相关文章

    如何选择高性能、美西VPS服务商: 比较、评测及优化建议

    美西VPS概述 美西VPS,简单来说,就是那些位于美国西部地区的虚拟专用服务器,像在洛杉矶这样的城市里。这些服务器给用户提供了一种灵活而高效的托管解决方案,特别是对于需要快速访问和低延迟连接的用户群体。美西VPS的设计理念是为用户提供高性能和高可靠性的服务,同时确保在数据传输时的安全性。 美西VPS...

    PacificRack低价VPS服务评测与用户体验分析

    在云计算和虚拟主机服务日益普及的今天,PacificRack作为QuadraNET旗下的全资子品牌,逐渐在低价VPS市场中崭露头角。它的主要定位是为那些对性能要求不高,且对价格敏感的用户提供解决方案。PacificRack通过严格的资源管理,致力于为用户提供一种经济实惠的选择,适合希望以最低成本体验...

    如何解决甲骨文IP被墙的问题及有效方法

    谈到甲骨文,大家可能会联想到古老的文字和悠久的历史,然而随着信息技术的飞速发展,甲骨文的应用已经不仅仅局限于文化研究。在数据存储、信息管理等领域,甲骨文的IP(互联网协议)在全球范围内发挥着重要的作用。不幸的是,现如今许多用户却发现自己无法正常访问这些IP。这就是我们所说的“甲骨文IP被墙”的现象。...

    LeaseWeb旧金山数据中心:为企业提供高效IT基础设施解决方案

    在谈到全球范围内的IT基础设施解决方案时,LeaseWeb无疑是一个重要的名字。成立于荷兰的LeaseWeb,凭借其卓越的服务和强大的网络能力,已经发展成为一家全球性的科技公司。它不仅提供传统的独立服务器服务,还涵盖了云计算、服务器托管等多样化的解决方案。对我而言,LeaseWeb就像是一座桥梁,连...

    Hostwinds LLC:卓越的网络托管服务与高性价比优势

    Hostwinds LLC成立于2010年,位于美国西雅图。这家公司一直专注于提供多种网络托管服务,包括虚拟主机、虚拟专用服务器(VPS)和独立服务器。在这个竞争激烈的市场中,Hostwinds凭借其独特的优势和不断升级的服务赢得了客户的信赖。我个人认为,Hostwinds的历史反映了它对客户需求的...

    OVH云服务概述:高性价比的VPS与专用服务器解决方案

    OVH概述 谈到云服务提供商,OVH无疑是一个引人注意的名字。这家成立于1999年的法国公司,总部位于鲁贝,已经从当初的小公司发展成为全球领先的云服务平台。OVH不仅为个人和企业提供各种云计算解决方案,还持续推动技术创新,满足越来越多用户的需求。 OVH拥有广泛的服务网络,分布在北美、欧洲、亚洲和非...