通过模拟JDK中的动态代理,由浅入深讲解动态代理思想.
个人认为动态代理在设计模式中算是比较难的, 本篇文章将从无到有, 从一个简单代码示例开始迭代, 逐步深入讲解动态代理思想.
场景引入
- 假设现在有一个坦克类, 它实现了
Moveable
接口, 里面有一个move()
移动的方法. 代码如下:
class Tank implements Moveable{
@Override
public void move(){
System.out.println("坦克开始移动...");
try {
Thread.sleep((long) (Math.random() * 5000));
System.out.println("坦克移动结束...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
interface Moveable{
public void move();
}
- 为了能计算坦克移动所花费的时间我们打算在坦克的
move()
方法的前后添加一些代码, 用于记录坦克move()
方法的执行时间. - 我们将使用代理类, 并在代理类中执行上述操作, 首先展示的是以
继承
的方式进行代理.
class MoveTimeProxy1 extends Tank{
@Override
public void move() {
long start = System.currentTimeMillis();//开始时间
super.move();//调用坦克的move()方法
long end = System.currentTimeMillis();//结束时间
System.out.println("执行该方法用了" + (end - start) + "毫秒");
}
}
- 接着我们展示另外一种通过
聚合
实现代理的方式
class MoveTimeProxy2 implements Moveable{
Tank tank;
public MoveTimeProxy2(Tank tank){
this.tank = tank;
}
@Override
public void move() {
long start = System.currentTimeMillis();//开始时间
tank.move();//调用坦克的move()方法
long end = System.currentTimeMillis();//结束时间
System.out.println("执行该方法用了" + (end - start) + "毫秒");
}
}
- 以上两种均为实现代理的方式, 如果要分个优劣的话,
继承
方式的代理会差一些. 想想看, 如果现在除了记录时间, 还要记录日志的话, 则要创建一个新的继承
代理类并重写move()
方法. 如果需求变更, 需要先记录日志, 再记录时间的话, 又要创建一个新的继承
代理类. 如此下去, 代理类的创建将没完没了. - 相比之下,
聚合
实现的代理类则灵活得多. 每一个聚合
代理类能够实现一种代理, 并且代理的顺序是可以替换的. 请看代码(聚合
代理类的代码有所修改)
public class ProxyTest {
public static void main(String[] args) {
TimeProxy tp = new TimeProxy(new Tank());
LogProxy lp = new LogProxy(tp);
lp.move();
}
}
class TimeProxy implements Moveable{//记录时间的代理
Moveable m;//不再持有Tank引用, 而是持有Moveable接口引用
public TimeProxy(Moveable m){
this.m = m;
}
@Override
public void move() {
long start = System.currentTimeMillis();//开始时间
m.move();//调用move()方法
long end = System.currentTimeMillis();//结束时间
System.out.println("执行该方法用了" + (end - start) + "毫秒");
}
}
class LogProxy implements Moveable{//打印日志的代理
Moveable m;
public LogProxy(Moveable m){
this.m = m;
}
@Override
public void move() {
System.out.println("日志: 开始测试坦克移动...");
m.move();
System.out.println("日志: 坦克移动结束...");
}
}
动态代理引入
- 看完上面的例子, 大家应该对
代理
一词有更深刻的理解. 但是上面的代码中, 为坦克生成的代理类TimeProxy
是我们在代码中写死的, 所以这顶多算个静态代理, 如何通过动态的方式产生代理呢? - 在讲解动态代理之前我们需要明确的是, 上面的
聚合
代理方式通过持有某个接口的引用完成代理, 所以我们是针对某个接口产生代理, 而不是对某个具体的对象产生代理. - 为了模拟
Java
中的实现, 我们创建一个Proxy
类, 里面提供一个newProxyInstance()
方法, 用于返回一个代理. 我们希望通过如下代码就能动态生成一个代理.
public static void main(String[] args) {
Tank tank = new Tank();
Moveable m = (Moveable)Proxy.newProxyInstance();//动态获得一个代理
m.move();
}
- 从上面的代码可以看到我们甚至都不需要知道代理类的名字就可以动态的获取一个代理. 我们以上述的记录时间的代理为例子, 获取一个时间代理类.
- 在
newProxyInstance()
方法中, 我们先把原来TimeProxy
的源代码以字符串的方式存放, 再通过写入文件的方式创建出TimeProxy.java
文件. 然后通过Java原生的编译api将TimeProxy.java
编译成TimeProxy.class
文件. 最后把该class文件加载到内存中, 并调用其构造方法创建对象, 返回该代理对象. - 温馨提示: 本段代码不是专门教大家如何动态生成类, 因为有很多开源工具比如CGLib, ASM等可以更专业地完成这件事情, 这里仅使用Java原生API完成, 主要为了展现动态生成一个代理对象背后的过程.
class Proxy{
public static Object newProxyInstance() throws Exception {
//把整个TimeProxy类的实现写入字符串, 通过编译这一字符串得到TimeProxy对象
String src = "package designPattern.proxy;\n" +
"\n" +
"class TimeProxy implements Moveable{\n" +
" Moveable m;//不再持有Tank引用, 而是持有Moveable接口引用\n" +
"\n" +
" public TimeProxy(Moveable m){\n" +
" this.m = m;\n" +
" }\n" +
"\n" +
" @Override\n" +
" public void move() {\n" +
" long start = System.currentTimeMillis();//开始时间\n" +
" m.move();//调用坦克的move()方法\n" +
" long end = System.currentTimeMillis();//结束时间\n" +
" System.out.println(\"执行该方法用了\" + (end - start) + \"毫秒\");\n" +
" }\n" +
"}";
String filename = System.getProperty("user.dir")
+ "/src/main/java/designPattern/proxy/TimeProxy.java";//文件名(生成类的路径)
File f = new File(filename);
FileWriter fw = new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();
//编译
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();//拿到系统当前默认的编译器, 即Javac
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable units = fileManager.getJavaFileObjects(filename);//得到文件对象
JavaCompiler.CompilationTask t = compiler.getTask(null, fileManager, null, null, null, units);
t.call();//进行编译
fileManager.close();
//把class文件加载进内存并创建对象
URL[] urls = new URL[]{new URL("file:/" + System.getProperty("user.dir") + "/src")};
URLClassLoader ul = new URLClassLoader(urls);
Class c = ul.loadClass("designPattern.proxy.TimeProxy");//拿到class对象
Constructor ctr = c.getConstructor(Moveable.class);//拿到参数为Moveable的构造方法
Moveable m = (Moveable)ctr.newInstance(new Tank());//创建代理对象
return m;
}
}
- 我们继续对上面的代码进行优化, 目前代码中指定生成的是实现了
moveable
接口的代理对象. 而上面我们提到过动态代理是基于某个接口的(聚合型代理), 所以我们希望能够动态地指定接口, 并生成相应的代理类.
public class ProxyTest {
public static void main(String[] args) throws Exception {
Tank tank = new Tank();
Moveable m = (Moveable)Proxy.newProxyInstance(Moveable.class);//传入接口参数动态获得一个代理
m.move();
}
}
class Proxy{
public static Object newProxyInstance(Class intfce) throws Exception {
//把整个TimeProxy类的实现写入字符串, 通过编译这一字符串得到TimeProxy对象
String methodStr = "";
String n = "\n";
Method[] methods = intfce.getMethods();//拿到接口中的所有方法
for(Method m : methods){//拼接方法
methodStr += " @Override\n" +
" public void " + m.getName() + "() {\n" +
" long start = System.currentTimeMillis();//开始时间\n" +
" m.move();//调用坦克的move()方法\n" +
" long end = System.currentTimeMillis();//结束时间\n" +
" System.out.println(\"执行该方法用了\" + (end - start) + \"毫秒\");\n" +
" }\n";
}
//拼接出整个类
String src = "package designPattern.proxy;\n" +
"\n" +
"class TimeProxy implements " + intfce.getName() + "{\n" +
" Moveable m;//不再持有Tank引用, 而是持有Moveable接口引用\n" +
"\n" +
" public TimeProxy(Moveable m){\n" +
" this.m = m;\n" +
" }\n" +
"\n" + methodStr +
"}";
String filename = System.getProperty("user.dir")
+ "/src/main/java/designPattern/proxy/TimeProxy.java";//文件名(生成类的路径)
File f = new File(filename);
FileWriter fw = new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();
//编译
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();//拿到系统当前默认的编译器, 即Javac
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable units = fileManager.getJavaFileObjects(filename);//得到文件对象
JavaCompiler.CompilationTask t = compiler.getTask(null, fileManager, null, null, null, units);
t.call();//进行编译
fileManager.close();
//把class文件加载进内存并创建对象
URL[] urls = new URL[]{new URL("file:/" + System.getProperty("user.dir") + "/src")};
URLClassLoader ul = new URLClassLoader(urls);
Class c = ul.loadClass("designPattern.proxy.TimeProxy");//拿到class对象
Constructor ctr = c.getConstructor(Moveable.class);//拿到参数为Moveable的构造方法
Object m = ctr.newInstance(new Tank());//创建代理对象
return m;
}
}
动态代理进阶
- 在上一个版本中我们已经能够动态地生成一个代理对象了, 但是还有一个最重要的也是最难的点没有实现. 在上面的代码中我们对被代理对象进行的操作是记录方法的运行时间, 是在代码里面写死的. 我们希望可以让用户自定义增强手段, 比如说记录时间(
TimeProxy
), 输出日志(LogProxy
), 事务操作等等. - 对于这种在被代理对象前后进行增强的操作, 我们定义一个
InvocationHandler
接口, 并在它的实现类中给出具体的操作. 我们以初始的记录时间操作为例. - 下面给出完整的代码, 如果看不懂可以结合代码后面的总结来看.
public class ProxyTest {
public static void main(String[] args) throws Exception {
Tank tank = new Tank();
InvocationHandler h = new TimeHandler(tank);
Moveable m = (Moveable)Proxy.newProxyInstance(Moveable.class, h);//动态获得一个代理
m.move();
}
}
interface InvocationHandler{
public void invoke(Object o, Method m);//参数o指定执行对象(代理对象, 可能会用到), m指定执行的方法
}
class TimeHandler implements InvocationHandler{
private Object target;
public TimeHandler(Object target){
this.target = target;
}
@Override
public void invoke(Object o, Method m) {
long start = System.currentTimeMillis();//这行是用户自己加的增强代码
try{
m.invoke(target);
} catch (Exception e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();//这行是用户自己加的增强代码
System.out.println("执行该方法用了" + (end - start) + "毫秒");//这行是用户自己加的增强代码
}
}
class Proxy{
public static Object newProxyInstance(Class intfce, InvocationHandler h) throws Exception {
//把整个TimeProxy类的实现写入字符串, 通过编译这一字符串得到TimeProxy对象
String methodStr = "";
Method[] methods = intfce.getMethods();//拿到接口中的所有方法
for(Method m : methods){//拼接方法
methodStr += " @Override\n" +
" public void " + m.getName() + "() {\n" +
" try{\n" +
" Method md = " + intfce.getName() + ".class.getMethod(\"" + m.getName() + "\");\n" +
" h.invoke(this, md);\n" +
" }catch(Exception e){e.printStackTrace();}\n" +
" }\n";
}
//拼接出整个类
String src = "package designPattern.proxy;\n" +
"import java.lang.reflect.Method;\n" +
"\n" +
"class $Proxy1 implements " + intfce.getName() + "{\n" +
" designPattern.proxy.InvocationHandler h;\n" +
"\n" +
" public $Proxy1(InvocationHandler h){\n" +
" this.h = h;\n" +
" }\n" +
"\n" + methodStr +
"}";
String filename = System.getProperty("user.dir")
+ "/src/main/java/designPattern/proxy/$Proxy1.java";//文件名(生成类的路径)
File f = new File(filename);
FileWriter fw = new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();
//编译
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();//拿到系统当前默认的编译器, 即Javac
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable units = fileManager.getJavaFileObjects(filename);//得到文件对象
JavaCompiler.CompilationTask t = compiler.getTask(null, fileManager, null, null, null, units);
t.call();//进行编译
fileManager.close();
//把class文件加载进内存并创建对象
URL[] urls = new URL[]{new URL("file:/" + System.getProperty("user.dir") + "/src")};
URLClassLoader ul = new URLClassLoader(urls);
Class c = ul.loadClass("designPattern.proxy.$Proxy1");//拿到class对象
Constructor ctr = c.getConstructor(InvocationHandler.class);//拿到参数为Moveable的构造方法
Object m = ctr.newInstance(h);//创建代理对象
return m;
}
}
class Tank implements Moveable{
@Override
public void move(){
System.out.println("坦克开始移动...");
try {
Thread.sleep((long) (Math.random() * 5000));
System.out.println("坦克移动结束...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//输出结果
坦克开始移动...
坦克移动结束...
执行该方法用了4302毫秒
总结
- 在这里有必要对上面整个动态代理的实现总结一下.
- 首先要明确我们是基于一个接口进行代理, 比如本文中给出了一个
Moveable
接口, 而Tank
坦克类实现了Moveable
接口, 并实现了move()
方法. - 现在我们想对
move()
方法进行增强, 比如说记录这个方法的执行时间, 我们需要动态地获得一个代理类. - 而且为了让增强具有可扩展性, 我们创建了
InvocationHandler
接口, 里面有一个invoke(Object o, Method m)
方法. 调用invoke()
方法时, 需要传递两个参数, 一个是代理对象的引用o
(可能会用上), 另一个是需要被增强的方法, 本例中是move()
方法. - 在
invoke()
方法中我们可以在被增强方法的前后添加增强代码.
public void invoke(Object o, Method m) {
long start = System.currentTimeMillis();//这行是用户自己加的增强代码
try{
m.invoke(target);//执行被增强的方法, 例子中的move()
} catch (Exception e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();//这行是用户自己加的增强代码
System.out.println("执行该方法用了" + (end - start) + "毫秒");//这行是用户自己加的增强代码
}
- 补充一点, 要创建
InvocationHandler
的具体对象, 比如这里的TimeHander
, 需要传入被增强的对象, 这里是tank
, 因为被增强方法move()
需要由被增强对象执行.
- 搞定
InvocationHandler
后, 回头看为我们动态产生代理的Proxy
类, 这个类需要有一个属性字段InvocationHandler h
, 因为在进行增强时, 调用的是InvocationHandler
实现类中的invoke()
方法. 在动态代理进阶
一节的最后版本代码中, 我们动态生成的代理类源码是这样的:
class $Proxy1 implements designPattern.proxy.Moveable{
designPattern.proxy.InvocationHandler h;
public $Proxy1(InvocationHandler h){
this.h = h;
}
@Override
public void move() {
try{
Method md = designPattern.proxy.Moveable.class.getMethod("move");
h.invoke(this, md);
}catch(Exception e){e.printStackTrace();}
}
}
- 由源码可以看到当调用代理类的
move()
方法进行增强时, 会调用InvocaitonHandler
的实现类中的invoke()
方法, 传入代理类自身和被增强的方法, 这样就可以使用自定义的增强代码进行增强了.
- 动态代理有什么好处?
- 对于任意一个实现了某个接口的类, 我们都可以对其实现的接口中定义的方法进行增强.
- 可以在被增强方法前后自定义增强的逻辑.
- 可以进行多层嵌套代理.
通过模拟JDK中的动态代理,由浅入深讲解动态代理思想.的更多相关文章
- 流量分析系统---echarts模拟迁移中 ,geocoord从后台获取动态数值
由于在echarts的使用手册中说了 {Object} geoCoord (geoCoord是Object类型) ,所以不能用传统的字符串拼接或数组的方式赋值.在后台的controller中用Map& ...
- springmvc 动态代理 JDK实现与模拟JDK纯手写实现。
首先明白 动态代理和静态代理的区别: 静态代理:①持有被代理类的引用 ② 代理类一开始就被加载到内存中了(非常重要) 动态代理:JDK中的动态代理中的代理类是动态生成的.并且生成的动态代理类为$Pr ...
- 手动模拟JDK动态代理
为哪些方法代理? 实现自己动态代理,首先需要关注的点就是,代理对象需要为哪些方法代理? 原生JDK的动态代理的实现是往上抽象出一层接口,让目标对象和代理对象都实现这个接口,怎么把接口的信息告诉jdk原 ...
- 浅谈Spring中JDK动态代理与CGLIB动态代理
前言Spring是Java程序员基本不可能绕开的一个框架,它的核心思想是IOC(控制反转)和AOP(面向切面编程).在Spring中这两个核心思想都是基于设计模式实现的,IOC思想的实现基于工厂模式, ...
- 面试官:你说你懂动态代理,那你知道为什么JDK中的代理类都要继承Proxy吗?
之前我已经写过了关于动态代理的两篇文章,本来以为这块应该没啥问题,没想到今天又被难住了- 太难了!!! 之前文章的链接: 动态代理学习(一)自己动手模拟JDK动态代理. 动态代理学习(二)JDK动态代 ...
- 017 Java中的静态代理、JDK动态代理、cglib动态代理
一.静态代理 代理模式是常用设计模式的一种,我们在软件设计时常用的代理一般是指静态代理,也就是在代码中显式指定的代理. 静态代理由业务实现类.业务代理类两部分组成.业务实现类负责实现主要的业务方法,业 ...
- Java中如何实现代理机制(JDK动态代理和cglib动态代理)
http://blog.csdn.net/skiof007/article/details/52806714 JDK动态代理:代理类和目标类实现了共同的接口,用到InvocationHandler接口 ...
- 动态代理(一)——JDK中的动态代理
在开始动态代理的描述之前,让我们认识下代理.代理:即代替担任执行职务.在面向对象世界中,即寻找另一个对象代理目标对象与调用者交互.Java中分为静态代理和动态代理.这里对于静态代理不做详述.它们之间的 ...
- Spring AOP详解 、 JDK动态代理、CGLib动态代理
AOP是Aspect Oriented Programing的简称,面向切面编程.AOP适合于那些具有横切逻辑的应用:如性能监测,访问控制,事务管理以及日志记录.AOP将这些分散在各个业务逻辑中的代码 ...
随机推荐
- 代码生成平台Xxl-Code-Generator
<代码生成平台Xxl-Code-Generator> 一.简介 1.1 概述 Xxl-Code-Generator 是一个 "controller/service/dao/myb ...
- python爬虫入门(四)利用多线程爬虫
多线程爬虫 先回顾前面学过的一些知识 1.一个cpu一次只能执行一个任务,多个cpu同时可以执行多个任务2.一个cpu一次只能执行一个进程,其它进程处于非运行状态3.进程里包含的执行单元叫线程,一个进 ...
- HBuilder真机联调、手机运行
第一步:先确认手机是否连接上 未连接状态 如下图所示为已连接状态 导致手机未成功连接的原因: (1)手机与电脑未用USB数据线连接(嘿嘿,这一部大家估计都做到了,可略过) (2)电脑上需要安装电脑版的 ...
- Robot framework之元素定位实战
1.1 id 和name 定位 Web页面都是由许多标签和元素组成的,每个标签或元素都是很多属性,好比一个人 id 和name 可以看作一个人的身份证号和姓名.下面看下教育局招生系统的用户名输入 ...
- spring+activemq中多个consumer同时处理消息时遇到的性能问题
最近在做数据对接的工作,用到了activemq,我需要从activemq中接收消息并处理,但是我处理数据的步骤稍微复杂,渐渐的消息队列中堆的数据越来越多,就想到了我这边多开几个线程来处理消息. 可是会 ...
- 【Quartz】持久化到数据库【五】
前言 我们做到这里已经对Quartz定时器组件已经是学会了基本的使用了.但是我们有没有想过任务开启之后万一断掉了,当机了我们怎么办,你是否还想继续执行原先的任务.我们普通的创建是把任务放在内存中存 ...
- SSM-SpringMVC-09:SpringMVC中以继承MutiActionController类的方式实现处理器
------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- MutiActionController类,多行动处理器,简单来说,就是可以一个处理器中有多个处理方法,分支 ...
- 解决fatal error LNK1168的终极方法
很多人的VC++或Visual studio 会出现fatal error LNK1168错误很是头疼,MS也说不清, 什么改权限.用户名.注册表.CMD,卸载杀毒软件...一切都瞎扯,除非reins ...
- 你不知道的JavaScript--Item13 理解 prototype, getPrototypeOf 和__proto__
1.深入理解prototype, getPrototypeOf和_ proto _ prototype,getPropertyOf和 _ proto _ 是三个用来访问prototype的方法.它们的 ...
- Python3 模拟登录知乎(requests)
# -*- coding: utf-8 -*- """ 知乎登录分为两种登录 一是手机登录 API : https://www.zhihu.com/login/phone ...