`

继承?静态代理?写一个自己的动态代理吧

 
阅读更多

[ 需求分析 ]

在我们实际开发中常常会遇到这样的问题:记录一个类的方法运行时间,以分析性能。一般我们的做法是先在类的开始记录一个开始时间,然后在类的结束记录一个结束时间,二者相减就可以获取我们想要的结果。但是很多时候这些类已经打了jar包,我们无法直接修改源码,这个时候我们应该怎么办呢?

下文使用Tank的移动需要统计时间、记录日志来模拟需求场景,假定Moveable、Tank类无法修改。


interface:Moveable

public interface Moveable {
	public void move();
}


realization:Tank

public class Tank implements Moveable{
	public void move() {
		System.out.println("tank is moving");
	}
}

[ 继承实现 ]

① 现在我们假设需要统计Tank移动的时间,通过实现Tank,并加入统计时间的代码即可做到

import java.util.Random;
/**
 * use to record tank move time
 * @author zhangjim
 */
public class TankTimeProxy extends Tank {
	public void move() {
		try {
			long start = System.currentTimeMillis();
			super.move();
			// make the tank move solwly
			Thread.sleep(new Random().nextInt(1000)); 
			long end = System.currentTimeMillis();
			System.out.println("total spend time: " + (end - start) + "ms");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
② 测试代码:

/**
 * test client
 * @author zhangjim
 */
public class Client {
	public static void main(String[] args) {
		Moveable m = new TankTimeProxy();
		m.move();
	}
}
③ 分析:

上述方法可以解决我们的问题,但如果现在需要增加需求:加入日志记录功能,那么我们就要再继承TankTimeProxy

/**
 * use to record tank move logs
 * @author zhangjim
 */
public class TankLogProxy extends TankTimeProxy{
	public void move() {
		System.out.println("tank start to move...");
		super.move();
		System.out.println("tank stop to move...");
	}
}

这种实现方法存在一个很大的缺陷:很不灵活,如果后期还要加功能的话,就要不断的继承下去,父子关系过于臃肿,不利于维护。

[ 静态代理 ]

① 接上文,现在我们使用静态代理实现上面的功能:

重写TankTimeProxy:

import java.util.Random;

/**
 * use to record tank move time
 * @author zhangjim
 */
public class TankTimeProxy implements Moveable {
	private Moveable m;

	public TankTimeProxy(Moveable m) {
		this.m = m;
	}

	@Override
	public void move() {
		try {
			long start = System.currentTimeMillis();
			m.move();
			Thread.sleep(new Random().nextInt(1000));
			long end = System.currentTimeMillis();
			System.out.println("total spend time: " + (end - start) + "ms");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}


重写TankLogProxy:
/**
 * use to record tank move logs
 * @author zhangjim
 */
public class TankLogProxy implements Moveable{
	private Moveable m;

	public TankLogProxy(Moveable m) {
		this.m = m;
	}

	@Override
	public void move() {
		System.out.println("tank start to move...");
		m.move();
		System.out.println("tank stop to move...");
	}
}

② 测试一下吧

/**
 * test client
 * @author zhangjim
 */
public class Client {
	public static void main(String[] args) {
		Moveable m = new TankLogProxy(new TankTimeProxy(new Tank()));
		m.move();
	}
}

③ 分析:

通过代码不难看出,代理类和被代理类实现了同一个接口,其实这个就是装饰设计模式。相对于继承,聚合的类结构无疑更方便管理和维护,但是它仍然有一个弊病:对于不同的功能,我仍然需要加类。例如:加权限控制功能需要TankAuthorityProxy,加事务控制功能需要TankTransactionProxy等等,能不能让计算机帮我们产生这些类?自己动手试试吧!


[ 我的动态代理 ]

① 所谓的动态代理,就是说上文的TankTimeProxyTankLogProxy不需要我们手动创建了计算机会帮我们动态生成,也就是说这个代理类不是提前写好的,而是程序运行时动态生成的。


我们写一个proxy类,它的作用就是帮我们产生代理类。

主要实现思路:

i 将所有方法代码拼接成字符串

ii 将生成代理类的代码拼接成字符串(包含所有方法拼接成的字符串)

iii 将此字符串写入文件中、并使用JavaComplier对它进行编译

Ⅳ将编译好的文件load进内存供我们使用,并返回代理实例。

public class Proxy {
	public static Object newProxyInstance(Class intefc, InvocationHandler handle) throws Exception {
		String rt = "\r\t" ;
		String methodStr = "" ;
		
		// first we should realize all the methods of the interface
		Method[] methods = intefc.getMethods();
		for (Method m : methods) {
			methodStr +="public void "+m.getName()+"(){"+rt+
						"    try{"+rt+
						"        Method method = "+intefc.getName()+".class.getMethod(\""+m.getName()+"\");" + rt +
						"        handle.invoke(this,method);" +rt+
						"    }catch(Exception ex){}" +rt+
						"}" ;
						
		}
		String clazzStr =  "package com.zdp.dynamicProxy;"+rt+
						   "import java.lang.reflect.Method;"+rt+
						   "public class $Proxy1 implements "+intefc.getName()+"{"+rt+
						   "    private com.zdp.dynamicProxy.InvocationHandler handle ;"+rt+
						   "    public $Proxy1(InvocationHandler handle){"+rt+
						   "        this.handle=handle;"+rt+
						   "    }"+rt+
						   "    @Override"+rt+
						    methodStr +rt+
						   "}";
		
		
		// write to a java file 
		File file = new File("D:/develop_environment/babasport/homework/src/com/zdp/dynamicProxy/$Proxy1.java") ;
		FileWriter writer = null ;
		try {
			writer = new FileWriter(file);
			writer.write(clazzStr) ;
			writer.flush() ;
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try {
				if(writer !=null){
					writer.close() ;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		//load the java file, and then create an instance 
		URL[] urls = new URL[] {new URL("file:/" + "D:/develop_environment/babasport/homework/src/")};
		URLClassLoader urlLoader = new URLClassLoader(urls);
		Class c = urlLoader.loadClass("com.zdp.dynamicProxy.$Proxy1");
		
		//return the proxy instance
		Constructor ctr = c.getConstructor(InvocationHandler.class);
		Object proxyInstance = ctr.newInstance(handle);

		return proxyInstance;
	}
}
② 因为要处理所有的业务,我们定义一个接口InvocationHandler

public interface InvocationHandler {
	public void invoke(Object o, Method m) ;  
}

③ MyHandler是真正的处理类

/**
 * use to record logs and time
 * @author zhangjim
 */
public class MyHandler implements com.zdp.dynamicProxy.InvocationHandler {
	private Object target;

	public MyHandler(Object target) {
		this.target = target;
	}

	public void invoke(Object obj, Method method) {
		try {
			System.out.println("tank start to move...");
			long start = System.currentTimeMillis();
			method.invoke(target);
			Thread.sleep(new Random().nextInt(1000));
			long end = System.currentTimeMillis();
			System.out.println("total spend time: " + (end - start)  + "ms");
			System.out.println("tank stop to move...");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
④ Proxy类会帮我们动态创建一个类$Proxy1

public class $Proxy1 implements com.zdp.dynamicProxy.Moveable {
	private com.zdp.dynamicProxy.InvocationHandler handle;

	public $Proxy1(InvocationHandler handle) {
		this.handle = handle;
	}

	@Override
	public void move() {
		try {
			Method method = com.zdp.dynamicProxy.Moveable.class.getMethod("move");
			handle.invoke(this, method);
		} catch (Exception ex) {
		}
	}
}

⑤ 测试一下,看看效果怎么样

/**
 * test client
 * @author zhangjim
 */
public class Client {
	public static void main(String[] args) throws Exception {
		Moveable m = new Tank();
		InvocationHandler handle = new MyHandler(m);
		Moveable proxy = (Moveable) Proxy.newProxyInstance(Moveable.class, handle);
		proxy.move();
	}
}

⑥ 简要总结:

Proxy:动态创建代理类,通过调用处理器的处理方法来实现代理。

InvocationHandler:实现对被代理对象的处理。


[ 应用场景 ]

① 系统日志记录

② 权限控制(符合一定条件才执行某方法)

③ 事务控制(方法执行之前开启事务,方法执行之后提交或回滚事务)


[ 特别感谢 ]

这篇日志是对马士兵老师:《设计模式之动态代理》的一个总结,非常感谢马老师的辛勤工作。
分享到:
评论

相关推荐

    java设计模式【之】静态代理【源码】【场景:帮爸爸买菜】.rar

    * 在开发者的角度来看,创建一个代理对象,提供给用户使用,避免用户直接访问真正的对象 * 在用户角度来看,就是普通的类方法调用 * * 作用 * 1.保护被代理对象 * 2.增强被代理对象 * 3.完全替换被代理对象 ...

    安卓java读取网页源码-AndroidLearningNotes:第一次提交

    安卓java读取网页源码 AndroidLearningNotes Java Java基础面试知识 int与integer的区别 探探对java多态的理解 String、StringBuffer、StringBuilder区别 ...静态代理和动态代理的区别,什么场景使用

    超级有影响力霸气的Java面试题大全文档

    例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。 当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望...

    java 面试题 总结

    例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。 当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望...

    java面试题

    例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。 当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望...

    Java面试题.docx

    12、静态代理和动态代理的区别,什么场景使用? 14、Java中实现多态的机制是什么? 16、说说你对Java反射的理解 17、说说你对Java注解的理解 18、Java中String的了解 19、String为什么要设计成不可变的? 20、...

    C# IL中间代码注入实现切面编程

    之前分享的那篇“面向切面编程–渲染...那篇分享中也提到使用这中方式不适用于静态方法,而且代理类需继承一个接口或 者MarshalByRefObject类,为此这里寻找到了另一种更直接的方法进行动态代理,来解决这两个弊端。

    demo:java生产项目常用的demo

    文档比较长,有兴趣查看的可以安装一个: 来展示目录 一.代理模式 1.静态代理 继承方式实现 聚合方式实现 2.动态代理 使用jdk proxy代理接口方式实现 使用Cglib代理类方式实现 自己实现的动态代理 模仿jdk proxy自己...

    Java 基础面试题

    17. 动态代理和静态代理 18. 封装、继承、多态 19. static加载顺序 20. 代理和反射(3分钟),反射泛型还有用吗 21. final关键字 22. 线程wait和sleep相同点和不同点 23. 为什么start调用run方法,调用run不会...

    二十三种设计模式【PDF版】

    将牛郎织女分开(本应在一起,分开他们,形成两个接口),在他们之间搭建一个桥(动态的结合) 设计模式之 Flyweight(共享元) 提供 Java运行性能,降低小而大量重复的类的开销. C. 行为模式 设计模式之 Command(命令) ...

    impromptu-interface:静态接口到动态实现(鸭子铸造)。 将DLR与Reflect.Emit结合使用

    net4.0 / netstd2.0框架允许您使用静态接口包装任何对象(静态或动态),即使它不是继承自该对象。 它通过在代理内部发出缓存的动态绑定代码来做到这一点。 ImpromptuInterface可用Nuget 您可以在MyGet上找到最新...

    java-servlet-api.doc

    然而,一个映射可能是由一个URL和许多Servlet实例组成,例如:一个分布式的Servlet引擎可能运行在不止一个的服务器中,这样的话,每一个服务器中都可能有一个Servlet实例,以平衡进程的载入。作为一个Servlet的...

    AZAlbum:是一个相册模块,可以用到ios系统中快速继承,使用相册选择功能

    AZAlbum 是一个相册模块,可以用到ios系统中快速继承,使用相册选择功能。 如何加入到你的项目中? 两种方式 使用静态文件库 AZAlbumSDK 使用源码 AZAlbum (建议使用源码,方便自由扩展) 1: 导入AZAlbum 或者静态库...

    1579068285.png

    简单创建代理模式,快速使用。能够让你快速了解代理的好处很使用。可看到 1、代理类继承了Proxy类并且...3、有一个静态代码块,通过反射或者代理类的所有方法 4、通过invoke执行代理类中的目标方法doSomething

    spring.doc

    4.1.3 Spring的动态代理 71 4.2 AOP编程 71 4.2.1概念: 71 SpringAOP概念拓展: 73 之前实现了目标方法的动态调用,现在来实现切面的动态调用。 74 4.2.2 AOP实现的两种模式 78 4.2.2.1 xml形式 78 XML形式拓展: ...

    LuaBind 源码 (Lua增强库)

    你正在使用一个UNIX系统(或者 cygwin),他们将使得构建LuaBind静态库变得很简单.如果 你正在使用 Visual Studio ,很简单的包含 src 目录下的文件到你的工程即可. 构建LuaBind的时候,你可以设定一些选项来使得库更加...

    Java 基础核心总结 +经典算法大全.rar

    静态代理与动态代理常见的动态代理实现JDK Proxy CGLIB JDK Proxy 和 CGLIB 的对比动态代理的实际应用 Spring AOP 变量 变量汇总实例变量 实例变量的特点全局变量 静态变量 静态变量的特点类变量 局部变量

    基于maven项目的SSM框架与layu前端框架的代码

    [简单点解释],比方说你想在你的biz层所有类中都加上一个打印‘你好,AOP’的功能这你经可以用aop思想来做,你先写个类写个方法,方法经实现打印‘你好,AOP’让后你Ioc这个类 ref=“biz.*”让每个类都注入。...

    asp.net知识库

    .NET关于string转换的一个小Bug Regular Expressions 完整的在.net后台执行javascript脚本集合 ASP.NET 中的正则表达式 常用的匹配正则表达式和实例 经典正则表达式 delegate vs. event 我是谁?[C#] 表达式计算引擎...

Global site tag (gtag.js) - Google Analytics