原始碼專題之spring設計模式
jdk動態代理
jdk動態代理
程式碼實現
滿足代理模式應用場景的三個必要條件
- 兩個角色:執行者、被代理物件
- 注重過程,必須要做,被代理物件沒時間做或者不想做(怕羞羞),不專業
- 執行者必須拿到被代理物件的個人資料(執行者持有被代理物件的引用)
jdk的動態代理通過呼叫Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法,生成目標物件的代理類,interfaces引數為目標物件所實現的全部介面,InvocationHandler的實現類負責在呼叫方法前後處理自定義邏輯,下面我們以媒婆介紹物件為背景實現:
1 首先是需要找物件的人
public interface Person { void findLove(); String getSex(); String getName(); } --- public class XiaoFang implements Person{ private String sex = "女"; private String name = "小芳"; @Override public void findLove() { System.out.println("我叫" + this.name + ",性別:" + this.sex + "我找物件的要求是:"); System.out.println("高富帥"); System.out.println("有房有車的"); System.out.println("身高要求180cm以上,體重70kg"); } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
2 小芳不好意思找物件,就要找媒婆來搭橋
public class Meipo implements InvocationHandler { private Person target; //需要代理的目標物件 //獲取被代理人的個人資料 //將目標物件傳入進行代理 public Object getInstance(Person target) throws Exception{ this.target = target; Class clazz = target.getClass(); System.out.println("被代理物件的class是:"+clazz); return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);//返回代理物件 } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("開始進行海選..."); System.out.println("------------"); //代理找物件 Object ret = method.invoke(this.target, args); System.out.println("------------"); System.out.println("如果合適的話,就準備結婚); return ret; } }
3 正式開始委託媒婆找物件
public class TestFindLove { public static void main(String[] args) { try { Person obj = (Person)new Meipo().getInstance(new XiaoFang()); System.out.println(obj.getClass()); obj.findLove(); } catch (Exception e) { e.printStackTrace(); } } }
手寫實現jdk動態代理
這裡我們不用jdk提供的reflect api,自己手寫實現jdk動態代理,來進一步看一下動態代理的內部實現。
1 需要找物件的人,參照上面的person介面及實現
2 媒婆需要獲取被代理人的個人資訊,並生成一個替代品(代理物件)
public class MyPorxy { private static String ln = "\r\n"; public static Object newProxyInstance(MyClassLoader classLoader, Class<?>[] interfaces, MyInvocationHandler h){ try{ //1、生成原始碼 String proxySrc = generateSrc(interfaces[0]); //2、將生成的原始碼輸出到磁碟,儲存為.java檔案 String filePath = MyPorxy.class.getResource("").getPath(); File f = new File(filePath + "$Proxy0.java"); FileWriter fw = new FileWriter(f); fw.write(proxySrc); fw.flush(); fw.close(); //3、編譯原始碼,並且生成.class檔案 JavaCompilercompiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null); Iterable iterable = manager.getJavaFileObjects(f); CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable); task.call(); manager.close(); //4.將class檔案中的內容,動態載入到JVM中來 //5.返回被代理後的代理物件 Class proxyClass = classLoader.findClass("$Proxy0"); Constructor c = proxyClass.getConstructor(MyInvocationHandler.class); f.delete(); return c.newInstance(h); }catch (Exception e) { e.printStackTrace(); } return null; } private static String generateSrc(Class<?> interfaces){ StringBuffer src = new StringBuffer(); src.append("package com.lh.reflect;" + ln); src.append("import java.lang.reflect.Method;" + ln); src.append("public class $Proxy0 implements " + interfaces.getName() + "{" + ln); src.append("MyInvocationHandler h;" + ln); src.append("public $Proxy0(MyInvocationHandler h) {" + ln); src.append("this.h = h;" + ln); src.append("}" + ln); for (Method m : interfaces.getMethods()) { src.append("public " + m.getReturnType().getName() + " " + m.getName() + "(){" + ln); src.append("try{" + ln); src.append("Method m = " + interfaces.getName() + ".class.getMethod(\"" +m.getName()+"\",new Class[]{});" + ln); src.append("this.h.invoke(this,m,null);" + ln); src.append("}catch(Throwable e){e.printStackTrace();}" + ln); src.append("}" + ln); } src.append("}"); return src.toString(); } }
3 MyClassLoader程式碼:
//程式碼生成、編譯、重新動態load到JVM public class MyClassLoader extends ClassLoader{ private File baseDir; public MyClassLoader(){ String basePath = MyClassLoader.class.getResource("").getPath(); this.baseDir = new java.io.File(basePath); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String className = MyClassLoader.class.getPackage().getName() + "." + name; if(baseDir != null){ File classFile = new File(baseDir,name.replaceAll("\\.", "/") + ".class"); if(classFile.exists()){ FileInputStream in = null; ByteArrayOutputStream out = null; try{ in = new FileInputStream(classFile); out = new ByteArrayOutputStream(); byte [] buff = new byte[1024]; int len; while ((len = in.read(buff)) != -1) { out.write(buff, 0, len); } return defineClass(className, out.toByteArray(), 0,out.size()); }catch (Exception e) { e.printStackTrace(); }finally{ if(null != in){ try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if(null != out){ try { out.close(); } catch (IOException e) { e.printStackTrace(); } } classFile.delete(); } } } return null; } }
4 接下來媒婆介紹物件的環節就水到渠成了
public interface MyInvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; } --- public class MyMeipo implements MyInvocationHandler { private Person target; //獲取被代理人的個人資料 public Object getInstance(Person target) throws Exception{ this.target = target; Class clazz = target.getClass(); System.out.println("被代理物件的class是:"+clazz); return MyPorxy.newProxyInstance(new MyClassLoader(), clazz.getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("開始進行海選..."); System.out.println("------------"); Object ret = method.invoke(this.target, args); System.out.println("------------"); System.out.println("如果合適的話,就準備結婚"); return ret; } }
總結
jdk動態代理原理:
- 拿到被代理物件的引用,然後獲取它的介面
- JDK代理重新生成一個類,同時實現我們給的代理物件所實現的介面
- 把被代理物件的引用也拿到了
- 重新動態生成一個class位元組碼
- 然後編譯