设计模式-动态代理 实现机制

一.首先使用API中的Proxy来实现动态代理的例子

(1)定义一个接口Moveable, 里面就有一个move方法

package com.feng.proxy;

/**
 * 定义的接口,实现对这个接口的代理
 * 简单起见,只定义一个方法来演示动态代理的效果
 * @author Administrator
 *
 */
public interface Moveable {
	
	void move();

}
(2)为Moveable接口定义实现类Car

package com.feng.proxy;
/**
 * 为Moveable定义一个实现类
 * 实现move方法
 * @author Administrator
 *
 */
public class Car implements Moveable{

	@Override
	public void move() {
		System.out.println("I am running");
	}

	
}
(3)定义一个TimeInvocationHandler,将要添加的逻辑放在这个类中

package com.feng.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 定义一个InvocationHandler的实现类,定义要添加的操作
 * @author Administrator
 *
 */
public class TimeInvocationHandler implements InvocationHandler{

	//定义一个被代理对象
	private Object target;
	
	//定义一个set方法
	public void setTarget(Object target) {
		this.target = target;
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		//原操作前添加的操作
		System.out.println("begin logs...");
		
		//原操作,就是target。method();
		Object result = method.invoke(target, args);
		
		//原操作后添加的操作
		System.out.println("end logs...");
		return result;
	}


}
(4)定义一个代理类,负责返回一个代理对象

package com.feng.proxy;

import java.lang.reflect.Proxy;

/**
 * 时间代理类
 * 获取一个代理类对象
 * @author Administrator
 *
 */
public class TimeProxy {

	public static Object getProxyInstance(Object target)
	{
		TimeInvocationHandler h = new TimeInvocationHandler();
		h.setTarget(target);
		
		//newProxyInstance 是Proxy的静态方法,三个参数为类加载器,被代理的接口数组,InvacationHandler
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), h);
	}
}
(5)编写一个测试类

package com.feng.proxy;

public class Client {

	/**
	 * 测试类
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		Moveable m = (Moveable) TimeProxy.getProxyInstance(new Car());
		m.move();
	}

}

运行上面的程序,结果如下:我们已经成功的在move()方法前后加上了我们想添加的操作


通过使用API中提供的代理机制演示了动态代理,接下来我们自己写一个代理类

二.简单代理,实现代理的两种形式继承与聚合

还是以上面的例子为例,有一个moveable接口,有一个实现类car,我现在要在car的move()方法前加一些操作,在后加一些操作,应该如何实现

(1)通过继承实现代理
定义一个TimerProxyByExtends  

package com.feng.proxy;

/**
 * 通过继承car,就可以在car的move()方法前后就一些操作了
 * @author Administrator
 *
 */
public class TimeProxyByExtends extends Car{

	@Override
	public void move() {
		//加在前面的操作
		System.out.println("前面的操作");
		
		super.move();
		
		//加在后面的操作
		System.out.println("后面的操作");
	}

	
}
编写一个测试类
package com.feng.myproxy;

public class Client {

	/**
	 * 测试类
	 * @param args
	 */
	public static void main(String[] args) {
		
		Moveable m = new TimeProxyByExtends();
		m.move();
	}

}

执行结果为:



(2)通过聚合实现代理

定义一个代理类

package com.feng.myproxy;

public class TimeProxyByOneOf implements Moveable{

	private Moveable m;

	public TimeProxyByOneOf(Moveable m) {
		super();
		this.m = m;
	}

	@Override
	public void move() {
		// TODO Auto-generated method stub
		System.out.println("通过聚合实现代理前操作");
		m.move();
		System.out.println("通过聚合实现代理后操作");
	}
		
}
定义一个测试类
package com.feng.myproxy;

public class Client {

