`

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

 
阅读更多

[ 需求分析 ]

在我们实际开发中常常会遇到这样的问题:记录一个类的方法运行时间,以分析性能。一般我们的做法是先在类的开始记录一个开始时间,然后在类的结束记录一个结束时间,二者相减就可以获取我们想要的结果。但是很多时候这些类已经打了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:实现对被代理对象的处理。


[ 应用场景 ]

① 系统日志记录

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

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


[ 特别感谢 ]

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

相关推荐

    包含静态代理和动态代理demo代码

    以下是一个简单的静态代理模式的步骤: 1. 定义一个接口,如`Service`,其中声明了需要被代理的方法。 2. 创建真实类(Target)实现`Service`接口,实现具体业务逻辑。 3. 创建代理类(Proxy)同样实现`Service`...

    静态代理与动态代理Demo

    静态代理是程序员在编译时就已经明确知道了代理类和真实被代理类的关系,代理类通常会继承或实现被代理类的接口,这样就可以直接调用被代理类的方法。在实际应用中,静态代理常用于控制访问权限、添加日志、性能监控...

    Java静态代理与动态代理demo

    以下是一个简单的静态代理实现示例: ```java // 目标接口 interface Service { void doSomething(); } // 目标实现类 class RealService implements Service { @Override public void doSomething() { System...

    java静态代理、动态代理、装饰设计模式

    静态代理是最基础的形式,它需要程序员手动创建一个代理类,该类实现了与目标类相同的接口。代理类持有目标类的引用,并在调用目标类方法之前或之后添加额外的逻辑。这种方式的优点是代码清晰,易于理解;缺点是如果...

    动态代理和静态代理demo

    代理模式通常分为静态代理和动态代理两种实现方式,这里我们将详细探讨这两种代理模式,并通过一个简单的Java示例进行演示。 ### 静态代理 静态代理是最基础的代理形式,它的实现主要依靠继承或接口实现。在静态...

    代理模式的各种实现 (动态代理、静态代理)

    代理模式在Java中主要有两种实现方式:静态代理和动态代理,其中动态代理又分为JDK动态代理和CGlib代理。 1. 静态代理 静态代理是程序员手动创建代理类并实现与目标类相同接口的方式。在静态代理中,代理类和目标类...

    静态代理和动态代理

    静态代理是最基础的代理实现方式,它需要程序员手动创建一个代理类,这个代理类通常会继承或实现目标类的接口。在代理类中,程序员会添加额外的逻辑,如日志记录、事务管理等。以下是一个简单的静态代理示例: ```...

    静态代理、jdk动态代理、cglib动态代理

    静态代理的实现方式是:在编译时,创建一个代理类,该代理类继承自被代理的对象,并重写其中的一些方法,以便控制对被代理对象的访问。 静态代理的优点是:它的实现方式简单易懂,易于维护和扩展。但是,它的缺点是...

    Java 代理 代理模式 静态代理与动态代理 常见的动态代理实现 .md

    为了更好地理解代理模式的工作原理,我们通过下面的例子来实现一个简单的代理模式。 **定义接口**: ```java public interface SellPerfume { void sellPerfume(double price); } ``` **定义真实对象**: ```java...

    JAVA JDK静态代理、动态代理、CGlib代理的代码演示

    Java提供了两种主要的代理实现方式:JDK静态代理和动态代理,另外还有第三方库如CGlib提供的代理实现。下面我们将详细探讨这些代理技术,并通过代码演示来理解它们的工作原理。 ### 1. JDK静态代理 静态代理是我们...

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

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

    PHP大牛线上培训班课-静态代理请求对象静态代理.zip

    静态代理是一种代理模式,其主要目的是在不修改原始对象(被代理对象)的基础上,通过一个中间代理类来控制对原始对象的访问。代理类通常会增加额外的功能或控制,如日志记录、权限检查、缓存等,而这些功能对原始...

    静态代理demo

    静态代理的核心思想是通过创建一个代理类,该类实现与原始目标类相同的接口或继承相同的目标类,从而在代理类中添加额外的功能。代理类通常会持有目标对象的引用,并将调用转发到目标对象上,同时可以在调用前后添加...

    代理模式-静态动态代理-jdk动态代理-cglib动态代理

    代理模式是一种设计模式,它允许我们为一个对象创建一个代理对象,这个代理对象可以在调用原始对象的方法之前或之后执行额外的操作。在Java中,我们可以使用两种方式实现动态代理:JDK动态代理和CGLIB动态代理。 1....

    Java动态代理详解; CGLib实现动态代理1

    在静态代理中,我们通常会定义一个抽象主题接口(ISubject),然后创建真实主题类(RealSubject)来实现这个接口,接着创建代理类(Proxy)也实现该接口,并持有真实主题类的引用。代理类在调用真实对象方法时可以...

    静态代理示例

    在编程领域,代理模式是一种设计模式,它允许我们创建一个代理类来代表另一个对象,以增强或控制原对象的功能。静态代理是代理模式的一种实现方式,尤其在Java中较为常见。下面将详细介绍静态代理及其应用。 ### ...

    关于jdk动态代理的源码剖析

    1. **代理类继承自`Proxy`类**:每一个动态代理类都是`Proxy`类的子类,这意味着它们继承了`Proxy`类中的所有属性和方法。 2. **代理类实现了目标接口**:动态代理类会实现用户指定的目标接口,这样就可以像操作原始...

    一篇讲动态代理的好文 <动态代理的前世今生>

    它不同于静态代理(例如Java中的接口实现或C++中的多重继承),后者需要在编译时确定。动态代理提供了一种更加灵活的方式来实现代理模式,尤其适合于那些需要在运行时动态配置或扩展功能的系统。 #### 三、动态代理...

    11 动态代理的例子

    静态代理是通过显式地创建代理类来实现,而动态代理则是在运行时动态生成代理对象。 动态代理的核心在于`java.lang.reflect.Proxy`类和`java.lang.reflect.InvocationHandler`接口。Proxy类提供了创建动态代理对象...

    Jdk动态代理和cglib动态代理原理

    CGLIB(Code Generation Library)是一个强大的高性能的代码生成库,主要用于Java字节码操作和动态代理。与JDK动态代理不同,CGLIB不依赖于接口,而是通过继承目标类来创建代理对象。 1. **CGLIB核心组件**:主要...

Global site tag (gtag.js) - Google Analytics