[]

【设计模式】动态代理,附使用方法

Posted by lizhao on 12-23,2016

当打算在现有的方法前后加上一些逻辑(比如在前后加上日志),同时又不想去更改现有的代码,就可以使用动态代理。

个人理解动态代理的实现逻辑是:
生成一份源码(FileIO),
并且编译她(JavaCompiler/StandardJavaFileManager/Iterable/CompilationTask),
加载到内存里面(URL[],URLClassLoader.loadClass()),
获取构造函数(class(具体).getConstructor,ctr.newInstance),
调用具体的方法(这个方法使用反射机制来写invoke)

就相当于打算调用class1的method1方法,在自己生成源码的时候,就需要在calss1的method1的方法里面写:

@Override 
	public void method1()  { 
		try{
			Method md = com.lizhao.class1.class.getMethod("method1");
			**h.invoke(this,md); // InvocationHandler h;   InvocationHandler是自己写的实现方法,可以看下面**
		}catch(Exception e){


			 e.printStackTrace();


		}
	} 

具体映射的时候是调用这个方法:

package com.lizhao;

import java.lang.reflect.Method;

public class TimeInvocationHandler implements InvocationHandler {

	private Object object;
	
	public TimeInvocationHandler(Object object){
		this.object=object;
	}
	
	@Override
	public void invoke(Object proxy, Method method) throws Exception {
		System.out.println("start time: "+System.currentTimeMillis());//前逻辑
		//proxy.getClass().getMethod(name, null);
		**method.invoke(object);//这里是使用底层的反射,后面加上参数**
		System.out.println("end time: "+System.currentTimeMillis());//后逻辑
	}
}

上面是调用的一个过程,因为不全,所以肯定看的不是很明白

下面是自己实现的动态代理类:

//代理类 Proxy.java

package com.lizhao;

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.URL;
import java.net.URLClassLoader;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;
//代理类
public class Proxy {
	public Object newProxyInstance(Class infac,InvocationHandler h) throws Exception {
		String rt = "\r\n";
		Method[] methods = infac.getMethods();
		String strMethod = "";


//获取传入的类的所有方法
		for(Method m:methods){
			System.out.println(m.getName());
//			strMethod+= 
//					"	@Override " + rt 
//					+ "	public "+m.getReturnType()+" "+m.getName() + "() { " + rt
//					+ "		System.out.println(\"start time: \"+System.currentTimeMillis()); " + rt 
//					+ "		t."+m.getName() + "(); " + rt
//					+ "		System.out.println(\"end time: \"+System.currentTimeMillis()); " + rt 
//					+ "	} " + rt ;
			strMethod+= 
					"	@Override " + rt 
					+ "	public "+m.getReturnType()+" "+m.getName() + "()  { " + rt
					+ "		try{"+ rt
					+ "		Method md = "+infac.getName()+".class.getMethod(\""+m.getName() + "\");" + rt 
					+ "			h.invoke(this,md); " + rt
					+ "		}catch(Exception e){e.printStackTrace();}"+ rt
					+ "	} " + rt ;
		}
		
		String src = "package com.lizhao;" + rt 
				+ "	import java.lang.reflect.Method; " + rt 
				+ "public class TimeProxyN implements " + infac.getName()+"{ " + rt
				+ "	private InvocationHandler h ; " + rt 
				+ "	public TimeProxyN( InvocationHandler h) { " + rt 
				+ "		this.h = h; " + rt 
				+ "	} "+ rt 
				+ strMethod +rt 
				+ "} "+ rt;
		//String fileName = "file:/D:/studyDir/codeDir/temSrc/com/lizhao/com/lizhao/TimeProxy.java";// java文件存放的路径
		String fileName = "D:/studyDir/codeDir/temSrc/com/lizhao/TimeProxyN.java";// java文件存放的路径
		
		System.out.println(fileName);
		// 写入文件
		File f = new File(fileName);
		//f.createNewFile();
		FileWriter fw;
		fw = new FileWriter(f);
		fw.write(src);// 将内容写到文件里面
		fw.flush();
		fw.close();

		// 编译文件
		JavaCompiler systemJavaCompiler = ToolProvider.getSystemJavaCompiler();// java的编译类
		StandardJavaFileManager standardFileManager = systemJavaCompiler.getStandardFileManager(null, null, null);// 管理类
		Iterable units = standardFileManager.getJavaFileObjects(fileName);// 将路径传入
		CompilationTask task = systemJavaCompiler.getTask(null, standardFileManager, null, null, null, units);
		task.call();
		standardFileManager.close();

		// 将类加载到内存里面
		URL[] urls = new URL[] { new URL("file:/D:/studyDir/codeDir/temSrc/") };
		URLClassLoader urlClassLoader = new URLClassLoader(urls);
		Class c = urlClassLoader.loadClass("com.lizhao.TimeProxyN");
		System.out.println(c.getName());

		// 获取构造函数,里面传入的值就是代表 需要传入的类型,
		// 比如可以传入 (int a,int b),就表示寻找构造构造函数Const(int a,int b)
		Constructor ctr = c.getConstructor(InvocationHandler.class);
		Object m = ctr.newInstance(h);
		return  m;
	}
}