	/**
	 * 测试类
	 * @param args
	 */
	public static void main(String[] args) {
		
		TimeProxyByOneOf m = new TimeProxyByOneOf(new Car());
		m.move();
	}

}
测试结果为:



现在比较一下这两种方式的区别:

假设现在有一种场景,我不仅要对Car实现时间的代理还要实现记录日志的代理还要实现权限的代理,在这我就用a, b ,c来表示这三种代理

如果使用继承的方式实现,我要有一个car1实现a, car2实现b,car3实现c,不仅如此我还要对代理进行叠加,我想实现ab(先实现时间代理再实现日志代理)接着要定义car4去继承car1,我又想实现ac,又要定义一个car5,总之有多少种组合我就要定义多少了代理类,这样很容易引起类爆炸。因为在实现代理的时候我们通常不适用继承来实现。

如果使用聚合的方式来实现,我们可以定义car1实现a, 定义car2实现b, 定义car3实现c,接着如果实现组合的情况,比如实现ab,我只需要将car1的对象作为car2的被代理对象即可,不需要添加新的类,下面来模拟一个多种代理一起使用的情况

在上面程序的继承上我再定义一个日记代理和权限代理,实现如下:

package com.feng.myproxy;

public class LogsProxy implements Moveable{

	private Moveable m;
		
	public LogsProxy(Moveable m) {
		super();
		this.m = m;
	}

	@Override
	public void move() {
		System.out.println("开始记录日志");
		m.move();
		System.out.println("结束记录日志");		
	}

}

package com.feng.myproxy;

public class AuthorizedProxy implements Moveable{

	private Moveable m;
	
	public AuthorizedProxy(Moveable m) {
		super();
		this.m = m;
	}

	@Override
	public void move() {
		System.out.println("开始检查权限");
		m.move();		
	}
}

测试类:如果想要换组合顺序的话,只要改一下代码中的顺序即可,或者写顺序写到配置文件中,从配置文件中读取

package com.feng.myproxy;

public class Client {

	/**
	 * 测试类
	 * @param args
	 */
	public static void main(String[] args) {
		
		TimeProxyByOneOf m = new TimeProxyByOneOf(new Car());
		LogsProxy l = new LogsProxy(m);
		AuthorizedProxy a = new AuthorizedProxy(l);
		a.move();
	}

}

执行结果如下:


这只是简单的代理,存在一些不灵活的地方,比如这个时间代理现在只能代理实现了Moveable接口的类,如果有其他接口的话,还要再写一个另一个接口的代理类,如果有一百个接口那么就要写上一百个接口的时间代理类,显然这很不灵活,能不能写一个代理类能适应于所以的类的,这就要用到动态代理了

三. 动态代理

使用上述程序中的Moveable,car类

首先定义一个自己的Proxy,里面有一个静态的newProxyInstance方法,在该方法中实现动态的编译,加载,实例化的过程,并返回类的对象

(1)现在类中定义一个字符串,字符串里面放着我们之前写的代理类代码,对这个字符串操作,先将字符串写到一个java文件中,然后进行编译,加载,实例化

代码如下:

package com.feng.dynamicproxy;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

public class Proxy {

