爲其餘對象提供一種代理以控制對這個對象的訪問。java
最重要的三要素:app
動態代理經過反射機制動態地生成代理者的對象,咱們在code的時候沒必要要關心代理誰,代理誰咱們將在執行階段來決定。JDK爲咱們已提供了很方便的動態代理接口InvocationHandler
。jvm
先來定義一個場景,我要去申請專利,這個時候就能夠經過專利代理人來進行專利申請。那麼我就是被代理者,專利代理人就是代理。ide
業務接口測試
public interface Person {
void apply();
}
複製代碼
業務實現類ui
public class Apkcore implements Person {
@Override
public void apply() {
System.out.println("apkcore開始申請專利");
}
}
複製代碼
業務處理類(專利代理人)this
public class PatentAgent implements InvocationHandler {
private Person mPersion;
public Object getInstance(Person persion) {
this.mPersion = persion;
Class clazz = persion.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(mPersion, args);
after();
return result;
}
private void after() {
System.out.println("申請提交成功了");
}
private void before() {
System.out.println("準備申請專利的材料");
}
}
複製代碼
測試類spa
public static void main(String[] args) {
PatentAgent patentAgent = new PatentAgent();
Person person = (Person) patentAgent.getInstance(new Apkcore());
person.apply();
}
複製代碼
能夠看到生成的結果爲:.net
準備申請專利的材料
apkcore開始申請專利
申請提交成功了
複製代碼
到此,使用JDK給咱們提供的InvocationHandler實現動態代理就已經完成了。代理
咱們能夠打印一下測試中獲得的person對象
PatentAgent patentAgent = new PatentAgent();
Person person = (Person) patentAgent.getInstance(new Apkcore());
System.out.println(person.getClass());
person.apply();
複製代碼
能夠看到輸出結果爲class com.sun.proxy.$Proxy0
咱們在生成的class中,是不能找到這個$Proxy0
的,它只是JVM在內存中生成的動態代理類。
那麼咱們能夠把這個類用一個文件寫出來。
//獲取字節碼內容
byte[] data = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Persion.class});
FileOutputStream os = null;
try {
String path = "$Proxy0.class";
File file = new File(path);
if (file.exists()) {
file.delete();
}
os = new FileOutputStream("$Proxy0.class");
os.write(data);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != os) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
複製代碼
能夠在根目錄下生成$Proxy0.class
文件,打開這個文件
public final class $Proxy0 extends Proxy implements Person {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
//這是person.apply方法調用的地方
public final void apply() throws {
try {
//是調用了父類的h的invoke方法
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
//m3就是apply方法,另外三個都是Object的方法,能夠忽略不看
m3 = Class.forName("com.apkcore.proxy.blog.Person").getMethod("apply");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
複製代碼
能夠看到,主要就看apply方法,這是咱們調用的方法super.h在父類Proxy中
protected InvocationHandler h;
複製代碼
而咱們在PatentAgent中,使用Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this)
這個this就是PatentAgent
,它是個InvocationHandler
的實現類,因此能夠知道,super.h.invoke
其實就是調用了PatentAgent
中的invoke
方法,而invoke的第二個參數m3就是m3 = Class.forName("com.apkcore.proxy.blog.Person").getMethod("apply");
,因此咱們在PatentAgent中調用method.invoke(mPersion, args)
就至關於mPerson.apply方法。
動態代理的過程:
- Proxy經過傳遞給它的參數(interface/invocationHandler)生成代理類$Proxy0
- Proxy經過傳遞給它的參數(ClassLoade)來加載生成的代理類$Proxy0的字節碼文件
咱們先看源碼中InvocationHandler
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
複製代碼
因此咱們能夠模仿着實現本身的InvocationHandler
public interface MyInvocationHandler {
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
複製代碼
那麼對應的,咱們的動態代理PatentAgent要改成
public class MyPatentAgent implements MyInvocationHandler {
private Person mPersion;
public Object getInstance(Person persion) {
this.mPersion = persion;
Class clazz = persion.getClass();
return MyProxy.newProxyInstance(new MyClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(mPersion, args);
after();
return result;
}
private void after() {
System.out.println("申請提交成功了");
}
private void before() {
System.out.println("準備申請專利的材料");
}
}
複製代碼
其中的ClassLoader與Proxy都使用本身的而不是JDK提供的。
public class MyProxy {
public static Object newProxyInstance(MyClassLoader classLoader, Class[] interfaces, MyPatentAgent h) {
return null;
}
}
public class MyClassLoader extends ClassLoader{
}
複製代碼
由上面的知識咱們能夠知道,咱們要先在運行時生成一個$Proxy0這樣的java文件
public class MyProxy {
private static final String ln = "\r\n";
public static Object newProxyInstance(MyClassLoader classLoader, Class[] interfaces, MyPatentAgent h) {
if (h == null) {
throw new NullPointerException();
}
//1.生成源代碼$MyProxy0這個源文件
//爲了實現方便,默認只代理一個接口
String proxySrc = generateSrc(interfaces[0]);
//2.保存到文件
String filePath = MyProxy.class.getResource("").getPath();
File f = new File(filePath + "$MyProxy0.java");
FileWriter fw = null;
try {
fw = new FileWriter(f);
fw.write(proxySrc);
fw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != fw) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
private static String generateSrc(Class interfaces) {
Method[] methods = interfaces.getMethods();
StringBuilder sb = new StringBuilder();
sb.append("package ")
.append(MyProxy.class.getPackage().getName()).append(";").append(ln)
.append("import java.lang.reflect.Method;").append(ln)
.append("public class $MyProxy0 implements ").append(interfaces.getName()).append("{").append(ln)
.append("MyInvocationHandler h;").append(ln)
.append("public $MyProxy0(MyInvocationHandler h) {").append(ln)
.append("this.h = h;").append(ln)
.append("}").append(ln);
for (Method method : methods) {
sb.append("public ")
//使用最簡單的不傳參的方法
.append(method.getReturnType().getName()).append(" ").append(method.getName())
.append(" () {").append(ln)
.append("try {").append(ln)
.append("Method m = ").append(interfaces.getName()).append(".class.getMethod(\"").append(method.getName()).append("\",new Class[]{});").append(ln)
.append("this.h.invoke(this,m,null);").append(ln)
.append("}catch(Throwable throwable){ }").append(ln)
.append("}").append(ln);
}
sb.append("}");
return sb.toString();
}
}
複製代碼
append手寫代碼的時候,能夠邊寫邊生成,查看哪裏寫得有問題
調用
public static void main(String[] args) {
MyPatentAgent patentAgent = new MyPatentAgent();
// Person person = (Person)
patentAgent.getInstance(new Apkcore());
// System.out.println(person.getClass());
// person.apply();
}
}
複製代碼
來進行文件的生成,生成的文件在
而後把這個java文件複製到咱們的工程中,能夠查看咱們是否寫漏或者寫錯。
生成的$MyProxy0文件內容以下
package com.apkcore.proxy.blog.custom;
import java.lang.reflect.Method;
public class $MyProxy0 implements com.apkcore.proxy.blog.Person {
MyInvocationHandler h;
public $MyProxy0(MyInvocationHandler h) {
this.h = h;
}
public void apply() {
try {
Method m = com.apkcore.proxy.blog.Person.class.getMethod("apply", new Class[]{});
this.h.invoke(this, m, null);
} catch (Throwable throwable) {
}
}
}
複製代碼
JavaCompiler:jdk用來編譯java的源程式,提供在運行期動態編譯java代碼爲字節碼的功能
//3.編譯源代碼,並生成class文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
Iterable iterable = manager.getJavaFileObjects(f);
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
task.call();
manager.close();
複製代碼
運行測試代碼,就會在編譯目錄下生成一個class文件。如圖
如今咱們已經獲得了咱們所須要的class文件,只須要把它加載到jvm中便可
//4.將class文件中的內容動態加載到JVM中
//5.返回被代理的代理對象
Class proxyClass = classLoader.findClass("$MyProxy0");
Constructor constructor = proxyClass.getConstructor(MyInvocationHandler.class);
return constructor.newInstance(h);
複製代碼
整個MyProxy的代碼以下
public class MyProxy {
private static final String ln = "\r\n";
public static Object newProxyInstance(MyClassLoader classLoader, Class[] interfaces, MyPatentAgent h) {
if (h == null) {
throw new NullPointerException();
}
//1.生成源代碼$MyProxy0這個源文件
//爲了實現方便,默認只代理一個接口
String proxySrc = generateSrc(interfaces[0]);
//2.保存到文件
String filePath = MyProxy.class.getResource("").getPath();
File f = new File(filePath + "$MyProxy0.java");
FileWriter fw = null;
try {
fw = new FileWriter(f);
fw.write(proxySrc);
fw.flush();
//3.編譯源代碼,並生成class文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
Iterable iterable = manager.getJavaFileObjects(f);
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
task.call();
manager.close();
//4.將class文件中的內容動態加載到JVM中
//5.返回被代理的代理對象
Class proxyClass = classLoader.findClass("$MyProxy0");
Constructor constructor = proxyClass.getConstructor(MyInvocationHandler.class);
return constructor.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != fw) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
private static String generateSrc(Class interfaces) {
Method[] methods = interfaces.getMethods();
StringBuilder sb = new StringBuilder();
sb.append("package ")
.append(MyProxy.class.getPackage().getName()).append(";").append(ln)
.append("import java.lang.reflect.Method;").append(ln)
.append("public class $MyProxy0 implements ").append(interfaces.getName()).append("{").append(ln)
.append("MyInvocationHandler h;").append(ln)
.append("public $MyProxy0(MyInvocationHandler h) {").append(ln)
.append("this.h = h;").append(ln)
.append("}").append(ln);
for (Method method : methods) {
sb.append("public ")
//使用最簡單的不傳參的方法
.append(method.getReturnType().getName()).append(" ").append(method.getName())
.append(" () {").append(ln)
.append("try {").append(ln)
.append("Method m = ").append(interfaces.getName()).append(".class.getMethod(\"").append(method.getName()).append("\",new Class[]{});").append(ln)
.append("this.h.invoke(this,m,null);").append(ln)
.append("}catch(Throwable throwable){ }").append(ln)
.append("}").append(ln);
}
sb.append("}");
return sb.toString();
}
}
複製代碼
能夠知道,咱們如今只須要在MyClassLoader中findClasss進行加載
public class MyClassLoader extends ClassLoader{
private File baseDir;
public MyClassLoader() {
this.baseDir = new File(MyClassLoader.class.getResource("").getPath());
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String className = MyClassLoader.class.getPackage().getName()+"."+name;
if (null!=baseDir) {
File classFile = new File(baseDir,name.replaceAll("\\.","/")+".class");
if (classFile.exists()){
FileInputStream in = null;
ByteArrayOutputStream out = null;
try {
//先把class文件轉化爲字節流
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);
}
//使用defineClass把字節流傳jvm中
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();
}
}
}
}
}
return super.findClass(name);
}
}
複製代碼
運行測試代碼
public static void main(String[] args) {
MyPatentAgent patentAgent = new MyPatentAgent();
Person person = (Person)
patentAgent.getInstance(new Apkcore());
System.out.println(person.getClass());
person.apply();
}
class com.apkcore.proxy.blog.custom.$MyProxy0 準備申請專利的材料 apkcore開始申請專利 申請提交成功了 複製代碼
這時咱們發現,在咱們的編譯目錄下是有$MyProxy0.,java和.class兩個文件存在的,咱們也能夠在使用完以後增長一個刪除語句,分別在MyProxy和MyClassLoader的finally語句中加入文件delete()方法就好了
經過手寫了JDK的動態代理,對代理模式的動態代理認識又加深了一步,原理就是拿到被代理對象的引用,而後獲取它的接口,jdk代理從新生成一個類,同時實現咱們給的代理對象所實現的接口,把被代理的對象引用也拿到了,從新動態生成一個class字節碼,而後編譯。
參考
下面是個人公衆號,歡迎你們關注我