對JDK動態代理的模擬 動態代理在JDK中的實現: 來看看newProxyInstance()這個方法在JDK中的定義 它需要三個參數: ClassLoader loader:類載入器,JDK代理中認為由同一個類載入器載入的類生成的對象相同所以要傳入一個載入器,而且在代理對象生成過程中也可能用到類加 ...
對JDK動態代理的模擬
動態代理在JDK中的實現:
IProducer proxyProduec = (IProducer)Proxy.newProxyInstance(producer.getClass().getClassLoader()
, producer.getClass().getInterfaces(),new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object rtValue = null;
Float money = (Float)args[0];
if("saleProduct".equals(method.getName())){
rtValue = method.invoke(producer, money * 0.8f);
}
return rtValue;
}
});
來看看newProxyInstance()這個方法在JDK中的定義
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
...
}
它需要三個參數:
ClassLoader loader:類載入器,JDK代理中認為由同一個類載入器載入的類生成的對象相同所以要傳入一個載入器,而且在代理對象生成過程中也可能用到類載入器。
Class<?>[] interfaces:要被代理的類的介面,因為類可以實現多個介面,使用此處給的是Class數組。
InvocationHandler h:代理邏輯就是通過重寫該介面的invoke()方法實現
通過對newProxyInstance()方法的分析我們能可以做出以下分析:
第二個參數Class<?>[] interfaces是介面數組,那麼為什麼需要被代理類的介面?應該是為了找到要增強的方法,因為由JDK實現的動態代理只能代理有介面的類,
2.InvocationHandler h:參數通過重寫其invoke()方法實現了對方法的增強。我們先來看一下invoke()方法是如何定義的
/**
* 作用:執行的被代理對象的方法都會經過此方法
* @param proxy 代理對象的引用
* @param method 當前執行的方法的對象
* @param args 被代理對象方法的參數
* @return 被代理對象方法的返回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
而想要重新該方法並完成對目標對象的代理,就需要使用method對象的invoke()方法(註:這個方法與InvocationHandler 中的invoke方法不同不過InvocationHandler 中的invoke方法主要也是為了聲明使用Method中的invoke()方法)。我們在來看看Method中的invoke()方法
public Object invoke(Object obj, Object... args)
這裡終於看到我們要代理的對象要寫入的位置。
對有以上內容,我們可以做出以下猜想:(說是猜想,但實際JDk的動態代理就是基於此實現,不過其處理的東西更多,也更加全面)
Proxy.newProxyInstance(..)這個方法的並不參與具體的代理過程,而是通過生成代理對象proxy來調用InvocationHandler 中的invoke()方法,通過invoke()方法來實現代理的具體邏輯。
所以我以下模擬JDK動態代理的這個過程,就是基於以上猜想實現,需要寫兩個內容,一個是生成代理對象的類(我命名為ProxyUtil),一個實現對代理對象的增強(我命名為MyHandler介面)
Proxy類
package wf.util;
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.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class ProxyUtil {
/**
* public UserDaoImpl(User)
* @param targetInf
* @return
*/
public static Object newInstance(Class targetInf,MyHandler h){
Object proxy = null;
String tab = "\t";
String line = "\n";
String infname = targetInf.getSimpleName();
String content = "";
//這裡把代理類的包名寫死了,但JDK實現的過程中會判斷代理的方法是否是public,如果是則其包名是預設的com.sun.Proxy包下,而如果方法類型不是public則生產的Java文件包名會和要代理的對象包名相同,這是和修飾符的訪問許可權有關
String packageName = "package com.google;"+line;
String importContent = "import "+targetInf.getName()+";"+line
+"import wf.util.MyHandler;"+line
+"import java.lang.reflect.Method;"+line;
//聲明類
String clazzFirstLineContent = "public class $Proxy implements "+infname+"{"+line;
//屬性
String attributeCont = tab+"private MyHandler h;"+line;
//構造方法
String constructorCont = tab+"public $Proxy (MyHandler h){" +line
+tab+tab+"this.h = h;"+line
+tab+"}"+line;
//要代理的方法
String mtehodCont = "";
Method[] methods = targetInf.getMethods();
for (Method method : methods) {
//方法返回值類型
String returnType = method.getReturnType().getSimpleName();
// 方法名稱
String methodName = method.getName();
//方法參數
Class<?>[] types = method.getParameterTypes();
//傳入參數
String argesCont = "";
//調用目標對象的方法時的傳參
String paramterCont = "";
int flag = 0;
for (Class<?> type : types) {
String argName = type.getSimpleName();
argesCont = argName+" p"+flag+",";
paramterCont = "p" + flag+",";
flag++;
}
if (argesCont.length()>0){
argesCont=argesCont.substring(0,argesCont.lastIndexOf(",")-1);
paramterCont=paramterCont.substring(0,paramterCont.lastIndexOf(",")-1);
}
mtehodCont+=tab+"public "+returnType+" "+methodName+"("+argesCont+")throws Exception {"+line
+tab+tab+"Method method = Class.forName(\""+targetInf.getName()+"\").getDeclaredMethod(\""+methodName+"\");"+line;
if (returnType == "void"){
mtehodCont+=tab+tab+"h.invoke(method);"+line;
}else {
mtehodCont+=tab+tab+"return ("+returnType+")h.invoke(method);"+line;
}
mtehodCont+=tab+"}"+line;
}
content=packageName+importContent+clazzFirstLineContent+attributeCont+constructorCont+mtehodCont+"}";
// System.out.println(content);
//把字元串寫入java文件
File file = new File("D:\\com\\google\\$Proxy.java");
try {
if (!file.exists()){
file.createNewFile();
}
FileWriter fw = new FileWriter(file);
fw.write(content);
fw.flush();
fw.close();
//編譯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();
URL[] urls = new URL[]{new URL("file:D:\\\\")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class clazz = urlClassLoader.loadClass("com.google.$Proxy");
Constructor constructor = clazz.getConstructor(MyHandler.class);
proxy = constructor.newInstance(h);
}catch (Exception e){
e.printStackTrace();
}
return proxy;
}
}
該類會通過String字元串生成一個java類文件進而生成代理對象
補充: 我在實現Java文件時把代理類的包名寫死了,但JDK實現的過程中會判斷代理的方法是否是public,如果是則其包名是預設的com.sun.Proxy包下,而如果方法類型不是public則生產的Java文件包名會和要代理的對象包名相同,這是和修飾符的訪問許可權有關
生成的java類為($Proxy)
package com.google;
import wf.dao.UserDao;
import wf.util.MyHandler;
import java.lang.reflect.Method;
public class $Proxy implements UserDao{
private MyHandler h;
public $Proxy (MyHandler h){
this.h = h;
}
public void query()throws Exception {
Method method = Class.forName("wf.dao.UserDao").getDeclaredMethod("query");
h.invoke(method);
}
public String query1(String p)throws Exception {
Method method = Class.forName("wf.dao.UserDao").getDeclaredMethod("query1");
return (String)h.invoke(method);
}
}
可以看到代理對象其實起一個中專作用,來調用實現代理邏輯的MyHandler介面
MyHandler實現如下:
package wf.util;
import java.lang.reflect.Method;
public interface MyHandler {
//這裡做了簡化處理只需要傳入method對象即可
public Object invoke(Method method);
}
其實現類如下
package wf.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MyHandlerImpl implements MyHandler {
Object target;
public MyHandlerImpl(Object target){
this.target=target;
}
@Override
public Object invoke(Method method) {
try {
System.out.println("MyHandlerImpl");
return method.invoke(target);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
這裡對代理對象的傳入是通過構造方法來傳入。
至此模擬完成,看一下以下結果
package wf.test;
import wf.dao.UserDao;
import wf.dao.impl.UserDaoImpl;
import wf.proxy.UserDaoLog;
import wf.util.MyHandlerImpl;
import wf.util.MyInvocationHandler;
import wf.util.ProxyUtil;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) throws Exception {
final UserDaoImpl target = new UserDaoImpl();
System.out.println("自定義代理");
UserDao proxy = (UserDao) ProxyUtil.newInstance(UserDao.class, new MyHandlerImpl(new UserDaoImpl()));
proxy.query();
System.out.println("java提供的代理");
UserDao proxy1 = (UserDao) Proxy.newProxyInstance(Test.class.getClassLoader(), new Class[]{UserDao.class},
new MyInvocationHandler(new UserDaoImpl()));
proxy1.query();
}
}
輸出:
自定義代理
MyHandlerImpl
查詢資料庫
------分界線----
java提供的代理
proxy
查詢資料庫
兩種方式都實現了代理,而實際上JDK的動態代理的主要思想和以上相同,不過其底層不是通過String來實現代理類的Java文件的編寫,而JDK則是通過byte[]實現,其生成Class對象時通過native方法實現,通過c++代碼實現,不是java要討論的內容。
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
private static native Class<?> defineClass0(ClassLoader loader, String name,
byte[] b, int off, int len);