通过这个类可以获得一个动态代理对象,而且里面的所有方法都给重写了;

InvocationHandler.java接口

package com.lizhao;

import java.lang.reflect.Method;

public interface InvocationHandler {
	public void invoke(Object proxy, Method method) throws Exception;
	
}

实现接口的类:

TimeInvocationHandler .java

package com.lizhao;

import java.lang.reflect.Method;

public class TimeInvocationHandler implements InvocationHandler {

	private Object object;
	
	public TimeInvocationHandler(Object object){
		this.object=object;
	}
	
	@Override
	public void invoke(Object proxy, Method method) throws Exception {
		System.out.println("start time: "+System.currentTimeMillis());
		//proxy.getClass().getMethod(name, null);
		method.invoke(object);
		System.out.println("end time: "+System.currentTimeMillis());
	}

}

4.我们想要操作的类的接口:

package com.lizhao;

public interface Moveable {
	public void move();
}



package com.lizhao;

public class Tank implements Moveable{

	@Override
	public void move() {
		System.out.println("Tank move start");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("Tank move end");
	}
	
}

5.在test函数里面测试:

package com.lizhao.test;
import com.lizhao.Moveable;
import com.lizhao.Proxy;
import com.lizhao.Tank;
import com.lizhao.TimeInvocationHandler;
public class Main {
	public static void main(String[] args) throws Exception  {
		Tank t = new Tank();
		Moveable newProxyInstance = (Moveable) new Proxy().newProxyInstance(Moveable.class,new TimeInvocationHandler(t));
		newProxyInstance.move();
	}
}

里面遇到的一些问题记录:

String fileName = System.getProperty("user.dir")+"\\src\\com\\lizhao\\TimeProxy.java";//java文件存放的路径、
这个路径是要写文件的全名称,之前就写到了src目录,然后会报找不到文件的错误

JavaCompiler systemJavaCompiler = ToolProvider.getSystemJavaCompiler();//java的编译类
StandardJavaFileManager standardFileManager = systemJavaCompiler.getStandardFileManager(null, null, null );//管理类、
这是获取jdk给我们提供的编译类,如果你是直接使用jre的话就会报空指针错误。因为jre里面是无法获取这个类的。
解决办法:1.window--preference--java--installed jres--》右边编辑框Search,找到自己jdk的目录,也可以是上一层,比如:C:\Program Files\Java\,在这个目录下面查找,然后列表里面就会出现jdk。
2.项目目录上面右键--properties--Java Build Path--remove原来的jre,点击右边的 Add Library,点击第二个Aleernate Jre,这时候就可以选择jdk了

