java代理:静态代理和动态代理

时间:2022-11-24 21:50:07

这里总结下java中的静态代理和动态代理。

Java中有一个设计模式是代理模式

代理模式是常用的Java设计模式,特征是代理类与委托类有相同的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。

代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。简单的说就是,我们在访问实际对象的时候,是通过代理对象来访问的,代理模式就是在访问实际对象的时候引入一定程度的间接性,因为这种间接性,可以附加多种用途。

静态代理

静态代理,由程序员创建或特定工具自动生成源代码,在编译时已经将接口,被代理类(委托类),代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

假定一个团购长途汽车车票的场景,有50个乘客要去客运站买长途汽车车票,由跟车人去代买50张车票。在这里,乘客有买车票的行为,跟车人也有买车票的行为,那么乘客买车票就可以由跟车人去代理执行。

首先是创建一个买票人的接口。

/**
* 买票人接口
*/
public interface TickectBuyer { // 买车票
void buyTicket();
}

然后是创建一个乘客类(委托类),去实现买票人接口。

/**
* 乘客类,实现了买票人接口
*/
public class Passenger implements TickectBuyer { private String name; Passenger(String name) {
this.name = name;
} @Override
public void buyTicket() {
System.out.println("乘客【" + name + "】买了一张车票。");
}
}

然后是创建一个乘客代理类,同样实现买票人接口。

因为持有一个乘客类对象,所以它可以代理乘客类对象执行买车票的行为。

/**
* 乘客代理类,也实现买票人接口
*/
public class PassengerProxy implements TickectBuyer { private String name; // 被代理的乘客类
private Passenger passenger; PassengerProxy(String name, Passenger passenger) { this.name = name; // 只代理乘客类
if (passenger.getClass() == Passenger.class) {
this.passenger = passenger;
}
} @Override
public void buyTicket() {
// 委托类附加的操作
System.out.print("代买人【" + name + "】代"); // 调用委托类(乘客类)的方法
passenger.buyTicket();
}
}

最后创建一个测试类测试代理的结果。

/**
* 乘客代理测试类
*/
public class PassengerProxyTest { public static void main(String[] args) {
// 乘客陈小鸡(乘客类)
Passenger passenger = new Passenger("陈小鸡");
// 跟车人(乘客代理类)
PassengerProxy carFollower = new PassengerProxy("王小狗", passenger);
// 由跟车人代理陈小鸡买车票
carFollower.buyTicket();
}
}

结果是:代买人【王小狗】代乘客【陈小鸡】买了一张车票。

这里可以看到,代理类可以通过持有委托类对象去调用委托类的方法,从而达到代理委托类去执行委托类行为的目的。然后,在调用委托类方法的时候,可以在调用的前面或者后面添加代理类自己的行为,比如上面代码中添加打印代理人信息的行为。这个就是代理模式的一个很大的优点,可以在代理点切入一些特定的、附加的操作,却不会改变原来委托要执行的行为。

动态代理

代理类在程序运行时常见的代理方式被称为动态代理。我们上面静态代理的例子中,代理类PassengerProxy是自己定义好的,在程序运行之前就已经编译完成。不同的是,动态代理的代理类并不是在Java代码中定义好的,而是在运行时根据我们在Java代码中的指示动态生成的。相比于静态代理,动态代理的优势在于可以很方便地对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。比如说,想要在每个代理的方法前都加上一个处理方法:

public void buyTicket() {
// 在调用委托类的方法前,加入其他逻辑处理
beforeMethod(); // 调用委托类(乘客类)的方法
passenger.buyTicket();
}

这里只有一个buyTickect()方法,就只要写一次beforeMethod()方法。可是如果有很多个地方都要调用beforeMethod()方法,就需要改很多个地方,给修改或维护带来麻烦。动态代理就是为了解决这样的麻烦,而由聪明绝顶(滑稽)的人才想出来的解决方法。

在Java的java.lang.reflect包(反射包啦,看到这应该明白动态代理是用Java的反射机制实现的了吧,不会反射的还不去先学一下反射)下提供了一个Proxy类和InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

创建一个InvocationHandler对象。

// 创建一个与代理对象相关联的InvocationHandler
InvocationHandler passengerHandler = new MyInvocationHandler<TickertBuyer>(passenger);

使用Proxy类的getProxyClass静态方法生成一个动态代理类passengerProxyClass。

Class<?> passengerProxyClass = Proxy.getProxyClass(TickectBuyer.class.getClassLoader(), new Class<?>[] {TickectBuyer.class});

获得passengerProxy中一个带InvocationHandler参数的构造器constructor。

Constructor<?> constructor = passengerProxy.getConstructor(InvocationHandler.class);

通过构造器constructor来创建一个动态实例passengerProxy。

TickectBuyer passengerProxy = (TickectBuyer) constructor.newInstance(passengerHandler);

这样,一个动态代理对象passengerProxy就创建完毕了。另外的,上面四个步骤可以通过Proxy类的newProxyInstancs方法来简化:

// 创建一个与代理对象相关联的InvocationHandler
InvocationHandler passengerHandler = new MyInvocationHandler<TickectBuyer>(passenger);
// 创建一个代理对象passengerProxy,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
TickectBuyer passengerProxy= (TickectBuyer) Proxy.newProxyInstance(TickectBuyer.class.getClassLoader(), new Class<?>[]{TickectBuyer.class}, passengerHandler);

那么动态代理是要如何执行,如何通过代理对象来执行被代理对象的方法呢。我们可以通过完整的动态代理的例子来说明。还是上面跟车人帮乘客代买车票的例子。

首先是定义一个TickectBuyer接口,其中有一个未实现的buyTickect()方法。

public interface TickectBuyer {

    // 买车票
void buyTicket();
}

然后是创建需要被代理的乘客类。

public class Passenger implements TickectBuyer {

    private String name;

    Passenger(String name) {
this.name = name;
} @Override
public void buyTicket() {
try {
// 假设买一张票要5秒
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("乘客【" + name + "】买了一张车票。");
}
}

然后定义一个检测方法执行时间的工具类,在任何方法执行之前先调用start()方法,执行后调用finish()方法,就可以计算出该方法的运行时间,这也是一个最简单的方法执行时间检测工具。

public class MonitorUtil {

    private static ThreadLocal<Long> tl = new ThreadLocal<>();

