疑问
在jdk动态代理中,还是以买房子举例:张三需要买房子,然后房产中介帮助张三找好的房源,但是实际上真正搜索到房源的是电脑,然后电脑返回一系列房源信息。我们可以这么去理解,电脑是我们新生成的代理类,那么它到底是如何一步一步演变而来的呢?
我们要怎么查看动态代理类?
try {//直接用流从jvm内存中写出来
byte[] bytes=ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Person.class});
FileOutputStream fos=new FileOutputStream("D://$Proxy0.class");
fos.write(bytes);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
这时候,D盘就出现了这个代理的文件,我们可以直接放入IDEA里查看。下面我们就来重点分析下这个$Proxy0.class的产生过程。
自定义动态代理
1.在买房子案例中,房产中介有实现InvocationHandler接口,这里我们直接自定义一个这样的接口,取名为FtInvocationHandler。
public interface FtInvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
2.在jdk中会执行Proxy.newProxyInstance生成代理类,这里我也不用Proxy,而是用自己定义的FtProxy。
/**
* 模仿jdk的Proxy
* 1.根据目标类提供的接口,动态的去拼接一个代理类,组成一个字符串
* 2.在类路径下创建一个$Proxy0.java文件,并将字符串写入到$Proxy0.java文件中
* 3.调用java的编译器,将$Proxy0.java转为$Proxy0.class
* 4.将编译生成的$Proxy0.class文件加载到jvm中去执行,最终返回一个动态代理实例
* 5.动态代理实例调用接口的方法,实际上就是调用房产中介中的invoke方法,然后invoke里面执行穿插逻辑,反射调用目标类的真实方法,这样就达到了动态代理的目的
*/
public class FtProxy {
//定义换行符,后面拼接java文件,每拼完一行都需要换行
private static final String ln="\r\n";
//模仿jdk中的newProxyInstance方法
public static Object newProxyInstance(FtClassLoader classLoader,Class<?>[] interfaces,FtInvocationHandler h){
try {
//1.动态生成代理类源代码的字符串形式
String src=generateSrc(interfaces);
//2.java文件输出磁盘,先拿到FtProxy的类路径
String fileP=FtProxy.class.getResource("").getPath();
//输出下类路径
System.out.println(fileP);
//在类路径下创建一个$Proxy0.java的文件,并将代理类源代码写入到该文件中
File file=new File(fileP +"$Proxy0.java");
FileWriter fw=new FileWriter(file);
fw.write(src);
fw.flush();
fw.close();
//3.调用java编译器把生成的.java文件编译成.class文件
JavaCompiler javaCompiler= ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager=javaCompiler.getStandardFileManager(null,null,null);
Iterable iterable=manager.getJavaFileObjects(file);
JavaCompiler.CompilationTask task=javaCompiler.getTask(null,manager,null,null,null,iterable);
task.call();
manager.close();
//4.把编译生成的.class加载到jvm中
Class proxyClass=classLoader.findClass("$Proxy0");
Constructor c=proxyClass.getConstructor(FtInvocationHandler.class);
//默默删除动态生成的.java
file.delete();
//5.返回字节码重组以后的新的代理对象
return c.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//该方法用来拼接动态代理类,相当于代理类的代码我们自己手动用字符串去拼接
private static String generateSrc(Class<?>[] interfaces){
//用StringBuffer拼java代码
StringBuffer sb=new StringBuffer();
//拼包名
sb.append("package com.taofut.sjms.proxy.custom;" +ln);
//拼导入得包
sb.append("import com.taofut.sjms.proxy.jdk1.Person;" +ln);
sb.append("import java.lang.reflect.Method;" +ln);
//拼代理类名
sb.append("public class $Proxy0 implements "+interfaces[0].getName() +"{" +ln);
//定义属性FtInvocationHandler h
sb.append("FtInvocationHandler h;" +ln);
//构造方法
sb.append("public $Proxy0(FtInvocationHandler h){" +ln);
sb.append("this.h = h;" +ln);
sb.append("}" +ln);
//拼实现接口的方法
for(Method m:interfaces[0].getMethods()){
sb.append("public "+m.getReturnType().getName()+ " " +m.getName() +"(){" +ln);
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(Throwable e){" +ln);
sb.append("e.printStackTrace();" +ln);
sb.append("}" +ln);
sb.append("}");
}
sb.append("}" +ln);
//最终返回一个代理类的源代码的字符串形式
return sb.toString();
}
}
3.自定义类加载器:
/**
* 手写一个类加载器
*/
public class FtClassLoader extends ClassLoader{
//定义类路劲下的文件
private File classPathFile;
public FtClassLoader(){
//获得类路径
String classPath=FtClassLoader.class.getResource("").getPath();
this.classPathFile=new File(classPath);
}
//1. 把$Proxy0.class文件转换为byte数组进行加载,最终得到类型为$Proxy0的Class对象
//2.Class反射调用构造方法,得到一个$Proxy0的实例,这个实例就是代理对象
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//类名
String className=FtClassLoader.class.getPackage().getName()+"."+name;
if(classPathFile != null){
//将传进来的类名$Proxy0转换成类路径下的$Proxy0.class文件
File classFile=new File(classPathFile,name.replaceAll("\\.","/")+".class");
if(classFile.exists()){
//如果$Proxy0.class文件存在,将$Proxy0.class文件转换成字节码数组
FileInputStream fis=null;
ByteArrayOutputStream bos=null;
try {
fis=new FileInputStream(classFile);
bos=new ByteArrayOutputStream();
byte[] bytes=new byte[1024];
int len;
while ((len=fis.read(bytes)) != -1){
bos.write(bytes,0,len);
}
//得到一个Class对象
return defineClass(className,bos.toByteArray(),0,bos.size());
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(null != fis){
fis.close();
}
if(null != bos){
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return null;
}
}
测试
1.首先人的统称:
public interface Person {
public void buyHouse();
}
2.有个叫张三的人,他要买房子:
public class ZhangSan implements Person {
@Override
public void buyHouse() {
System.out.println("我是张三:我要买一个120平的房子");
}
}
3.自定义的.房产中介出来了,实现了自定义的FtInvocationHandler 接口:
public class CustomHouseAgency implements FtInvocationHandler {
private ZhangSan zhangSan;
public CustomHouseAgency(ZhangSan zhangSan) {
this.zhangSan = zhangSan;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是自定义中介:麻烦张三提供房源要求给我");
method.invoke(zhangSan,args);
System.out.println("我是自定义中介:帮助张三找到合适房源");
return null;
}
}
4.开始找房源,买房子:
public class CustomProxyTest {
public static void main(String[] args) {
//张三出来
ZhangSan zhangSan=new ZhangSan();
//张三提供信息
Class clazz=zhangSan.getClass();
//房产中介出来
CustomHouseAgency houseAgency=new CustomHouseAgency(zhangSan);
//房产中介开始搜索房源
Person result=(Person) FtProxy.newProxyInstance(new FtClassLoader(),clazz.getInterfaces(),houseAgency);
//搜索房源成功
result.buyHouse();
}
//执行结果:
//我是自定义中介:麻烦张三提供房源要求给我
//我是张三:我要买一个120平的房子
//我是自定义中介:帮助张三找到合适房源
}
4.自定义生成的动态代理类如下:
package com.taofut.sjms.proxy.custom;
import com.taofut.sjms.proxy.custom.FtInvocationHandler;
import com.taofut.sjms.proxy.jdk1.Person;
import java.lang.reflect.Method;
public class $Proxy0 implements Person {
FtInvocationHandler h;
public $Proxy0(FtInvocationHandler var1) {
this.h = var1;
}
public void buyHouse() {
try {
Method var1 = Person.class.getMethod("buyHouse", new Class[0]);
this.h.invoke(this, var1, (Object[])null);
} catch (Throwable var2) {
var2.printStackTrace();
}
}
}
总结:以上测试完美的实现了自定义jdk的动态代理,只要思路清晰,其实原理也还好理解。这里我在总结一下具体过程:
1.根据目标类提供的接口Person,动态的去拼接一个代理类,组成一个字符串
2.在类路径下创建一个Proxy0.java文件,并将字符串写入到Proxy0.java文件中
3.调用java的编译器,将Proxy0.java转为Proxy0.class
4.将编译生成的$Proxy0.class文件加载到jvm中去执行,最终返回一个动态代理实例
4.1.类加载器会将.class文件转换成字节码数组,最终得到一个Class对象
4.2.Class对象反射调用构造方法生成一个代理实例
5.代理实例调用接口的方法,实际上就是调用房产中介中的invoke方法,然后invoke里面执行穿插逻辑,反射调用目标类的真实方法,这样就达到了动态代理的目的