一篇文章让你搞懂Java中的静态代理和动态代理
什么是代理模式
代理模式是常用的java设计模式,在Java中我们通常会通过new一个对象再调用其对应的方法来访问我们需要的服务。代理模式则是通过创建代理类(proxy)的方式间接地来访问我们需要的服务。
举一个生活中的例子:像我们在网上通过中介租到其背后房东的房子,因为房东也嫌麻烦想省事,此时中介就相当于代理而房东则是被代理,两者是分开的,这样我们就不会直接访问到房东,大部分情况下在中介手中租到的房子都会比原价要贵一些,这好比代理的作用,即不需要通过房东中介也可以在原有房价基础上进行增减或者添加其他的推广方式等操作来进行出租。
在Java中也是如此,我们需要遵循类的单一性原则,只有功能单一这个类被改动的可能性才会最小或者说在尽量不修改源码的前提下进行方法扩展,这样也可以避免在不清楚别人代码的情况下进行修改所导致的各种问题等等。
代理的优缺点
优点:
可以提高程序灵活性和可扩展性。
在不修改原代码的基础上,扩展和增强实现;
代码解耦,在代理中通过参数可以判断真实类,做出不同的响应或调用;
隐藏部分实现过程和细节,可以起到保护目标对象的作用
缺点:
由于在客户端和真实对象之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢;
实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
静态代理在委托类变多的情况时会显的非常臃肿,不方便阅读与使用
静态代理
由程序员创建或工具生成代理类的源码,再编译代理类,即代理类和委托类的关系再程序运行前就已经存在
实现步骤:
定义一个接口和接口的实现类
创建一个代理类同样的实现上述接口
将目标对象注入代理类中然后在代理类方法中调用目标类中的对应方法,这样我们就可以通过代理类在不修改原有方法的基础上进行扩展
代码实现:
/**
* 目标接口
*/
interface Hobby {
//唱
void sing();
//跳
void dance();
}
/**
* 目标类
*/
class MyHobby implements Hobby {
@Override
public void sing() {
System.out.println("sing...");
}
@Override
public void dance() {
System.out.println("dance...");
}
}
/**
* 代理类
*/
class Hobbystaticproxy implements Hobby {
Hobby hobby;
public Hobbystaticproxy(Hobby hobby) {
this.hobby = hobby;
}
@Override
public void sing() {
rap();
hobby.sing();
rap();
}
@Override
public void dance() {
rap();
hobby.dance();
rap();
}
public void rap() {
System.out.println("static-rap...");
}
}
/**
* main方法
*/
class Test {
public static void main(String[] args) {
Hobby hobby = new MyHobby();
Hobbystaticproxy hobbystaticproxy = new Hobbystaticproxy(hobby);
hobbystaticproxy.sing();
hobbystaticproxy.dance();
}
}
输出结果:

可以看到我们在没有修改目标类的情况下通过代理类来实现按目标接口并且分别在目标类的 唱、跳方法前后都扩展了一个rap方法,这就是静态代理
缺点:接口类变化会影响实现类和代理类;比如方法修改返回值、参数类型、增加方法,实现类和代理类都需要修改,不灵活而且很麻烦。
动态代理
使用反射和字节码的技术,在运行期创建指定接口或类的子类,以及其实例对象,控制被代理对象的访问,使用的工具有 jdkproxy、cglibproxy 等。
上述静态代理在程序运行前就需要把对应的类和方法写好,这样就被写死了,这只是一个简单的接口里面也只有两个方法,那如果接口中有几十个方法都需要扩展呢,总不能一个个地手动去添加吧,所以有了我们的动态代理
实现步骤:
定义一个接口和接口的实现类
创建一个代理类实现
InvocationHandler接口(指定运行时生成代理类需要完成的具体任务重写
InvocationHandler接口中的invoke方法创建被代理类的对象,调用处理程序最后通过代理类对象来调用对应方法
代码实现:
/**
* 目标接口
*/
interface Hobby {
void sing();
void dance();
}
/**
* 目标类
*/
class MyHobby implements Hobby {
@Override
public void sing() {
System.out.println("sing...");
}
@Override
public void dance() {
System.out.println("dance...");
}
}
/**
* 代理类
*/
class HobbyDynamicProxy implements InvocationHandler{
private final Object obj;
public HobbyDynamicProxy(Object obj){
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
rap();
Object result = method.invoke(obj, args);
rap();
return result;
}
public void rap() {
System.out.println("Dynamic-rap...");
}
}
/**
* main方法
*/
class Test {
public static void main(String[] args) {
InvocationHandler renterHandler = new HobbyDynamicProxy(new MyHobby());
Hobby hobbyProxy = (Hobby)Proxy.newProxyInstance(Hobby.class.getClassLoader(), new Class[]{Hobby.class},renterHandler);
hobbyProxy.sing();
hobbyProxy.dance();
}
}
输出结果:

InvocationHandler 接口 和 invoke 方法介绍
InvokationHandler是Java 反射包里面的一个接口通过用户类来实现,来激发一个动态代理类的方法。
它只有一个方法:
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
Object:实现方法的代理对象
Method:代理实例激发的方法,Porxy参数中的接口方法
Object[]:传递给方法的一系列参数
静态代理和动态代理的区别
静态代理:
在jvm运行之前就已经获取到代理类的class信息。代理类需要开发者自己写好,即开发者需要自己实现代理类的.java文件,也就是说在项目编译之前就需要存在代理类的.java文件,然后在编译阶段就可以将代理类的.java文件编译成.class文件,从而得到代理类的class信息;
动态代理:
不需要开发人员自己实现代理类的,也就是项目代码中是不存在代理类的.java文件的,既然代理类未由开发者实现,那么程序经过编译之后肯定也不会有代理类的.class文件。也就是说经过编译之后程序未启动运行之前,关于代理类的信息我们一无所知,它是在程序运行过程中需要用到的时候才会由jvm动态生成的,而且生成之后也只存在于内存中,不会写到磁盘保存成.class文件,更加不会保存为.java文件;
总之一句话就是静态代理是需要开发人员自己实现代理类的逻辑的,且代理类的class信息是在程序运行之前就已经可以获取到的了,而动态代理是不需要开发人员自己实现代理类的,
一篇文章让你搞懂Java中的静态代理和动态代理的更多相关文章
- 轻松搞懂Java中的自旋锁
前言 在之前的文章<一文彻底搞懂面试中常问的各种“锁”>中介绍了Java中的各种“锁”,可能对于不是很了解这些概念的同学来说会觉得有点绕,所以我决定拆分出来,逐步详细的介绍一下这些锁的来龙 ...
- 一文彻底搞懂Java中的环境变量
一文搞懂Java环境变量 记得刚接触Java,第一件事就是配环境变量,作为一个初学者,只知道环境变量怎样配,在加上各种IDE使我们能方便的开发,而忽略了其本质的东西,只知其然不知其所以然,随着不断的深 ...
- Java中的反射机制和动态代理
一.反射概述 反射机制指的是Java在运行时候有一种自观的能力,能够了解自身的情况为下一步做准备,其想表达的意思就是:在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法:对于任意一个对象 ...
- 五分钟学Java:一篇文章带你搞懂spring全家桶套餐
原创声明 本文首发于微信公众号[程序员黄小斜] 本文作者:黄小斜 转载请务必在文章开头注明出处和作者. 本文思维导图 什么是Spring,为什么你要学习spring? 你第一次接触spring框架是在 ...
- 一文搞懂--Java中重写equals方法为什么要重写hashcode方法?
Java中重写equals方法为什么要重写hashcode方法? 直接看下面的例子: 首先我们只重写equals()方法 public class Test { public static void ...
- 一文搞懂 Java 中的枚举,写得非常好!
知识点 概念 enum的全称为 enumeration, 是 JDK 1.5 中引入的新特性. 在Java中,被 enum关键字修饰的类型就是枚举类型.形式如下: enum Color { RED, ...
- 来吧,一文彻底搞懂Java中最特殊的存在——null
没事的时候,我并不喜欢逛 P 站,而喜欢逛 programcreek 这些技术型网站,于是那天晚上,在夜深人静的时候,我就发现了一个专注基础但不容忽视的主题.比如说:Java 中的 null 到底是什 ...
- 这一篇文章帮你搞定Java(含Java全套资源)
当下想学习Java开发的人越来越多,对于很多零基础的人来说,没有相关的视频教程及相关的学习线路,学起来是一件很费劲的事情,还有很多人从网上及其它渠道购买视频,这些视频资料的价格对于刚毕业的大学生来说也 ...
- java中的静态分派和动态分派
多态是java的基本特征之一,多态即一个对象具有多种形态(多种表达形式,猴子是动物的一种的表现形式),例如:子类是父类的一种形态. 当方法重载时,就会涉及到多态. 1:在重载时是通过参数的静态类型,而 ...
随机推荐
- 【Java中的线程】java.lang.Thread 类分析
进程和线程 联想一下现实生活中的例子--烧开水,烧开水时是不是不需要在旁边守着,交给热水机完成,烧开水这段时间可以去干一点其他的事情,例如将衣服丢到洗衣机中洗衣服.这样开水烧完,衣服洗的也差不多了.这 ...
- Vue路由器的hash和history两种工作模式 && Vue项目编译部署
1 # 一.Vue路由器的两种工作模式 2 # 1.对于一个uri来说,什么是hash值? 井号及其后面的内容就是hash值. 3 # 2.hash值不会包括含在HTTP请求中,即:hash值不会带给 ...
- Kettle需求场景复现
前置说明 遍历文件夹下的文件,读取所有的sheet页(指定的sheet)落库 读取execl文件和csv文件,获得文件中sheet/csv数据,进行落库,并增加字段实现更新: 如果execl中存在两个 ...
- 封装Fraction-分数类(C++)
Fraction 分数类 默认假分数,可自行修改 由于concept的原因 template <typename T> concept is_float_v = std::is_float ...
- 多线程与高并发(五)—— 源码解析 ReentrantLock
一.前言 ReentrantLock 是基于 AQS 实现的同步框架,关于 AQS 的源码在 这篇文章 已经讲解过,ReentrantLock 的主要实现都依赖AQS,因此在阅读本文前应该先了解 AQ ...
- Windows下安装新硬盘
首先,插上一个硬盘然后开机,会发现"我的电脑/此电脑"里面并没有这个硬盘,这是因为此时硬盘还没初始化和分区,分完区后每个分区会被作为一个逻辑盘显示在里面.那么接下来就是过程. Wi ...
- 从C过渡到C++——换一个视角深入数组[真的存在高效吗?](2)
从C过渡到C++--换一个视角深入数组[真的存在高效吗?](2) C风格高效的数组遍历 在过渡到C++之前我还是想谈一谈如何书写高效的C的代码,这里的高效指的是C代码的高效,也就是在不开启编译器优化下 ...
- 分库分表之ShardingSphere
目录 分库分表诞生的前景 分库分表的方式(垂直拆分,水平复制) 1.垂直拆分 1.1 垂直分库 1.2 垂直分表 2.水平拆分 2.1 水平分库 2.2 水平分表 分库分库中间件 ShardingSp ...
- [HDU1812] Count the Tetris - polya定理
题面 Problem Description 话说就是因为这个游戏,Lele已经变成一个名人,每当他一出现在公共场合,就有无数人找他签名,挑战. 为了防止引起社会的骚动,Lele决定还是乖乖呆在家里. ...
- Hive的基本知识与操作
Hive的基本知识与操作 目录 Hive的基本知识与操作 Hive的基本概念 为什么使用Hive? Hive的特点: Hive的优缺点: Hive应用场景 Hive架构 Client Metastor ...