手写基于jdk的动态代理
满足代理模式应用场景的三个必要条件(穷取法)
1.两个角色:执行者、被代理对象
2.注重过程,必须要做,被代理对象没时间做或者不想做
3.执行者必须拿到被代理对象的引用
动态代理深究其归根到底最的只有一件事:字节码重组
专业的事交给专业的事做
买车票–>黄牛(代理对象)
租房子–>中介
打官司–>律师
基于jdk的动态代理
//接口
package com.csj.jdk;
public interface Person {
/**
* 寻找真爱
*/
void findLove();
}
//被代理类
package com.csj.jdk;
public class Coder implements Person{
@Override
public void findLove() {
System.out.println("寻找程序猿❥(^_-)");
}
}
package com.csj.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 媒婆
*/
public class MeiPo implements InvocationHandler {
private Person person;
public Person getInstance(Person person) {
this.person = person;
return (Person) Proxy.newProxyInstance(person.getClass().getClassLoader(), person.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("媒婆来了,要获取相应的资料信息");
this.person.findLove();
System.out.println("获取资料信息1完毕");
return null;
}
}
//testMain
package com.csj.jdk;
public class JdkProxyTest {
public static void main(String[] args) {
Person coder = new Coder();
Person proxy = new MeiPo().getInstance(coder);
proxy.findLove();
}
}
执行结果
可以看到这个jdk动态代理已经帮我们创建好了代理类,且类名为$Proxy0
我们可以把其字节码文件打印出来查看生成的代理类信息
在运行测试方法追加
byte[] $Proxy0s = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{
Person.class
});
try {
FileOutputStream fos = new FileOutputStream("$Proxy0.class");
fos.write($Proxy0s);
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
因为idea是自带反编译的,我们可以双击查看动态代理生成的类信息,或者是通过一些其他的工具。
只观看我们业务相关的核心代码,可以看到动态代理生成的字节码继承了Proxy实现了Person接口且Method m3是我们要使用的findLove方法,在静态代码块的将其初始化,当执行findLove的时候执行的是我们动态代理$Proxy0的findLove方法,继而追看到我们的public final void findLove()方法(因为不允许其子类修改,final修饰)
super.h.invoke(this, m3, (Object[])null);
super就是我们的Proxy,因为我们在业务处理类Meipo中调用了
所以我们已经把h传入进来给Proxy类的h(InvocationHandler)参数
当其调用invoke方法的时候就是我们相关在Meipo中的
即动态代理方法执行完毕
手写jdk的动态代理
原理
原理:
- 拿到被代理类的引用,然后获取它的接口(getInstances)
- jdk代理重新生成一个类,同时实现我们给的代理对象实现的接口
- 把被代理对象的引用也拿到了
- 重新动态生成一个class字节码
- 然后编译
在之前我们用到了InvocationHandler,Proxy,还有ClassLoader,所以这次我们自己来手动实现这些业务相关使用的类,来一探究竟底层做了什么
新的目录结构
//person接口
public interface Person {
/**
* 寻找真爱
*/
void findLove();
}
//接口其子类coder
class Coder implements Person {
@Override
public void findLove() {
System.out.println("Manual-->寻找程序猿❥(^_-)");
}
}
/**
* 业务处理类实现了我们自己写的接口ManualInvocationHandler
*/
public class ManualMeipo implements ManualInvocationHandler{
private Person person;
public Person getInstance(Person person) throws Exception {
this.person = person;
return (Person) ManualProxy.newProxyInstance( new ManualClassLoader(), person.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
System.out.println("ManualMeipo-->");
this.person.findLove();
System.out.println("findLove匹配完毕--》");
return null;
}
}
//模仿者InvocationHandler定义个接口方法
public interface ManualInvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args);
}
/**
* custom Proxy 自己定义的实例生成newProxyInstance(自己的ClassLoader,接口(简单实现),自己的InvocationHandler)
*/
public class ManualProxy {
//换行
private static final String LN = "\r\n";
public static Object newProxyInstance(ManualClassLoader loader,
Class<?>[] interfaces,
ManualInvocationHandler h) throws Exception {
try {
//1.生成源代码
String proxyCode = generateCode(interfaces);
//2.将生成的源代码输出到磁盘,保存为.java文件
String path = ManualProxy.class.getResource("").getPath();
File file = new File(path + "$Proxy0.java");
FileWriter fw = new FileWriter(file);
fw.write(proxyCode);
fw.flush();
fw.close();
//3.编译源代码,并且生成.class文件
//java代理提供的
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> it = manager.getJavaFileObjects(file);
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, it);
task.call();
manager.close();
// file.delete();
//4.将class文件中的内容,动态加载到jvm中来
//5.返回被代理后的代理对象
Class<?> proxyClass = loader.findClass("$Proxy0");
Constructor<?> c = proxyClass.getConstructor(ManualInvocationHandler.class);
file.delete();
return c.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static String generateCode(Class<?>[] interfaces) {
StringBuffer sb = new StringBuffer();
sb.append("package com.csj.manual;" + LN);
sb.append("import java.lang.reflect.Proxy;" + LN);
sb.append("import java.lang.reflect.Method;" + LN);
sb.append("public final class $Proxy0 implements " + interfaces[0].getName() + " {" + LN);
sb.append("ManualInvocationHandler h;" + LN);
sb.append("public $Proxy0 (ManualInvocationHandler h) {" + LN);
sb.append("this.h = h;" + LN);
sb.append("}" + LN);
for (Method m : interfaces[0].getMethods()) {
// public final void findLove()为了简单,不做复杂的处理,只是为了生成findLove业务方法
sb.append("public final " + m.getReturnType().getName() + " " + m.getName() + "(){");
sb.append("try{" + LN);
sb.append("Method m = " + interfaces[0].getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{});" + LN);
sb.append("this.h.invoke(this,m,null);" + LN);
sb.append("}catch(Exception e){" + LN);
sb.append("}" + LN);
}
sb.append("}}");
return sb.toString();
}
}
//自己的classLoader
/**
* simple custom classLoader
*/
//代码生成,编译,重新动态load到jvm
public class ManualClassLoader extends ClassLoader{
private File baseDir;
public ManualClassLoader(){
String basePath = ManualClassLoader.class.getResource("").getPath();
this.baseDir = new File(basePath);
}
//找到之后加载到我们的jvm中来
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//扎到我们的class(包名+类名)
String className = ManualClassLoader.class.getPackage().getName() + "." + name;
if(baseDir != null) {
File classFile = new File(baseDir, name.replaceAll("\\.", "/")+ ".class");
if (classFile.exists()) {
FileInputStream in = null;
ByteArrayOutputStream baos = null;
byte[] buff = new byte[1024];
try {
baos =new ByteArrayOutputStream();
in = new FileInputStream(classFile);
int len;
while ((len = in.read(buff)) != -1) {
baos.write(buff, 0, len);
}
//load进jvm
return defineClass(className, baos.toByteArray(), 0, baos.size());
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
in.close();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
classFile.delete();
}
}
}
return null;
}
}
//测试方法
/**
* 代理关注的是过程
*/
class ManualJdkTest {
public static void main(String[] args) throws Exception {
new ManualMeipo().getInstance(new Coder()).findLove();
}
}
整个的实现过程就是这样。
动态代理的时候动态生成一个类实现了我们定义的接口和继承了Proxy这个类。
同时Proxy有其InvocationHandler的引用命名为h,在我们Proxy.newInstance的时候会创建这个代理类根据动态生成模板进行生成,根据其java高级api进行编译生成class文件,通过其classLoader的findClass进行查找相应的.class文件进行装载数据defineClass方法来loader进我们的jvm,同时在实行完成之后删除动态代理生成的.java,.class文件。
PS:动态代理方便了我们的使用,让我们更专注的进行某一模块的业务开发。提高了解耦的能力,动态代理使用的频繁会生成很多的类信息,loader进我们jvm的栈中,堆中,增长我们的内存空间,不过这些并不会让我们杜绝选择动态代理,因为有其相应的垃圾回收机制,和其可自定义配置的内存空间。
如果一个项目的栈空间消耗很大的话(栈内存分配也达到了机器的上限),也可以适当的对一些不必要的模块的动态代理给取消,来提升相应的空间进行优化。