PS:这个地方之前在网上找原因说是jdk版本的问题,然后我去重新下载了个最新的,从1.8.5到1.8.112。中间因为先下载了32位的jdk,然后配置好环境变量后,打开eclipse就报错"exit code=13",是因为jdk版本位数和eclipse版本位数不匹配导致的,又重新下回来了、。。

** 根据视频    马士兵---【设计模式--动态代理】 **

**
**

虽然原理是这样,但是在我们具体用的时候就不会这个麻烦的,下面就拿SSH框架中的DAO层举个例子:

我们知道在mvc框架里面程序的运行方式是这样的:Client--》Action--》Service--》Dao--model

当我们想要在Dao层加入一些自己的逻辑,同时又不想去改变DAO原来的代码,就像下面这么写:

test.java

package com.bjsxt.service;

import java.lang.reflect.Proxy;
import java.net.URL;
import java.net.URLClassLoader;

import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.bjsxt.dao.UserDAO;
import com.bjsxt.invoke.LogInvocationHandler;
import com.bjsxt.model.User;

public class UserServiceTest {
	
	@Test
	public void test() throws Exception {
		//使用了spring,和动态代理关系不大,这个就相当于读取配置文件
		BeanFactory beanFaction = new ClassPathXmlApplicationContext("beans.xml"); 

		//第一个参数ClassLoader loader
		URL[] urls = new URL[] { new URL("file:/"+System.getProperty("user.dir")+"/src/") };
		URLClassLoader urlClassLoader = new URLClassLoader(urls);
		//第二个参数 Class<?>[] interfaces,
		Class classes[] = new Class[] {UserDAO.class};
		//第三个参数InvocationHandler h
		UserService userService = (UserService) beanFaction.getBean("userService");
		LogInvocationHandler logInvocationHandler = new LogInvocationHandler(userService.getUserDAO());
		//获取代理对象		
		UserDAO newProxyInstance = (UserDAO) Proxy.newProxyInstance(urlClassLoader ,classes , logInvocationHandler);
		
		userService.setUserDAO(newProxyInstance);
		userService.add();
	}

}

一些我认为比较重要的地方做了注释

相关的类我也贴出来:

UserDAO.class

package com.bjsxt.dao;

public abstract interface UserDAO {
	public void sava();
}

UserMySqlDao.java//实现了UserDAO接口

package com.bjsxt.dao.impl;

import com.bjsxt.dao.UserDAO;


public class UserMySqlDao implements UserDAO{
	private String daoName;
	public String getDaoName() {
		return daoName;
	}
	public void setDaoName(String daoName) {
		this.daoName = daoName;
	}
	public void sava(){
		System.out.println("UserMySqlDao:sava()");
	}
	@Override
	public String toString() {
		return "daoName:"+daoName;
	}
	public void init(){
		System.out.println("init UserMySqlDao");
	}
}


LogInvocationHandler.java



package com.bjsxt.invoke;

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

public class LogInvocationHandler implements InvocationHandler {
	private Object target;
	public LogInvocationHandler(Object target) {
		this.target = target;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("LogInvocationHandler: log printing");
		method.invoke(target);
		System.out.println("LogInvocationHandler: log end");
		return null;
	}
}

UserService.java

package com.bjsxt.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import com.bjsxt.dao.UserDAO;
import com.bjsxt.model.User;

public class UserService {
	UserDAO userDAO;
	public UserService(){
	}
	public UserDAO getUserDAO() {
		return userDAO;
	}
	@Autowired
	public void setUserDAO(@Qualifier("UserMySql2")UserDAO userDAO) {
		this.userDAO = userDAO;
	}
	public void add(User user){
		this.userDAO.sava();
	}
	public void add(){
		this.userDAO.sava();
	}
	public void init(){
		System.out.println("init UserService");
	}
	public void destroy(){
		System.out.println("desrory UserService");
	}
	
}

运行之后:

init UserMySqlDao
init UserService
LogInvocationHandler: log printing
UserMySqlDao:sava()
LogInvocationHandler: log end