为其余对象提供一种代理以控制对这个对象的访问。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字节码,而后编译。
参考
下面是个人公众号,欢迎你们关注我