需要知道一個(gè)東西是怎么來(lái)的完慧,你做一個(gè)就清楚了,所以我著手模擬了下
首先定義一個(gè)接口
public interface Action {
public void move();
public void speak();
}
實(shí)現(xiàn)這個(gè)接口
/**
* @Author: YangZaining
* @Date: Created in 19:40$ 2018/8/11$
*/
public class ActionImpl implements Action{
@Override
public void move() {
System.out.println("移動(dòng)了件炉。市殷。愕撰。。");
}
@Override
public void speak() {
System.out.println("sxl nb");//別在意這句話,我好朋友而已
}
}
現(xiàn)在我們有這樣一個(gè)要求搞挣,對(duì)move方法進(jìn)行測(cè)試計(jì)算它的執(zhí)行時(shí)間带迟,則我們會(huì)想到繼承或組合這個(gè)類,或者直接在main方法里調(diào)用測(cè)試囱桨,以下只是實(shí)現(xiàn)它的一種方式
如下:
/**
* @Author: YangZaining
* @Date: Created in 19:59$ 2018/8/11$
*/
public class Test implements Action {
Action action;
public Test(Action action) {//傳入實(shí)現(xiàn)的子類即可
this.action = action;
}
@Override
public void move() {
long st = System.currentTimeMillis();
action.move();
long ed = System.currentTimeMillis();
System.out.println("運(yùn)行了" + (ed - st) + "ms");
}
@Override
public void speak() {
action.speak();
}
public static void main(String[] args) {
Action action = new ActionImpl();
new Test(action).move();
}
}
output:
移動(dòng)了仓犬。。舍肠。搀继。
運(yùn)行了199840us
現(xiàn)在我又有個(gè)要求,在speak方法前后加入一句話,這時(shí)候你又會(huì)想到簡(jiǎn)單貌夕,再寫(xiě)這樣一個(gè)類就可以了律歼,但是每次需求的變更你都要寫(xiě)這樣一個(gè)類,不是很難受嗎啡专?而且我們需要測(cè)試的時(shí)候才記錄這些內(nèi)容,上線了制圈,我們要撤銷這些內(nèi)容们童,這樣做的話不是很麻煩嗎?
所以我們需要一個(gè)代理的代理鲸鹦,去減輕工作量慧库,幫我們生成這樣的代理對(duì)象,所以我們要著手動(dòng)態(tài)的去創(chuàng)建這個(gè)類
import javax.tools.*;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;
/**
* @Author: YangZaining
* @Date: Created in 19:32$ 2018/8/11$
*/
public class Proxy {
public static Object newProxynewInstance() throws Exception {
String s = "package cn.learn;\n" +
"\npublic class Test implements Action {\n" +
"\n" +
" Action action;\n" +
"\n" +
" public Test(Action action) {\n" +
" this.action = action;\n" +
" }\n" +
"\n" +
" @Override\n" +
" public void move() {\n" +
" long st = System.nanoTime();\n" +
" action.move();\n" +
" long ed = System.nanoTime();\n" +
" System.out.println(\"運(yùn)行了\" + (ed - st) + \"us\");\n" +
" }\n" +
"\n" +
" @Override\n" +
" public void speak() {\n" +
" action.speak();\n" +
" }\n" +
"\n" +
"}\n";
String filePath = "D:\\src\\cn\\learn\\Test.java";//本地建一個(gè)目錄馋嗜,以免和編譯期編譯的文件混淆
File fs = new File(filePath);
FileWriter fw = new FileWriter(fs);//寫(xiě)入文件里
fw.write(s);
fw.flush();
fw.close();
//編譯文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
System.out.println("CompilerName:" + compiler);//編譯器名字
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable it = fileManager.getJavaFileObjects(filePath);
JavaCompiler.CompilationTask t = compiler.getTask(null, fileManager, null, null, null,it);
t.call();
fileManager.close();
//加載到內(nèi)存并且生成新對(duì)象
URL[] urls = new URL[]{new URL("file:/"+"d:/src/")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class c = urlClassLoader.loadClass("cn.learn.Test");
Constructor constructor = c.getConstructor(Action.class);
Object object = constructor.newInstance(new ActionImpl());
return object;
}
}
然后主函數(shù)里調(diào)用這個(gè)方法既可以獲取到代理對(duì)象齐板,對(duì)這個(gè)類繼續(xù)加工
import javax.tools.*;
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;
/**
* @Author: YangZaining
* @Date: Created in 19:32$ 2018/8/11$
*/
public class Proxy {
public static Object newProxynewInstance(Class inf) throws Exception {
Method[] methods = inf.getMethods();
String s = "package cn.learn;\n" +
"\npublic class Test implements " + inf.getName() + " {\n" +
"\n" +
" " + inf.getName() + " action;\n" +
"\n" +
" public Test(" + inf.getName() + " action) {\n" +
" this.action = action;\n" +
" }\n";
String ln = "\r\n";
for (Method m : methods) {
s += " @Override\n";
if ("move".equals(m.getName())) {
s += "public " + m.getGenericReturnType() + " " + m.getName() + "(){" +
" long st = System.nanoTime();\n" +
" action."+m.getName()+"();\n" +
" long ed = System.nanoTime();\n" +
" System.out.println(\"運(yùn)行了\" + (ed - st) + \"us\");\n" +
"}";
}
else {
s += "public " + m.getGenericReturnType() + " " + m.getName() + "(){" +
" action."+m.getName()+"();\n" +
" }\n" +
"\n";
}
}
s+="}\n";
String filePath = "D:\\src\\cn\\learn\\Test.java";
File fs = new File(filePath);
FileWriter fw = new FileWriter(fs);//寫(xiě)入文件里
fw.write(s);
fw.flush();
fw.close();
//編譯文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
System.out.println("CompilerName:" + compiler);//編譯器名字
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable it = fileManager.getJavaFileObjects(filePath);
JavaCompiler.CompilationTask t = compiler.getTask(null, fileManager, null, null, null, it);
t.call();
fileManager.close();
//加載到內(nèi)存并且生成新對(duì)象
URL[] urls = new URL[]{new URL("file:/"+"d:/src/")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class c = urlClassLoader.loadClass("cn.learn.Test");
Constructor constructor = c.getConstructor(inf);
Object object = constructor.newInstance(new ActionImpl());
return object;
}
}
進(jìn)一步加工,我們會(huì)發(fā)現(xiàn)這個(gè)只能實(shí)現(xiàn)測(cè)試時(shí)間的一個(gè)代理葛菇,而且還只是在move方法上
所以我們要定義一個(gè)專門(mén)處理事件的接口
import java.lang.reflect.Method;
public interface InvocationHandler {
public void invoke(Object o,Method m);//需要執(zhí)行的方法對(duì)象和方法本身(我們定義的函數(shù)沒(méi)有參數(shù)甘磨,暫且先不定義)
}
現(xiàn)在實(shí)現(xiàn)這個(gè)接口
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @Author: YangZaining
* @Date: Created in 23:26$ 2018/8/11$
*/
public class TalkHandler implements InvocationHandler{
@Override
public void invoke(Object o,Method m) {
System.out.println("我前進(jìn)了一步");
try {
m.invoke(o,new Object[]{});
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
System.out.println("我后退了一步");
}
}
為實(shí)現(xiàn)方便,就不特定判斷哪個(gè)方法是move,哪個(gè)方法是speak了眯停,下面是改進(jìn)后的代碼,這一塊做了幾個(gè)變動(dòng):1)生成代理對(duì)象構(gòu)造函數(shù)的參數(shù)改變济舆;2)在生成代碼塊里增加了Method方法傳值;3)以及下面構(gòu)造器傳值莺债;
為什么要怎么做呢滋觉?因?yàn)槲覀冃枰屛覀兩傻拇韺?duì)象按照我們所規(guī)定的方法進(jìn)行執(zhí)行,所以我們需要把我們的事件(InvocationHandler )進(jìn)行傳遞,而我們構(gòu)造的事件執(zhí)行需要被代理對(duì)象(Action)的方法齐邦,所以我們生成的代理對(duì)象(Test,名字無(wú)所謂只是對(duì)應(yīng)本例是這樣的)中反射進(jìn)行傳值椎侠,這一塊算是比較饒的地方,這里代碼語(yǔ)義容易混淆措拇,應(yīng)對(duì)照生成的代理對(duì)象理解,實(shí)在不明白就自己嘗試著修改我纪。
import javax.tools.*;
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;
/**
* @Author: YangZaining
* @Date: Created in 19:32$ 2018/8/11$
*/
public class Proxy {
public static Object newProxynewInstance(Class inf, InvocationHandler in) throws Exception {
Method[] methods = inf.getMethods();
String ln = "\r\n";
String s = "package cn.learn;" + ln +
"public class Test implements " + inf.getName() + " {" + ln +
ln +
" InvocationHandler in;" + ln +
" public Test(InvocationHandler in) {" + ln +//事件傳入
" this.in = in;" + ln +
" }\n";
for (Method m : methods) {
s += " @Override" + ln +
"public " + m.getGenericReturnType() + " " + m.getName() + "() {" + ln +
"try{"+ln+
" java.lang.reflect.Method ms = " + inf.getName() + ".class.getMethod(\"" + m.getName() + "\");" + ln +
"in.invoke(this,ms);" + ln +//事件方法的執(zhí)行
"}catch(Exception e){"+ln+
"e.printStackTrace();"+ln+
"}"+ln+
" }" + ln + ln;
}
s += "}" + ln;
String filePath = "D:\\src\\cn\\learn\\Test.java";
File fs = new File(filePath);
FileWriter fw = new FileWriter(fs);//寫(xiě)入文件里
fw.write(s);
fw.flush();
fw.close();
//編譯文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
System.out.println("CompilerName:" + compiler);//編譯器名字
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable it = fileManager.getJavaFileObjects(filePath);
JavaCompiler.CompilationTask t = compiler.getTask(null, fileManager, null, null, null, it);
t.call();
fileManager.close();
//加載到內(nèi)存并且生成新對(duì)象
URL[] urls = new URL[]{new URL("file:/" + "d:/src/")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class c = urlClassLoader.loadClass("cn.learn.Test");
System.out.println(c);
Constructor constructor = c.getConstructor(InvocationHandler.class);//發(fā)生改變,因?yàn)樯傻拇韺?duì)象傳值改變了
Object object = constructor.newInstance(in);//給生成的代理對(duì)象傳值
return object;
}
}
生成的代理對(duì)象文件內(nèi)容
package cn.learn;
public class Test implements cn.learn.Action {
InvocationHandler in;
public Test(InvocationHandler in) {
this.in = in;
}
@Override
public void speak() {
try {
java.lang.reflect.Method ms = cn.learn.Action.class.getMethod("speak");
in.invoke(this, ms);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void move() {
try {
java.lang.reflect.Method ms = cn.learn.Action.class.getMethod("move");
in.invoke(this, ms);
} catch (Exception e) {
e.printStackTrace();
}
}
}
這樣還不算完,這時(shí)我們執(zhí)行會(huì)陷入死循環(huán)
這里原因很簡(jiǎn)單宣羊,因?yàn)樯傻拇韺?duì)象我們把它自己傳給了InvocationHandler 執(zhí)行了其中的invoke方法璧诵,然后執(zhí)行的對(duì)象是生成的代理對(duì)象,然后又自己調(diào)用了自己的方法仇冯。
以move方法為例
首先
Test in.invoke(this,ms) --> TalkHandler void invoke(Object o,Method m)
由于this和Object o 是同一對(duì)象所以當(dāng)執(zhí)行TalkHandler m.invoke(o,new Object[]{});時(shí)相當(dāng)于調(diào)用了 Test中的public void speak() 方法之宿,然后speak方法里又重新來(lái)一次,就陷入了死循環(huán)苛坚。
所以做出如下修改
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @Author: YangZaining
* @Date: Created in 23:26$ 2018/8/11$
*/
public class TalkHandler implements InvocationHandler {
private Object obj;
public TalkHandler(Object obj) {//傳入所需要代理的對(duì)象
this.obj = obj;
}
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
@Override
public void invoke(Object o, Method m) {
System.out.println("我前進(jìn)了一步");
try {
m.invoke(obj, new Object[]{});//調(diào)用obj
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
System.out.println("我后退了一步");
}
}
然后進(jìn)行測(cè)試
/**
* @Author: YangZaining
* @Date: Created in 20:29$ 2018/8/11$
*/
public class Main {
public static void main(String[] args) {
try {
Action action = (Action) Proxy.newProxynewInstance(Action.class,new TalkHandler(new ActionImpl()));
action.move();
} catch (Exception e) {
e.printStackTrace();
}
}
}
output:
CompilerName:com.sun.tools.javac.api.JavacTool@85ede7b
class cn.learn.Test
我前進(jìn)了一步
移動(dòng)了比被。。泼舱。等缀。
我后退了一步
JAVA API中的Proxy和InvocationHandler實(shí)現(xiàn)了更多細(xì)節(jié),還需要閱讀文檔
我們能發(fā)現(xiàn)一個(gè)問(wèn)題既然InvocationHandler中的Object o 沒(méi)有用到娇昙,是不是可以去掉呢尺迂,答案是顯然的,只是當(dāng)我們要取生成代理類的某些方法或?qū)傩詴r(shí)冒掌,需要用到而已