在编程体系中,AOP切面技术,框架底层源码都离不开动态代理的影子。那么究竟动态代码的功能是如何实现的呢?今天本篇就此问题展开动态代理底层源码逻辑的讲解。
思路分析:
我们知道一个类,要想让它真正的参与运算,需要经过如下几个步骤:
a.编写.java文件;
b.编译成字节码.class文件;
c.使用类加载器加载到jvm中。
以上步骤便是我们平时编写类到产生作用的过程,基于此逻辑原理,同样的我们也可以按此步骤去动态的创建我们的类,也就是本篇要讲到的代理类。实现逻辑思路如下:
a.依据真实对象,动态的拼接.java代码的内容;
b.将.java代码以字符流的形式写入到磁盘;
c.使用类加载器加载到jvm中(此处编译和类加载器同步执行)。
好了,废话不多说,直接上代码:
代码语言:javascript复制package com.luban.proxy;
import com.sun.jndi.toolkit.url.UrlUtil;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class ProxyUtil {
/**
* @Author GONGWENXUE
* @Description //TODO 创建动态代理类
* @version: v1.8.0
* @Date 11:25 2020/8/12
* @param target 真实对象
**/
public static Object newInstance(Object target){
Object proxy=null;
//获取真实对象的接口(默认取第一个)
Class targetInf = target.getClass().getInterfaces()[0];
//获取方法集合
Method methods[] =targetInf.getDeclaredMethods();
String line="n";
String tab ="t";
//接口名
String infName = targetInf.getSimpleName();
//动态拼接.java文件字符串内容
String content ="";
//包名
String packageContent = "package com.google;" line;
//需要导入的包
String importContent = "import " targetInf.getName() ";" line;
//类名
String clazzFirstLineContent = "public class $Proxy implements " infName "{" line;
//字段
String filedContent =tab "private " infName " target;" line;
//构造方法内容
String constructorContent =tab "public $Proxy (" infName " target){" line
tab tab "this.target =target;"
line tab "}" line;
//方法
String methodContent = "";
for (Method method : methods) {
String returnTypeName = method.getReturnType().getSimpleName();
String methodName =method.getName();
// Sting.class String.class
Class args[] = method.getParameterTypes();
String argsContent = "";
String paramsContent="";
int flag =0;
for (Class arg : args) {
String temp = arg.getSimpleName();
//String
//String p0,Sting p1,
argsContent =temp " p" flag ",";
paramsContent ="p" flag ",";
flag ;
}
if (argsContent.length()>0){
argsContent=argsContent.substring(0,argsContent.lastIndexOf(",")-1);
paramsContent=paramsContent.substring(0,paramsContent.lastIndexOf(",")-1);
}
methodContent =tab "public " returnTypeName " " methodName "(" argsContent ") {" line
tab tab "System.out.println("log");" line
tab tab "target." methodName "(" paramsContent ");" line
tab "}" line;
}
//拼接完整.java内容
content = packageContent importContent clazzFirstLineContent filedContent constructorContent methodContent "}";
//创建文件对象,准备写入到磁盘
File file = new File("d:\com\google\$Proxy.java");
try {
if (!file.exists()) {
file.createNewFile();
}
//将.java文件写入到文件中
FileWriter fw = new FileWriter(file);
fw.write(content);
fw.flush();
fw.close();
//编译.java文件 JavaCompiler是java的动态编译类
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//使用动态编译创建文件管理器
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable units = fileMgr.getJavaFileObjects(file);
//添加编译文件的任务
JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
//执行编译任务(得到.class文件)
t.call();
fileMgr.close();
//打开网络连接
URL[] urls = new URL[]{new URL("file:D:\\")};
//使用类加载器加载文件
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class clazz = urlClassLoader.loadClass("com.google.$Proxy");
//得到构造器
Constructor constructor = clazz.getConstructor(targetInf);
//生成代理类
proxy = constructor.newInstance(target);
//clazz.newInstance();
//Class.forName()
}catch (Exception e){
e.printStackTrace();
}
return proxy;
}
}
综上代码中比较难理解的是动态编译类JavaCompiler 和文件管理器StandardJavaFileManager。不了解的可以简单理解成这是java的固定语法规则,使用他们就能动态的编译任何的.java文件。
真实对象的写法:
接口:
代码语言:javascript复制package com.luban.dao;
public interface UserDao {
public void query();
public void query(String p);
}
实现类:
代码语言:javascript复制public class UserDaoImpl implements UserDao{
public void query(){
System.out.println("假装查询数据库");
}
}
测试生成代理类:
代码语言:javascript复制public class Test {
public static void main(String[] args) {
UserDaoproxy = (UserDao) ProxyUtil.newInstance(new UserDaoImpl());
proxy.query();
}
}
运行结果:
运行测试类会你会发现在执行真实对象的打印之前,打印了代理对象的数据,这是由于我们在动态拼接方式methodContent时,先执行了自己的增强逻辑就那些打印,后调用真实的方法。
生成的代理对象:
虽然以上的代理实现了动态代理的功能,但仔细观察会发现还是有些问题的:增强逻辑是写死的打印,并未能动态增强。那么怎么解决这个问题呢?还有JDK的动态代理底层原理也是通过这种方式实现的吗?
本公众后后期为您揭晓答案!敬请关注!谢谢!