	public static Object newProxyInstance()
	{
		//1,定义一个类的字符串, 将时间代理类的代码原封不动的写到字符串中
		String rt = "\r\n";
		String str = "package com.feng.dynamicproxy;"+rt+

		"public class TimeProxy implements Moveable{"+rt+

		"	private Moveable m;"+rt+

		"	public TimeProxy(Moveable m) {"+rt+
		"		super();"+rt+
		"		this.m = m;"+rt+
		"	}"+rt+

		"	@Override"+rt+
		"	public void move() {"+rt+
		"		System.out.println(\"通过聚合实现代理前操作\");"+rt+
		"		m.move();"+rt+
		"		System.out.println(\"通过聚合实现代理后操作\");"+rt+
		"	}"+rt+
				
		"}";

		//2. 将字符串输出到一个java文件中,放到哪里都可以,我放到的是当前目录下的com.feng.dynamicproxy包下
		String fileName = System.getProperty("user.dir")+"/src/com/feng/dynamicproxy/TimeProxy.java";
		File file = new File(fileName);
		FileWriter fw;
		try {
			fw = new FileWriter(file);
			fw.write(str);
			fw.flush();
			fw.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		//3.对声场的TimeProxy。java进行编译,这里使用API自带的JavaCompiler进行编译
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager fileMsg = compiler.getStandardFileManager(null, null, null);
		Iterable untis = fileMsg.getJavaFileObjects(fileName);
		CompilationTask task = compiler.getTask(null, fileMsg, null, null, null, untis);
		task.call();
		
		//4.使用加载器将编译好的二进制文件加载到内存中
		Class c = null;
		try {
			URLClassLoader ul = new URLClassLoader(new URL[]{ new URL("file:/"+System.getProperty("user.dir")+"/src")});
			c = ul.loadClass("com.feng.dynamicproxy.TimeProxy");
			
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//5.出师话对象
		Object m = null;
		try {
			Constructor construct = c.getConstructor(Moveable.class);
			m = construct.newInstance(new Car());
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return m;
	}
}

编写测试类:

package com.feng.dynamicproxy;

public class Client {

	/**
	 * 测试类
	 * @param args
	 */
	public static void main(String[] args) {
		
		Moveable m = (Moveable) Proxy.newProxyInstance();
		m.move();
	}

	
}

执行结果如下图:


现在在我们的项目中是没有TimeProxy.java这个文件的,这个文件时由代码自动产生,编译,加载的。但是现在的代码还不是很灵活,因为现在代理类在Proxy中是写死的,还是只能操作Moveable接口的类,如何才能把这个接口改为适应于所有接口呢,那就是把接口当做参数传递进去

(2)把接口当做参数传递到Proxy中,这样就可以操作所有的类了,正常来说一个类可能会实现多个接口,但是这里方便起见我就模拟一个接口的情况,而且不做权限和返回值的限制了,只需要了解原理即可。

对上面的Proxy类进行改造:将有接口的地方都替换成参数的类型,获取接口中的方法,动态的构造方法

package com.feng.dynamicproxy;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

public class Proxy {

	public static Object newProxyInstance(Class interf)
	{
		String rt = "\r\n";
		//动态获取接口中的方法
		String methodStr = "";
		Method[] methods = interf.getMethods();
		for(Method m: methods)
		{
			methodStr+="	@Override"+rt+
					"	public void "+m.getName()+"() {"+rt+
					"		System.out.println(\"通过聚合实现代理前操作\");"+rt+
					"		m.move();"+rt+
					"		System.out.println(\"通过聚合实现代理后操作\");"+rt+
					"	}";
		}
		//1,定义一个类的字符串, 将时间代理类的代码原封不动的写到字符串中	
		String str = "package com.feng.dynamicproxy;"+rt+

		"public class TimeProxy implements "+interf.getName()+"{"+rt+

		"	private "+interf.getName() +" m;"+rt+

		"	public TimeProxy("+interf.getName() +" m) {"+rt+
		"		super();"+rt+
		"		this.m = m;"+rt+
		"	}"+rt+

		methodStr+rt+
				
		"}";

		//2. 将字符串输出到一个java文件中,放到哪里都可以,我放到的是当前目录下的com.feng.dynamicproxy包下
		String fileName = System.getProperty("user.dir")+"/src/com/feng/dynamicproxy/TimeProxy.java";
		File file = new File(fileName);
		FileWriter fw;
		try {
			fw = new FileWriter(file);
			fw.write(str);
			fw.flush();
			fw.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		//3.对声场的TimeProxy。java进行编译,这里使用API自带的JavaCompiler进行编译
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager fileMsg = compiler.getStandardFileManager(null, null, null);
		Iterable untis = fileMsg.getJavaFileObjects(fileName);
		CompilationTask task = compiler.getTask(null, fileMsg, null, null, null, untis);
		task.call();
		
		//4.使用加载器将编译好的二进制文件加载到内存中
		Class c = null;
		try {
			URLClassLoader ul = new URLClassLoader(new URL[]{ new URL("file:/"+System.getProperty("user.dir")+"/src")});
			c = ul.loadClass("com.feng.dynamicproxy.TimeProxy");
			
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//5.出师话对象
		Object m = null;
		try {
			Constructor construct = c.getConstructor(interf);
			m = construct.newInstance(new Car());
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return m;
	}
}
编写一个测试类:

package com.feng.dynamicproxy;

public class Client {

	/**
	 * 测试类
	 * @param args
	 */
	public static void main(String[] args) {
		
		Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class);
		m.move();
	}

	
}
结果如图:



上面的程序还是有问题,我们现在能够动态的生成传递进来的接口的方法了,但是方法体里面的语句还是在代码中写死的,这显然不灵活,如果方法体里面要加什么操作能够让用户来指定那就完美了,我们再次进行改造

(3)再添加一个参数InvocationHandler 里面有一个方法invoke,invoke在具体实现了InvocationHandler接口的类中实现。

首先定义自己的InvocationHandler接口

package com.feng.dynamicproxy;

import java.lang.reflect.Method;

public interface InvocationHandler {

	//真正实现中是有返回值的,并且参数中有方法的参数数组,这里为了简单模拟,就简化操作了
	public void invoke(Object o, Method m);
}
接着定义TimerInvocationhandler类实现InvocationHandler接口

package com.feng.dynamicproxy;

import java.lang.reflect.Method;

public class TimerInvocationHandler implements InvocationHandler {

	private Object target;
	
	
	public TimerInvocationHandler(Object target) {
		super();
		this.target = target;
	}


	@Override
	public void invoke(Object o, Method m) {
		// TODO Auto-generated method stub
		System.out.println("实现自定义方法体的操作前");
		try {
			m.invoke(target, new Class[]{});
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("实现自定义方法体的操作前");
	}

}

然后修改Proxy,将方法体的操作提供给用户自定义去编写,在动态生成代码的时候让代码调用InvocationHandler里面的invoke方法,同时传递自己,方法,一遍在InvocationHandler的实现类中调用此方法,同时就可以在此方法的前后加一些操作了。这里动态生成的代码中也不再是传一个具体的接口了,而是应该传一个InvocationHandler类型的参数,只要是调用方法,就会回调InvocationHandler方法中的invoke方法,这里的逻辑有点复杂,多看几遍就能看懂了

package com.feng.dynamicproxy;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import com.feng.dynamicproxy.InvocationHandler;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

public class Proxy {

	public static Object newProxyInstance(Class interf, InvocationHandler h)
	{
		String rt = "\r\n";
		//动态获取接口中的方法
		String methodStr = "";
		Method[] methods = interf.getMethods();
		for(Method m: methods)
		{
			methodStr+="	@Override"+rt+
					"	public void "+m.getName()+"() {"+rt+
					"	try{"+rt+
					"	Method method =	"+ interf.getName()+".class.getMethod(\""+m.getName()+"\");"+rt+
					"	h.invoke(this, method);"+rt+
					"	}catch(Exception e){}"+rt+
					"	}";
		}
		
		
		//1,定义一个类的字符串, 将时间代理类的代码原封不动的写到字符串中	
		String str = "package com.feng.dynamicproxy;"+rt+
		"import java.lang.reflect.Method;"+rt+
		"public class TimeProxy implements "+interf.getName()+"{"+rt+

		"	private "+h.getClass().getName() +" h;"+rt+

		"	public TimeProxy("+h.getClass().getName() +" h) {"+rt+
		"		super();"+rt+
		"		this.h = h;"+rt+
		"	}"+rt+

		methodStr+rt+
				
		"}";

		//2. 将字符串输出到一个java文件中,放到哪里都可以,我放到的是当前目录下的com.feng.dynamicproxy包下
		String fileName = System.getProperty("user.dir")+"/src/com/feng/dynamicproxy/TimeProxy.java";
		File file = new File(fileName);
		FileWriter fw;
		try {
			fw = new FileWriter(file);
			fw.write(str);
			fw.flush();
			fw.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		//3.对声场的TimeProxy。java进行编译,这里使用API自带的JavaCompiler进行编译
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager fileMsg = compiler.getStandardFileManager(null, null, null);
		Iterable untis = fileMsg.getJavaFileObjects(fileName);
		CompilationTask task = compiler.getTask(null, fileMsg, null, null, null, untis);
		task.call();
		
		//4.使用加载器将编译好的二进制文件加载到内存中
		Class c = null;
		try {
			URLClassLoader ul = new URLClassLoader(new URL[]{ new URL("file:/"+System.getProperty("user.dir")+"/src")});
			c = ul.loadClass("com.feng.dynamicproxy.TimeProxy");
			
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//5.出师话对象
		Object m = null;
		try {
			Constructor construct = c.getConstructor(h.getClass());
			m = construct.newInstance(h);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return m;
	}
}

编写测试类:

package com.feng.dynamicproxy;

public class Client {

	/**
	 * 测试类
	 * @param args
	 */
	public static void main(String[] args) {
		
		InvocationHandler h = new TimerInvocationHandler(new Car());
		Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class, h);
		m.move();
	}

	
}
结果如下图所示:

到此我们动态生成的代码中已经没有固定死的代码的,我们定义的代理也不再单单是为某一类对象的代理了。在真正的动态代理模式中会考虑更多的细节,但大致思想就是这个样子的。





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值