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

[ 需求分析 ]

在我们实际开发中常常会遇到这样的问题:记录一个类的方法运行时间,以分析性能。一般我们的做法是先在类的开始记录一个开始时间,然后在类的结束记录一个结束时间,二者相减就可以获取我们想要的结果。但是很多时候这些类已经打了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等等,能不能让计算机帮我们产生这些类?自己动手试试吧!

[ 我的动态代理 ]

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

我们写一个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:实现对被代理对象的处理。

[ 应用场景 ]

① 系统日志记录

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

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

[ 特别感谢 ]

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

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

时间: 2024-08-10 17:02:12

继承?静态代理?写一个自己的动态代理吧的相关文章

性能优于JDK代理,CGLib如何实现动态代理

按照代理的创建时期,代理类可以分为两种. 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译.在程序运行前,代理类的.class文件就已经存在了. 动态代理:在程序运行时,运用反射机制动态创建而成. 动态代理三种方式 动态代理实现有三种方式,jdk动态代理(基于接口),cglib动态代理(基于继承),javassist(hibernate中使用这种方式)实现动态代理. JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢? 这就需要CGLib了. cgl

设计模式之动态代理(Java的JDK动态代理实现)

对于JDK的动态代理,孔浩老师说学习的方法是把它记下来. 先写一个主题接口类,表示要完成的一个主题. package com.liwei.dynaproxy; /** * 要代理的主题接口 * @author Administrator * */ public interface Subject { public void miai(); } 再写一个实现类,这个实现类实现这个接口.当然,这个实现类就是我们要代理的对象. 为了区别不同的类的对象,我们为Person类增加了一个name属性,并且通

关于java的一个典型的动态代理

今天看书的一个过程中,看到一个动态代理看下代码 import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class DynamicProxy { public static void testDynamicProxy(){ Calculator calculator = new CalculatorImpl(); LogH

一个简单 JDK 动态代理的实例

动态代理的步骤: 创建一个实现了 InvocationHandler 接口的类,必须重写接口里的 invoke()方法. 创建被代理的类和接口 通过 Proxy 的静态方法 newProxyInsatance(ClassLoader loader,Class[] interfaces,InvocationHandler,handler) 来创建一个代理 通过代理来调用方法 简单的动态代理实例 1 package com.sean.zzzjvm; 2 3 import java.lang.refl

写一个scrapy中间件--ip代理池

# -*- coding: utf-8 -*- # Define here the models for your spider middleware # # See documentation in: # https://docs.scrapy.org/en/latest/topics/spider-middleware.html import random from scrapy import signals class TutorialDownloaderMiddleware(object

java动态代理详解,并用动态代理和注解实现日志记录功能

动态代理的概念 动态代理是程序在运行过程中自动创建一个代理对象来代替被代理的对象去执行相应的操作,例如, 我们有一个已经投入运行的项目中有一个用户DAO类UserDao用来对User对象进行数据库的增删改查操作,但是有一天,要求在对用户的增删改查操作时记录相应的日志,这是怎么办呢?难道我们去直接修改UserDao的源代码,然后在UserDao的每个方法中加入日志记录功能,这显然是不合理的,它违背了java的OCP原则,即对修改关闭对扩张开放.比如改现有的代码如下: 接口类 public inte

动态代理_JDK自带的动态代理实现

动态代理是指在运行时,动态生成代理.即,代理类的字节码将在运行时生成并载入当前的ClassLoader.与静态代理相比,动态代理有很多好处.首先,不需要为真实的主题写一个形式上完全一样的封装类,假如主题接口中的方法很多,为每一个接口写一个代理方法也是非常烦人的事情,如果接口有变动,则真实主题和代理都需要修改,不利于系统的维护:其次,使用一些动态代理的生成方法甚至可以在运行时指定代理类的执行逻辑,从而大大提升系统的灵活性. 注意:动态代理使用字节码动态生成加载技术,在运行时生成并加载类. 代理模式

代理模式(Java的动态代理)

(一)是什么?what? ①:定义: ②:特征:1.0代理类(中间追求者),与委托类(追求者)有同样的接口: 2.0代理类--->主要负责为委托类预处理消息.过滤消息.把消息传递给委托类,事后处理消息等.[类似:帮别人追女朋友,打探消息]. 3.0代理类与委托类之间通常会有 关联关系 [类似哥们儿].一个代理类的对象 与 一个委托类的对象关联.代理类本身不真正实现服务,而是通过调用委托类的方法,来提供特定服务.[类似于帮追的哥们  是  根据  喜欢那女孩儿的哥们儿  的要求  来做事儿]. ③

Java动态代理(二)CGLIB动态代理应用

JDK自从1.3版本开始,就引入了动态代理,JDK的动态代理用起来非常简单,但是它有一个限制,就是使用动态代理的对象必须实现一个或多个接口 .如果想代理没有实现接口的类可以使用CGLIB包. CGLIB是一个强大的高性能的代码生成包.它被许多AOP的框架(例如Spring AOP)使用,为他们提供方法的interception(拦截).Hibernate也使用CGLIB来代理单端single-ended(多对一和一对一)关联.EasyMock通过使用模仿(moke)对象来测试java代码的包.它