通俗易懂的讲解一下Java的代理模式
一、基本概念
代理模式是对象的结构模式。
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用(接口的引用)
二、静态代理
静态代理是指,代理类在程序运行前就已经定义好,其与**目标类(被代理类)**的关系在程序运行前就已经确立。
静态代理类似于企业与企业的法律顾问间的关系。法律顾问与企业的代理关系,并不是在“官司“发生后才建立的,而是之前就确立好的一种关系。
而动态代理就是外面打官司一样,是官司发生了之后临时请的律师。
代理可以看做就是在被代理对象外面包裹一层(和装饰者类似但又不同):
案例: 比如我们有一个可以移动的坦克,它的主要方法是move()
,但是我们需要记录它移动的时间,以及在它移动前后做日志,其静态代理的实现模式就类似下面的图:
两个代理类以及结构关系:
代码:
public interface Movable {
void move();
}
public class Tank implements Movable {
@Override
public void move() {
// 坦克移动
System.out.println("Tank Moving......");
try {
Thread.sleep(new Random().nextInt(5000)); // 随机产生 1~5秒, 模拟坦克在移动
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
两个代理类: TankTimeProxy
和TankLogProxy
:
public class TankTimeProxy implements Movable { private Movable tank; public TankTimeProxy(Movable tank) {
this.tank = tank;
} @Override
public void move() {
// 在前面做一些事情: 记录开始时间
long start = System.currentTimeMillis();
System.out.println("start time : " + start); tank.move(); // 在后面做一些事情: 记录结束时间,并计算move()运行时间
long end = System.currentTimeMillis();
System.out.println("end time : " + end);
System.out.println("spend all time : " + (end - start)/1000 + "s.");
}
}
public class TankLogProxy implements Movable { private Movable tank; public TankLogProxy(Movable tank) {
this.tank = tank;
} @Override
public void move() {
// tank 移动前记录日志
System.out.println("Tank Log start......."); tank.move(); // tank 移动后记录日志
System.out.println("Tank Log end.......");
}
}
测试:
public class Client {
public static void main(String[] args){
Movable target = new TankLogProxy(new TankTimeProxy(new Tank())); //先记录时间,再记录日志
// Movable target = new TankTimeProxy(new TankLogProxy(new Tank())); //先记录日志,再记录时间
target.move();
}
}
输出:
Tank Log start.......
start time : 1551271511619
Tank Moving......
end time : 1551271514522
spend all time : 2s.
Tank Log end.......
这其中有两个很重要的点,那就是:
- 两个代理对象内部都有着被代理对象(target)实现的接口的引用;
- 且两个代理对象都实现了被代理对象(target)实现的接口;
三、基本动态代理
上面静态代理的缺点在哪?
现在单看做时间这个代理,如果我们现在多了一个飞机,飞机里面的方法是fly()
,现在要给飞机做代理,那么我们不能用之前写的TankTimeProxy
,我们需要额外的写一个PlaneTimeProxy
,这明显是冗余代码,所以这就是静态代理最大的缺点,这可以用动态代理解决。
动态代理是指,程序在整个运行过程中根本就不存在目标类的代理类(在JDK内部叫$Proxy0
,我们看不到),目标对象的代理对象只是由代理生成工具(如代理工厂类) 在程序运行时由 JVM 根据反射等机制动态生成的。代理对象与目标对象的代理关系在程序运行时才确立。
对比静态代理,静态代理是指在程序运行前就已经定义好了目标类的代理类。代理类与目标类的代理关系在程序运行之前就确立了。
首先看动态代理的一些特点:
- 动态代理不需要写出代理类的名字,你要的代理对象我直接给你产生,是使用的时候生成的;
- 只需要调用
Proxy.newProxyInstance()
就可以给你产生代理类;
JDK动态代理相关API:
下面看使用动态代理解决上面的问题(可以用TimeProxy
代理一切对象):
public interface Movable {
void move();
}
public class Tank implements Movable {
@Override
public void move() {
// 坦克移动
System.out.println("Tank Moving......");
try {
Thread.sleep(new Random().nextInt(5000)); // 随机产生 1~5秒, 模拟坦克在移动
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
新增的飞机:
public interface Flyable {
void fly();
}
public class Plane implements Flyable{
@Override
public void fly() {
System.out.println("Plane Flying......");
try {
Thread.sleep(new Random().nextInt(5000)); // 随机产生 1~5秒, 飞机在飞行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我们的关键处理,即编写MyTimeProxyInvocationHandler
:
// 静态代理做不到既为飞机做时间代理,又为坦克做时间代理,但是动态代理可以为所有对象做代理
public class MyTimeProxyInvocationHandler implements InvocationHandler { private Object target;//注意这里是 Object ,不是Movable或者Flyable public MyTimeProxyInvocationHandler(Object target) {
this.target = target;
} // proxy : 代理对象 可以是一切对象 (Object)
// method : 目标方法
// args : 目标方法的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 在前面做一些事情: 记录开始时间
long start = System.currentTimeMillis();
System.out.println("start time : " + start); method.invoke(target, args); // 调用目标方法 invoke是调用的意思, 可以有返回值的方法(我们这里move和fly都没有返回值) // 在后面做一些事情: 记录结束时间,并计算move()运行时间
long end = System.currentTimeMillis();
System.out.println("end time : " + end);
System.out.println("spend all time : " + (end - start)/1000 + "s."); return null;
}
}
最后测试类:
public class Client {
public static void main(String[] args){
Movable tank = new Tank();
//可以为所有对象产生时间代理的 InvocationHandler
MyTimeProxyInvocationHandler myInvocationHandler = new MyTimeProxyInvocationHandler(tank);
Movable tankProxy = (Movable) Proxy.newProxyInstance(
tank.getClass().getClassLoader(),
tank.getClass().getInterfaces(),
myInvocationHandler
);
tankProxy.move(); System.out.println("--------------------"); Flyable plane = new Plane();
myInvocationHandler = new MyTimeProxyInvocationHandler(plane);
// 为飞机产生代理, 为..产生代理,这样可以为很多东西产生代理,静态代理做不到
Flyable planeProxy = (Flyable) Proxy.newProxyInstance(
plane.getClass().getClassLoader(),
plane.getClass().getInterfaces(),
myInvocationHandler
);
planeProxy.fly();
}
}
输出(同时为Tank
和Plane
做了代理):
start time : 1551275526486
Tank Moving......
end time : 1551275531193
spend all time : 4s.
--------------------
start time : 1551275531195
Plane Flying......
end time : 1551275532996
spend all time : 1s.
我们分析一下这个代理过程:
调用过程(重要):
- JDK内部的
Proxy
类在内部创建了一个$Proxy0
的代理对象(它实现了目标对象所在接口Movable
; $Proxy0
内部有InvocationHandler
接口的引用,然后在$Proxy
中调用了接口的invoke()
方法;- 而我们将
InvocationHandler
接口的实现类传入了Proxy
,所以我们在实现类中加入的前后逻辑就会得到执行;
如果这里还不够理解,可以看代理模式(二),会模拟实现JDK的底层实现。
四、CGLIB动态代理
问题: 使用 JDK 的 Proxy 实现代理,要求目标类与代理类实现相同的接口。若目标类不存在接口,则无法使用该方式实现。
可以用 CGLIB 来解决上面的问题。
CGLIB 代理的生成原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代理对象。
所以,使用CGLIB 生成动态代理,要求目标类必须能够被继承,即不能是 final 的类。
基本结构:
代码:
Tank
类(没有接口)
// 没有实现接口
public class Tank { public void move() {
// 坦克移动
System.out.println("Tank Moving......");
try {
Thread.sleep(new Random().nextInt(5000)); // 随机产生 1~5秒, 模拟坦克在移动
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
MyCglibFactory
类:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method; //需要实现MethodInterceptor, 当前这个类的对象就是一个回调对象
// MyCglibFactory 是 类A,它调用了Enhancer(类B)的方法: setCallback(this),而且将类A对象传给了类B
// 而类A 的 方法intercept会被类B的 setCallback调用,这就是回调设计模式
public class MyCglibFactory implements MethodInterceptor { //public interface MethodInterceptor extends Callback private Tank target; public MyCglibFactory(Tank target) {
this.target = target;
} public Tank myCglibCreator() {
Enhancer enhancer = new Enhancer(); // 设置需要代理的对象 : 目标类(target) , 也是父类
enhancer.setSuperclass(Tank.class); // 设置代理对象, 这是回调设计模式: 设置回调接口对象 :
enhancer.setCallback(this); // this代表当前类的对象,因为当前类实现了Callback return (Tank) enhancer.create();
} // 这个就是回调方法(类A的方法)
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 在前面做一些事情: 记录开始时间
long start = System.currentTimeMillis();
System.out.println("start time : " + start); method.invoke(target, args); // 在后面做一些事情: 记录结束时间,并计算move()运行时间
long end = System.currentTimeMillis();
System.out.println("end time : " + end);
System.out.println("spend all time : " + (end - start)/1000 + "s.");
return null;
}
}
测试:
public class Client {
public static void main(String[] args){
Tank proxyTank = new MyCglibFactory(new Tank()).myCglibCreator();
proxyTank.move();
}
}
输出(进行了时间代理TimeProxy
):
start time : 1551327522964
Tank Moving......
end time : 1551327526214
spend all time : 3s.
上面的设计模式用到了回调设计模式: 在 Java 中,类 A
调用类 B
中的某个方法 b()
,然后类 B
又在某个时候反过来调用类 A
中的某个方法 a()
,对于 A
来说,这个 a()
方法便叫做回调方法。
Java 的接口提供了一种很好的方式来实现方法回调。这个方式就是定义一个简单的接口,在接口之中定义一个我们希望回调的方法。这个接口称为回调接口。(Callback
) 在前面的例子中,我们定义的 MyCglibFactory
类就相当于前面所说的 A
类,而 Enhancer
类则是 B
类。A
类中调用了 Enhancer
类的 setCallback(this)
方法,并将回调对象 this
作为实参传递给了Enhancer 类。Enhancer 类在后续执行过程中,会调用A
类中的intercept()
方法,这个 intercept()方法就是回调方法。
免费Java高级资料需要自己领取,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G。
传送门:https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q
通俗易懂的讲解一下Java的代理模式的更多相关文章
- Java学习——代理模式
Java中的三种代理模式 一,什么是代理模式? 代理模式是一种设计模式,简单的来说就是在不改变源码的情况下,实现对目标对象的功能扩展. 比如有个歌手对象叫Singer,这个对象有一个唱歌方法叫sing ...
- Java设计模式-代理模式之动态代理(附源代码分析)
Java设计模式-代理模式之动态代理(附源代码分析) 动态代理概念及类图 上一篇中介绍了静态代理,动态代理跟静态代理一个最大的差别就是:动态代理是在执行时刻动态的创建出代理类及其对象. 上篇中的静态代 ...
- Java设计模式—代理模式
代理模式(Proxy Pattern)也叫做委托模式,是一个使用率非常高的模式. 定义如下: 为其他对象提供一种代理以控制对这个对象的访问. 个人理解: 代理模式将原类进行封装, ...
- Java设计模式——代理模式实现及原理
简介 Java编程的目标是实现现实不能完成的,优化现实能够完成的,是一种虚拟技术.生活中的方方面面都可以虚拟到代码中.代理模式所讲的就是现实生活中的这么一个概念:中介. 代理模式的定义:给某一个对象提 ...
- JAVA动态代理模式(从现实生活角度理解代码原理)
所谓动态代理,即通过代理类:Proxy的代理,接口和实现类之间可以不直接发生联系,而可以在运行期(Runtime)实现动态关联. java动态代理主要是使用java.lang.reflect包中的两个 ...
- JAVA 设计模式 代理模式
用途 代理模式 (Proxy) 为其他对象提供一种代理以控制对这个对象的访问. 代理模式是一种结构型模式. 结构
- java之代理模式
静态代理: java代理是一种模式--代理模式.采用代理模式,可以在不改变目标类代码的基础上,通过代理对象,来增加额外的功能(比如增加日志检测等)或者只需要目标对象的部分行为. java中,代理分为静 ...
- java 之 代理模式(大话设计模式)
java代理模式顾名思义,就类似于大学,我想和冰可乐,可是我又在玩游戏,于是我让我的室友帮我把可乐带回来,简单的来说我的室友就是代理,而买可乐就是需要做的行为.这是笔者理解的代理模式 大话设计模式-类 ...
- JAVA中代理模式
代理模式 在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用.代理对象可以在客户端和目标对象之间起到 中介的作用,并且可以通过代理对象去掉客户不能 ...
随机推荐
- [TCP/IP] TCP报文长度是由什么确定的
MTU:最大传输单元,以太网的MTU为1500Bytes MSS:最大分解大小,为每次TCP数据包每次传输的最大数据的分段大小,由发送端通知接收端,发送大于MTU就会被分片 TCP最小数据长度为146 ...
- 使用Lambda解决_inbound_nodes错误
Keras出现了下面的错误: AttributeError: 'NoneType' object has no attribute '_inbound_nodes' 原因是使用了Keras backe ...
- Shiro RememberMe 1.2.4 反序列化漏洞复现
目录 原理解释 环境搭建 复现过程 原理解释 Apache Shiro是一个Java安全框架,执行身份验证.授权.密码和会话管理. shiro默认使用了CookieRememberMeMana ...
- JAVAWEB复习-JS
1.概述 JavaScript是基于对象和事件的脚本语言,主要应用在客户端 特点:信息动态交互,不可直接访问本地磁盘,只要是可以解析js的浏览器都可以跨平台执行 2.JS和JAVA比较 a:JS是面向 ...
- Bayesian Optimization使用Hyperopt进行参数调优
超参数优化 Bayesian Optimization使用Hyperopt进行参数调优 1. 前言 本文将介绍一种快速有效的方法用于实现机器学习模型的调参.有两种常用的调参方法:网格搜索和随机搜索.每 ...
- 201871010105-曹玉中《面向对象程序设计(java)》第十周学习总结
201871010105-曹玉中<面向对象程序设计(java)>第十周学习总结 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这 ...
- day37_8_21表的查询
一.语法 表的查询一般使用select关键字,配合where筛选.顺序如下: # 先后顺序 from where select 二.where约束条件 首先先建立表: create table emp ...
- 每天一道Rust-LeetCode(2019-06-10)
每天一道Rust-LeetCode(2019-06-02) Z 字形变换 坚持每天一道题,刷题学习Rust. 题目描述 https://leetcode-cn.com/problems/simplif ...
- Redis面试题(46题)
1.什么是Redis?简述它的优缺点? Redis 的全称是:Remote Dictionary.Server,本质上是一个 Key-Value 类型的内存数据库,很像memcached,整个数据库统 ...
- printkd
#include <linux/fs.h> #include <asm/uaccess.h> #include <linux/namei.h> #include & ...