SPRing的动态代理有两种:一是JDK的动态代理;另一个是cglib动态代理(通过修改字节码来实现代理)。今天咱们主要讨论JDK动态代理的方式。JDK的代理方式主要就是通过反射跟动态编译来实现的,下面咱们就通过代码来看看它具体是怎么实现的。
假设我们要对下面这个用户管理进行代理:
接口:
package cn.hackcoder.service;/** * Created by linzhichao on 2017/2/21. */public interface UserMgr { void addUser(); void delUser(); void sayHi(String name);}实现:
package cn.hackcoder.service.impl;import cn.hackcoder.service.UserMgr;import java.util.Random;/** * Created by linzhichao on 2017/2/21. */public class UserMgrImpl implements UserMgr { @Override public void addUser() { System.out.println("添加用户....."); } @Override public void delUser() { System.out.println("删除用户....."); } @Override public void sayHi(String name) { try { Thread.sleep(new Random().nextInt(3000)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("hi:" + name); }}按照代理模式的实现方式,肯定是用一个代理类,让它也实现UserMgr接口,然后在其内部声明一个UserMgrImpl,然后分别调用addUser和delUser和sayHi方法,并在调用前后加上我们需要的其他操作。但是这样很显然都是写死的,我们怎么做到动态呢?别急,接着看。 我们知道,要实现代理,那么我们的代理类跟被代理类都要实现同一接口,但是动态代理的话我们根本不知道我们将要代理谁,也就不知道我们要实现哪个接口,那么要怎么办呢?我们只有知道要代理谁以后,才能给出相应的代理类,那么我们何不等知道要代理谁以后再去生成一个代理类呢?想到这里,我们好像找到了解决的办法,就是动态生成代理类!
这时候我们亲爱的反射又有了用武之地,我们可以写一个方法来接收被代理类,这样我们就可以通过反射知道它的一切信息——包括它的类型、它的方法等等。
JDK动态代理的两个核心分别是InvocationHandler和Proxy,下面我们就用简单的代码来模拟一下它们是怎么实现的:
InvocationHandler接口:
package cn.hackcoder.proxy;import java.lang.reflect.Method;/** * Created by linzhichao on 2017/2/21. */public interface InvocationHandler { void invoke(Object o, Method m, Object... params);}实现动态代理的关键部分,通过Proxy动态生成我们具体的代理类:
package cn.hackcoder.proxy;import javax.tools.JavaCompiler;import javax.tools.StandardJavaFileManager;import javax.tools.ToolProvider;import java.io.File;import java.io.FileWriter;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.net.URL;import java.net.URLClassLoader;import java.util.Arrays;/** * Created by linzhichao on 2017/2/21. */public class Proxy { private static final String srcDir = System.getProperty("user.dir") + "/"; /** * @param infer 被代理类的接口 * @param h 代理类 * @return * @throws Exception */ public static Object newProxyInstance(Class infer, InvocationHandler h) throws Exception { String proxyedName = infer.getSimpleName(); String packageName = h.getClass().getPackage().getName(); String methodStr = ""; String rt = "/r/n"; //利用反射得到infce的所有方法,并重新组装 Method[] methods = infer.getMethods(); for (Method m : methods) { Class[] classes = m.getParameterTypes(); StringBuilder paramList = new StringBuilder(); StringBuilder paramValueList = new StringBuilder(); StringBuilder paramNameList = new StringBuilder(); int i = 0; for (Class c : classes) { paramList.append(c.getName()).append(" arg").append(i).append(","); paramValueList.append(" arg").append(i).append(","); paramNameList.append(c.getName()).append(".class,"); i++; } if (classes.length > 0) { paramList.deleteCharAt(paramList.length() - 1); paramNameList.insert(0, ","); paramNameList.deleteCharAt(paramNameList.length() - 1); paramValueList.insert(0, ","); paramValueList.deleteCharAt(paramValueList.length() - 1); } methodStr += " @Override" + rt + " public " + m.getReturnType() + " " + m.getName() + "(" + paramList.toString() + ") {" + rt + " try {" + rt + " Method md = " + infer.getName() + ".class.getMethod(/"" + m.getName() + "/"" + paramNameList + ");" + rt + " h.invoke(this, md" + paramValueList + ");" + rt + " }catch(Exception e) {e.printStackTrace();}" + rt + " }" + rt; } //生成Java源文件 String srcCode = "package " + packageName + ";" + rt + "import java.lang.reflect.Method;" + rt + "public class $Proxy" + proxyedName + " implements " + infer.getName() + "{" + rt + " public $Proxy" + proxyedName + "(InvocationHandler h) {" + rt + " this.h = h;" + rt + " }" + rt + " " + packageName + ".InvocationHandler h;" + rt + methodStr + rt + "}"; String[] paths = packageName.split("//."); StringBuilder sb = new StringBuilder(); Arrays.asList(paths).forEach(path -> { sb.append(path); sb.append("/"); }); String fileName = srcDir + sb.toString() + "$Proxy" + proxyedName + ".java"; File f = new File(fileName); if (!f.exists()) { f.getParentFile().mkdirs(); f.createNewFile(); } FileWriter fw = new FileWriter(f); fw.write(srcCode); fw.flush(); fw.close(); //将Java文件编译成class文件 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null); Iterable units = fileMgr.getJavaFileObjects(fileName); JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units); t.call(); fileMgr.close(); //加载到内存,并实例化 URL[] urls = new URL[]{new URL("file://" + srcDir)}; URLClassLoader ul = new URLClassLoader(urls); Class c = ul.loadClass(packageName + ".$Proxy" + proxyedName); Constructor ctr = c.getConstructor(InvocationHandler.class); Object m = ctr.newInstance(h); return m; }}这个类的主要功能就是,根据被代理对象的信息,动态组装一个代理类,生成$ProxyUserMgr.java文件,然后将其编译成$ProxyUserMgr.class。这样我们就可以在运行的时候,根据我们具体的被代理对象生成我们想要的代理类了。这样一来,我们就不需要提前知道我们要代理谁。也就是说,你想代理谁,想要什么样的代理,我们就给你生成一个什么样的代理类。
然后,在客户端我们就可以随意的进行代理了。
package cn.hackcoder.proxy;import cn.hackcoder.service.UserMgr;import cn.hackcoder.service.impl.UserMgrImpl;/** * Created by linzhichao on 2017/2/21. */public class Client { public static void main(String[] args) throws Exception { UserMgr mgr = new UserMgrImpl(); //为用户管理添加事务处理 InvocationHandler h = new TransactionHandler(mgr); UserMgr u = (UserMgr) Proxy.newProxyInstance(UserMgr.class, h); //为用户管理添加显示方法执行时间的功能 TimeHandler h2 = new TimeHandler(u); u = (UserMgr) Proxy.newProxyInstance(UserMgr.class, h2); u.addUser(); System.out.println("/r/n==========华丽的分割线==========/r/n"); u.delUser(); System.out.println("/r/n==========华丽的分割线==========/r/n"); u.sayHi( "hackcoder"); }}这里我写了两个代理的功能,一个是事务处理,一个是显示方法执行时间的代理,当然都是非常简单的写法,只是为了说明这个原理。当然,我们可以想Spring那样将这些AOP写到配置文件,因为之前那篇已经写了怎么通过配置文件注入了,这里就不重复贴了。 到这里,你可能会有一个疑问:你上面说,只要放到容器里的对象,都会有容器的公共服务,我怎么没看出来呢?好,那我们就继续看一下我们的代理功能:事务处理:
package cn.hackcoder.proxy;import java.lang.reflect.Method;/** * Created by linzhichao on 2017/2/21. */public class TransactionHandler implements InvocationHandler { private Object target; public TransactionHandler(Object target) { super(); this.target = target; } @Override public void invoke(Object o, Method m, Object... params) { System.out.println("开启事务....."); try { m.invoke(target, params); } catch (Exception e) { e.printStackTrace(); } System.out.println("提交事务....."); }}执行时间:package cn.hackcoder.proxy;import java.lang.reflect.Method;/** * Created by linzhichao on 2017/2/21. */public class TimeHandler implements InvocationHandler { private Object target; public TimeHandler(Object target) { this.target = target; } @Override public void invoke(Object o, Method m, Object... params) { long time = -System.currentTimeMillis(); try { m.invoke(target, params); } catch (Exception e) { e.printStackTrace(); } time += System.currentTimeMillis(); System.out.println(m.getName() + "耗时:" + time); }}
新闻热点
疑难解答