    public static void start() {
tl.set(System.currentTimeMillis());
} // 结束时打印耗时
public static void finish(String methodName) {
long finishTime = System.currentTimeMillis();
System.out.println(methodName + "方法耗时" + (finishTime - tl.get()) + "ms");
}
}

然后创建PassengerInvocationHandler类,实现InvocationHandler接口。这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke()方法,所有执行代理对象的方法都会被替换成执行invoke()方法。在invoke()方法中执行被代理对象target的相应方法。当然,在代理过程中,我们可以在真正执行被代理对象的方法前加入自己的其他处理。这也是Spring中AOP实现的主要原理,其实就是Java基础的反射机制,没有什么神秘的黑科技啦。

public class PassengerInvocationHandler<T> implements InvocationHandler {
// InvocationHandler持有的被代理对象
private T target; public PassengerInvocationHandler(T target) {
this.target = target;
} /**
* proxy:代表动态代理对象
* method:代表正在执行的方法
* args:代表调用目标方法时传入的实参
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理执行" + method.getName() + "方法"); // 代理过程中插入监测方法,计算该方法耗时
MonitorUtil.start();
Object result = method.invoke(target, args);
MonitorUtil.finish(method.getName());
return result;
}
}

做完上面的工作后,我们就可以具体来创建动态代理对象了。

public class PassengerProxyTest2 {

    public static void main(String[] args) {

        // 创建一个实例对象,这个对象是被代理的对象
TickectBuyer zhangsan = new Passenger("张三"); // 创建一个与代理对象相关联的InvocationHandler
InvocationHandler passengerHandler = new PassengerInvocationHandler<TickectBuyer>(zhangsan); // 创建一个代理对象passengerProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
TickectBuyer passengerProxy = (TickectBuyer) Proxy.newProxyInstance(TickectBuyer.class.getClassLoader(),
new Class<?>[]{TickectBuyer.class}, passengerHandler); // 代理执行买车票的方法
passengerProxy.buyTicket();
}
}

我们执行这个PassengerProxyTest2类之前,先想以下,我们创建了一个需要被代理的乘客张三,将张三对象传给了passengerHandler,在创建代理对象passengerProxy时,将passengerHandler作为参数,上面有说到所有执行代理对象的方法都会被替换成执行invoke()方法,也就是说,最后执行的是passengerInvocationHandler中的invoke()方法。

java代理:静态代理和动态代理

上面说到,动态代理的优势在于可以很方便地对代理类的方法进行统一的处理,而不用修改每个代理类中的方法,这是因为所有被代理执行的方法,都是通过InvocationHandler中的invoke()方法调用的,我们只要在invoke()方法中统一处理,就可以给所有被代理的方法进行统一添加相同的操作了。例如这里的方法计时,所有的被代理对象执行的方法都会被计时。

动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然,清晰明了。因为动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地看到动态代理中被代理对象是怎么被代理的,也不知道为什么代理对象执行的方法都会通过InvocationHandler中的invoke()方法执行。为了弄清楚这些问题,就需要分析源码码,下次再用另外的篇幅来分析好了。

"暗恋或单恋都是人生中一场漫长的走神。"

java代理:静态代理和动态代理的更多相关文章

  1. Java编程的逻辑 &lpar;86&rpar; - 动态代理

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  2. Java基础加强-&lpar;注解,动态代理,类加载器,servlet3&period;0新特性&rpar;

    1.   Annotation注解 1.1.  Annotation概述 Annotation是JDK 5.0以后提供对元数据的支持,可以在编译.加载和运行时被读取,并执行相应的处理.所谓Annota ...

  3. java开发必学知识&colon;动态代理

    目录 1. 引言 2. 代理模式及静态代理 2.1 代理模式说明 2.2 静态代理 2.3 静态代理局限性 3. 动态代理 3.1 JAVA反射机制 3.2 JDK动态代理 3.2.1 JDK动态代理 ...

  4. 浅谈Java代理二:Cglib动态代理-MethodInterceptor

    浅谈Java代理二:Cglib动态代理-MethodInterceptor CGLib动态代理特点: 使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,而且CGLib底层采用ASM字节码生 ...

  5. 浅谈Java代理一:JDK动态代理-Proxy&period;newProxyInstance

    浅谈Java代理一:JDK动态代理-Proxy.newProxyInstance java.lang.reflect.Proxy:该类用于动态生成代理类,只需传入目标接口.目标接口的类加载器以及Inv ...

  6. Java高级特性—反射和动态代理

    1).反射 通过反射的方式可以获取class对象中的属性.方法.构造函数等,一下是实例: 2).动态代理 使用场景: 在之前的代码调用阶段,我们用action调用service的方法实现业务即可. 由 ...

  7. Spring代理模式(jdk动态代理模式)

    有动态代理和静态代理: 静态代理就是普通的Java继承调用方法. Spring有俩种动态代理模式:jdk动态代理模式 和 CGLIB动态代理 jdk动态代理模式: 代码实现: 房东出租房子的方法(继承 ...

  8. 动态代理:JDK原生动态代理(Java Proxy)和CGLIB动态代理原理&plus;附静态态代理

    本文只是对原文的梳理总结,以及自行理解.自己总结的比较简单,而且不深入,不如直接看原文.不过自己梳理一遍更有助于理解. 详细可参考原文:http://www.cnblogs.com/Carpenter ...

  9. Java基础之反射和动态代理

    1,反射是依赖于Class对象,然后根据Class对象,去操作该类的资源的.Class对象是发射的基石! 问题1:人这类事物用什么表示?汽车这类事物用什么表示>计算机文件用什么表示?有如此多的事 ...

  10. java反射机制应用之动态代理

    1.静态代理类和动态代理类区别 静态代理:要求被代理类和代理类同时实现相应的一套接口:通过代理类的对象调用重写接口的方法时,实际上执行的是被代理类的同样的 方法的调用. 动态代理:在程序运行时,根据被 ...

随机推荐

  1. 整合Open vSwitch与DNSmasq为虚拟机提供DHCP功能

    继上文<Ubuntu14.04安装配置Open vSwitch>安装好Open vSwitch后,这里我们将要创建两个KVM虚拟机,并通过DNSmasq来为这两个虚拟机自动分配私网IP地址 ...

  2. Rsync&plus;Inotify-tools实现数据实时同步

    inotify是一种强大的,细粒度的,异步文件系统时间监控机制,它可以替代crond实现与rsync的触发式文件同步,从而监控文件系统中添加,删除,修改,移动等细粒事件,从LINUX 2.6.13起, ...

  3. 关于pydev的语法的错误提示

    第三方包引入时,eclipse默认会把一些包定为错误的,错误是:“undefined variable from import...” 其实是对的,可是报错,很烦人 解决方法:window -- pr ...

  4. Composite C1是一个&period;Net平台上开源专业的CMS开源项目

    CompositeC1 4 发布 Composite C1是一个.Net平台上开源专业的CMS开源项目,很多的功能用户界面,面向任务的支持与各种工具协作.当编辑内容时在用户端体验很友好.编辑器与开发者 ...

  5. 关于echarts、layer&period;js和jqGrid的知识点

    使用echarts和layer.js直接去官方文档,能解决大部分问题. 但是有些问题,解释不够清楚,在这里记录一下. 1.echarts的使用 第一点:关于echarts的labelline在数据为零 ...

  6. Go vs &period;NET Core 2&period;1

    .NET Core 2.1 正式发布之际,微软团队在博客的中提到了 .NET Core 2.1 中的性能提升.这让我想起了去年 Go 语言 Iris MVC 框架作者做的 Go 与 .NET Core ...

  7. Mysql表中唯一编号的分配机制

    最近遇到一个问题:高并发环境下,如何避免MYSQL一张表里的某些列不要重复. 同其他博友一样 https://blog.csdn.net/jacketinsysu/article/details/51 ...

  8. &lbrack;AI&rsqb;AI章2 框架比较

    深度学习框架比较 神经网络一般包括:训练,测试两大阶段.训练:就是把训练数据(原料)和神经网络模型:如AlexNet.RNN等“倒进” 神经网络训练框架例如cafffe等然后用 CPU或GPU(真火) ...

  9. redhat 防火墙禁止允许的IP链接指定的端口

    编辑:iptables -A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp -s 192.168.4.3 --dport 80 -j A ...

  10. cloudera search环境搭建搭建-solrcloud

    转载:http://blog.csdn.net/xiao_jun_0820/article/details/40539291 本文基于Cloudera Manager5.0.0,所有服务基于CDH5. ...