爲了便於理解jdk動態代理,模仿jdk動態代理的思路,模擬寫一個動態代理demo,V1思路如下:
- 拼接代理對象源碼,使用文件流寫出$Proxy.java文件
- 將$Proxy.java動態編譯成.class文件
- $Proxy.class文件動態加載到JVM內存中,產生Class對象
- 使用反射構造出代理對象
- 用戶調用代理對象實現方法代理
源碼如下:
定義接口
package com.ant.myJdkProxy;
/**
* 接口,模擬有無返回值、有無參數多種情況方法
*/
public interface IndexDao {
void test();
void test(String s);
String testReturn(String s,Integer i);
}
定義實現類,即被代理對象:
package com.ant.myJdkProxy;
/**
* 接口實現類
*/
public class IndexDaoImpl implements IndexDao{
@Override
public void test() {
System.out.println("test()");
}
@Override
public void test(String s) {
System.out.println("Test("+s+")");
}
@Override
public String testReturn(String s, Integer i) {
System.out.println("testReturn("+s+","+i);
return "hshsh";
}
}
定義產生代理的工具類:
package com.ant.myJdkProxy;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
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;
/**
* 代理工具類
* 手動模擬jdk動態代理
* 步驟:根據傳進來的目標代理對象,動態實現代理邏輯的代理java對象文件
* .java文件-->編譯成class文件-->加載至jvm虛擬機內存,稱爲class對象-->反射創建代理對象-->調用代理方法實現代理
*
*/
public class ProxyUtil {
/**
* 生成java文件,假設生成在D盤下,包名是com.ant
* @param target 需要被代理的目標對象
* @param interfc 目標對象實現的接口
* @return
*/
public static Object getProxy(Object target,Class interfc) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
if(null == target || null == interfc){
throw new IllegalArgumentException("參數不允許爲空");
}
if(!interfc.isInterface()){
throw new IllegalArgumentException("必須是接口");
}
//1.生成java源代碼字符串
StringBuilder sb = genJavaStr(target,interfc);
//2.將源代碼寫成$Proxy.java至D:/com/ant下
File dir = new File("D:\\com\\ant");
if(!dir.exists()){
dir.mkdirs();
}
File file = new File("D:\\com\\ant\\$Proxy.java");
if(!file.exists()){
file.createNewFile();
}
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(sb.toString());
fileWriter.flush();
fileWriter.close();
//3.將$Proxy.java動態編譯成字節碼
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable units = fileMgr.getJavaFileObjects(file);
JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
t.call();
fileMgr.close();
//4.將class文件動態加載到jvm虛擬機內存中
URL[] urls = new URL[]{new URL("file:D:\\\\")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class<?> clazz = urlClassLoader.loadClass("com.ant.$Proxy");
//5.反射創建代理對象
Constructor<?> constructor = clazz.getConstructor(target.getClass());
return constructor.newInstance(target);
}
/**
* 生成源代碼字符串
* package com.ant
* public class $Proxy implement interface{
* private Object target;
*
* public $Proxy (Object target){
* this.target = target;
* }
*
* //重寫各個interface中的方法,並且在前後加上邏輯,執行目標對象的方法
* @Override
* public returnType methosName(args){
* System.out.println("before")
* returntype result = target.methosName(args);
System.out.println("after");
* return result;
* }
* }
*/
private static StringBuilder genJavaStr(Object target,Class interfc) {
String line = "\r\n";
String tab = "\t";
StringBuilder sb = new StringBuilder();
sb
.append("package com.ant;")
.append(line)
.append("public class $Proxy implements ").append(interfc.getName()).append(" {")
.append(line)
.append(tab)
.append("private ").append(target.getClass().getName()).append(" target;").append(line)
.append(tab).append("public $Proxy(").append(target.getClass().getName()).append(" target").append("){").append(line)
.append(tab).append(tab).append("this.target = target;").append(line)
.append(tab).append("}")
.append(line);
//方法
Method[] methods = interfc.getMethods();
if(null!=methods && methods.length>0){
for(Method m:methods){
sb.append(line).append(tab)
.append("@Override").append(line)
.append(tab)
//方法名稱
.append("public ").append(m.getReturnType().getName()).append(" ").append(m.getName()).append("(");
Class<?>[] parameterTypes = m.getParameterTypes();
//增加方法參數
String modalityParam = "";
if(null!=parameterTypes && parameterTypes.length>0){
StringBuilder sbu = new StringBuilder();
//var1,var2,var3 目標對象方法調用時使用
StringBuilder modalitySbu = new StringBuilder();
for(int i=0;i<parameterTypes.length;i++){
sbu.append(" ").append(parameterTypes[i].getName()).append(" var"+i).append(",");
modalitySbu.append("var"+i).append(",");
}
String temp = sbu.toString();
sb.append(temp.substring(0,sbu.lastIndexOf(",")));
String tempPa = modalitySbu.toString();
modalityParam = tempPa.substring(0,tempPa.lastIndexOf(","));
}
sb.append("){").append(line)
.append(tab).append(tab)
.append("System.out.println(\"方法邏輯處理之前\")").append(";").append(line)
.append(tab).append(tab);
if(void.class!=m.getReturnType()){
sb.append("return ");
}
sb.append("target.").append(m.getName()).append("(").append(modalityParam).append(");").append(line)
.append(tab).append("}");
}
}
sb.append(line).append("}");
return sb;
}
}
測試主函數:
package com.ant.myJdkProxy;
/**
* 測試主函數
*/
public class Main {
public static void main(String[] args){
IndexDao indexDao = new IndexDaoImpl();
try {
IndexDao proxy = (IndexDao)ProxyUtil.getProxy(indexDao, IndexDao.class);
proxy.test();
proxy.test("success");
proxy.testReturn("success",2333);
} catch (Exception e) {
e.printStackTrace();
}
}
}
到目前爲止我們簡單的動態代理已經寫完,但是V1版本存在的問題:代理邏輯是我們寫死的,無法達到用戶自行控制代理邏輯,即用戶無法通過先執行類似於加鎖,執行被代理邏輯,再解鎖這種方式控制,總得來說代理邏輯比較死,因此我們繼續向下升級我們的動態代理邏輯,思路如下:我們通過定義類似於jdk的InvocationHandler方式定義一個我們自己的接口MyInvocationHandler,當用戶執行代理對象的方法時,實際上是執行MyInvocationHandler.invoke方法,在invoke方法,我們將目標對象,目標對象執行的Method對象以及方法參數傳給用戶,由用戶自行調用Method.invoke()方法,同時用戶可以在調用invoke方法之前和之後增加自己的自定義邏輯,來實現完全與jdk動態代理相同的靈活性:
目標接口:
package com.ant.myJdkProxy.v2;
/**
* 接口,模擬有無返回值、有無參數多種情況方法
*/
public interface IndexDao {
void test() throws Throwable;
void test(String s);
String testReturn(String s, Integer i);
}
接口實現類:
package com.ant.myJdkProxy.v2;
/**
* 接口實現類
*/
public class IndexDaoImpl implements IndexDao {
@Override
public void test() {
System.out.println("test()");
}
@Override
public void test(String s) {
System.out.println("Test("+s+")");
}
@Override
public String testReturn(String s, Integer i) {
System.out.println("testReturn("+s+","+i);
return "hshsh";
}
}
自定義的InvocationHandler接口:
package com.ant.myJdkProxy.v2;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public interface MyInvocationHandler {
/**
* 模擬jdk InvocationHandler,實際上爲了能夠讓用戶自定義代理邏輯,提供給用戶的鉤子函數,由用戶定義代理邏輯與真實方法調用的順序
* 當用戶獲取到代理對象調用方法時,代理對象方法中實際上是調用本方法
* @param method
* @param args
* @return
*/
Object invoke(Object target,Method method,Object...args) throws InvocationTargetException, IllegalAccessException;
}
用戶使用代理時,自行創建的InvocationHandler實現,用於實現代理邏輯:
package com.ant.myJdkProxy.v2;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class IndexInvocationHandler implements MyInvocationHandler {
@Override
public Object invoke(Object target, Method method, Object... args) throws InvocationTargetException, IllegalAccessException {
System.out.println("方法執行開始前");
Object o = method.invoke(target, args);
System.out.println("方法執行開始後");
return o;
}
}
代理工具類:
package com.ant.myJdkProxy.v2;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
/**
* 代理工具類
* 手動模擬jdk動態代理
* 步驟:根據傳進來的目標代理對象,動態實現代理邏輯的代理java對象文件
* .java文件-->編譯成class文件-->加載至jvm虛擬機內存,稱爲class對象-->反射創建代理對象-->調用代理方法實現代理
*/
public class ProxyUtil {
/**
* 生成java文件,假設生成在D盤下,包名是com.ant
*
* @param target 需要被代理的目標對象
* @param interfc 目標對象實現的接口
* @return
*/
public static Object getProxy(Object target, Class interfc, MyInvocationHandler invocationHandler) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
if (null == target || null == interfc) {
throw new IllegalArgumentException("參數不允許爲空");
}
if (!interfc.isInterface()) {
throw new IllegalArgumentException("必須是接口");
}
//1.生成java源代碼字符串
StringBuilder sb = genJavaStr(target, interfc, invocationHandler);
//2.將源代碼寫成$Proxy.java至D:/com/ant下
File file = createJavaFile(sb.toString());
//3.將$Proxy.java動態編譯成字節碼
compile(file);
//4.將class文件動態加載到jvm虛擬機內存中
Class clazz = loadClass();
//5.反射創建代理對象
Constructor<?> constructor = clazz.getConstructor(target.getClass(), MyInvocationHandler.class);
return constructor.newInstance(target,invocationHandler);
}
/**
* 生成源代碼字符串
* package com.ant
* public class $Proxy implement interface{
* private Object target;
* <p>
* public $Proxy (Object target){
* this.target = target;
* }
* <p>
* //重寫各個interface中的方法,並且在前後加上邏輯,執行目標對象的方法
*
* @Override public returnType methosName(args){
* System.out.println("before")
* returntype result = target.methosName(args);
* System.out.println("after");
* return result;
* }
* }
*/
private static StringBuilder genJavaStr(Object target, Class interfc, MyInvocationHandler invocationHandler) {
String line = "\r\n";
String tab = "\t";
StringBuilder sb = new StringBuilder();
sb
.append("package com.ant;")
.append(line)
.append("public class $Proxy implements ").append(interfc.getName()).append(" {")
.append(line)
.append(tab)
.append("private ").append(target.getClass().getName()).append(" target;").append(line)
.append("private ").append("com.ant.myJdkProxy.v2.MyInvocationHandler invocationHandler;").append(line)
.append(tab).append("public $Proxy(").append(target.getClass().getName()).append(" target").append(",").append("com.ant.myJdkProxy.v2.MyInvocationHandler invocationHandler").append("){").append(line)
.append(tab).append(tab).append("this.target = target;").append(line)
.append(tab).append(tab).append("this.invocationHandler = invocationHandler;").append(line)
.append(tab).append("}")
.append(line);
//方法
Method[] methods = interfc.getMethods();
if (null != methods && methods.length > 0) {
for (Method m : methods) {
sb.append(line).append(tab)
.append("@Override").append(line)
.append(tab)
//方法名稱
.append("public ").append(m.getReturnType().getName()).append(" ").append(m.getName()).append("(");
Class<?>[] parameterTypes = m.getParameterTypes();
//增加方法參數
String modalityParam = "";
String reflectParam = "";
if (null != parameterTypes && parameterTypes.length > 0) {
StringBuilder sbu = new StringBuilder();
//var1,var2,var3 目標對象方法調用時使用
StringBuilder modalitySbu = new StringBuilder();
for (int i = 0; i < parameterTypes.length; i++) {
sbu.append(" ").append(parameterTypes[i].getName()).append(" var" + i).append(",");
modalitySbu.append("var" + i).append(",");
reflectParam = reflectParam + parameterTypes[i].getName() + ".class";
reflectParam += ",";
}
String temp = sbu.toString();
sb.append(temp.substring(0, sbu.lastIndexOf(",")));
String tempPa = modalitySbu.toString();
modalityParam = tempPa.substring(0, tempPa.lastIndexOf(","));
reflectParam = reflectParam.substring(0, reflectParam.lastIndexOf(","));
}
sb.append("){").append(line)
.append(tab).append(tab);
//方法體
/*
try{
return (java.lang.String)invocationHandler.invoke(target,target.getDeclaredMethod("test",java.lang.String.class,java.lang.Integer.class),var0,var1,var2);
}cache(Throwable t){
t.printStackTrace();
}
return null;
*/
sb.append("try {").append(line).append(tab).append(tab).append(tab);
if (void.class != m.getReturnType()) {
sb.append("return ").append("(").append(m.getReturnType().getName()).append(")");
}
sb.append("invocationHandler.invoke(").append("target,")
.append("target.getClass().getDeclaredMethod(").append("\"").append(m.getName()).append("\"");
if (null != reflectParam && reflectParam != "") {
sb.append(",").append(reflectParam);
}
sb.append(")");
if (m.getParameterTypes() != null && m.getParameterTypes().length > 0) {
sb.append(",").append(modalityParam);
}
sb.append(");");
sb.append(line).append(tab).append("}catch(Throwable t){").append(line)
.append(tab).append(tab).append(tab).append("t.printStackTrace();").append(line)
.append(tab).append(tab).append("}").append(line);
if(void.class != m.getReturnType()){
sb.append(tab).append(tab).append("return null;");
}
sb.append(line).append(tab).append("}");
}
}
sb.append(line).append("}");
return sb;
}
/**
* 根據源代碼,創建java文件
*
* @param sourceCode
* @return
* @throws IOException
*/
private static File createJavaFile(String sourceCode) throws IOException {
File dir = new File("D:\\com\\ant");
if (!dir.exists()) {
dir.mkdirs();
}
File file = new File("D:\\com\\ant\\$Proxy.java");
if (!file.exists()) {
file.createNewFile();
}
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(sourceCode);
fileWriter.flush();
fileWriter.close();
return file;
}
/**
* 將源代碼編譯成字節碼文件
*
* @param file
* @throws IOException
*/
private static void compile(File file) throws IOException {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable units = fileMgr.getJavaFileObjects(file);
JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
t.call();
fileMgr.close();
}
/**
* 動態加載class文件到jvm內存中
*
* @return
*/
private static Class loadClass() throws MalformedURLException, ClassNotFoundException {
URL[] urls = new URL[]{new URL("file:D:\\\\")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class<?> clazz = urlClassLoader.loadClass("com.ant.$Proxy");
return clazz;
}
}
測試類:
package com.ant.myJdkProxy.v2;
/**
* 測試主函數
*/
public class Main {
public static void main(String[] args){
IndexDao indexDao = new IndexDaoImpl();
try {
IndexDao proxy = (IndexDao) ProxyUtil.getProxy(indexDao, IndexDao.class, new IndexInvocationHandler() );
proxy.test();
proxy.test("2333");
proxy.testReturn("牛逼",2333);
} catch (Exception e) {
e.printStackTrace();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
我們自己實現的jdk動態代理只是demo級別的,有很多內容都沒有考慮到,其次在性能上與遠程的jdk動態代理完全無法比,因爲我們實現的動態代理涉及到多次IO操作,而jdk動態代理直接產生字節碼數組(byte[]),然後直接native方法產生Class對象,通過Class對象反射產生代理對象,速度是我們demo的97倍,測試代碼如下:
package com.ant.myJdkProxy.jdk;
import com.ant.myJdkProxy.v2.IndexDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JdkInvocationHandler implements InvocationHandler {
private IndexDao indexDao;
public JdkInvocationHandler(IndexDao indexDao){
this.indexDao = indexDao;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("jdk before");
Object result = method.invoke(indexDao,args);
System.out.println("jdk after");
return result;
}
}
package com.ant.myJdkProxy.v2;
import com.ant.myJdkProxy.jdk.JdkInvocationHandler;
import java.lang.reflect.Proxy;
/**
* 測試主函數
* 經過測試我們自己寫的動態代理執行時間是jdk動態代理的97倍
*/
public class Main {
public static void main(String[] args){
IndexDao indexDao = new IndexDaoImpl();
try {
//1973982603
long start1 = System.nanoTime();
IndexDao proxy = (IndexDao) ProxyUtil.getProxy(indexDao, IndexDao.class, new IndexInvocationHandler() );
proxy.test();
proxy.test("2333");
proxy.testReturn("牛逼",2333);
System.out.println(System.nanoTime()-start1);
//20197073
long start2 = System.nanoTime();
IndexDao indexDao1 = (IndexDao) Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{IndexDao.class}, new JdkInvocationHandler(new IndexDaoImpl()));
indexDao1.test();
indexDao1.test("2333");
indexDao1.testReturn("牛逼",2333);
System.out.println(System.nanoTime()-start2);
} catch (Exception e) {
e.printStackTrace();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}