很多java优秀框架都应用了java里的高级特性,比如java动态代理等。
为了更好地方便大家理解 我后续将会写的文章内容,咱们先来理解下这些特性。
秉承先简后难的原则,这篇文章,先简单手写一个java动态代理。
这是什么意思? 买房子找中介,那么中介就是代理人,卖房人就是被代理的人。
java代理中涉及两个名词:代理对象(增强后的对象)以及目标对象(被代理的对象)
先了解一下静态代理的实现方式
一 java静态代理
1 新建一个 目标对象的接口类 ObjService,接口方法 echoUserName
/**
* 目标对象接口。
*/
public interface ObjService {
void echoUserName(String name);
}
2 新建一个 实现类,ObjServiceImpl
/**
* 目标对象实现类。
*/
public class ObjServiceImpl implements ObjService{
@Override
public void echoUserName(String name) {
System.out.println( "hello,"+name);
}
}
3 目标对象有了,接下来,就是创建一个 同样实现ObjService接口的 代理类 LogProxyServiceImpl,在构造方法中传入要代理的目标对象。在方法中加入增强的代码逻辑。
/**
* 日志代理类
*/
public class LogProxyServiceImpl implements ObjService{
private ObjService objService;
/**
* 将 目标对象 通过构造函数的方式 加到代理类里
* @param objService
*/
public LogProxyServiceImpl(ObjService objService){
this.objService = objService;
}
@Override
public void echoUserName(String name) {
//增强的逻辑
System.out.println("log log log start $#34;);
objService.echoUserName(name);
System.out.println("log log log end $#34;);
}
}
4 测试一下结果
public class ProxyTest {
public static void main(String[] args) {
// 创建目标对象
ObjService objService = new ObjServiceImpl();
//为对象加代理类
ObjService logProxy = new LogProxyServiceImpl(objService);
logProxy.echoUserName("小码哥");
}
}
结果如下。代理成功了。
可以看到 每实现一个对象的代理类,都得新建一个,如果需要记录执行时间,那只能再加一个 执行时间的代理类。所以一个对象有多个代理类,多个对象呢,类就泛滥了。
public static void main(String[] args) {
// 创建目标对象
ObjService objService = new ObjServiceImpl();
//为对象加日志代理类
ObjService logProxy = new LogProxyServiceImpl(objService);
//再加入时间代理类
ObjService timeProxy = new TimeProxyServiceImpl(logProxy);
timeProxy.echoUserName("小码哥");
}
二 java动态代理
大家可以想象一下,执行一个java的方法,需要做哪些工作呢?
第一,需要新建一个java文件;第二,编译成.class文件 ;第三,新建获取对象;最后运行。
动态生成java代理类,也是需要这么做的。接下来,咱们就来通过动态代理的方式来实现上述ObjService目标对象的日志代理类。
下面是重点,大家打起十二分精神。
1 新建一个 动态代理生成类DynamicProxy, 方法名称 newProxyInstance
public static Object newProxyInstance(Object target) throws Exception {
// 获取目标对象的接口,接口可以多个。这里简单点,取第一个
Class targetInfo = target.getClass().getInterfaces()[0];
// (1)通过目标对象 构建代理类文件,返回文件地址
File file = getProxyFile(target,targetInfo);
//(2)编译上面的java文件
compile(file);
//(3)通过类加载器加载类,并构建代理对象
URL[] urls = new URL[]{new URL("file:d:\\\\tmp\\\\")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class cl = urlClassLoader.loadClass("com.example.proxy.$Proxy");
Constructor constructor = cl.getConstructor(targetInfo);
Object result = constructor.newInstance(target);
return result;
}
2 通过目标对象 构建代理类文件,返回文件地址。getProxyFile方法的代码如下,里面是通过反射的方式获取的。
private static File getProxyFile(Object target,Class targetInfo) throws Exception{
String content = ""; //java文件的内容。
String packageContent = "package com.example.proxy;"; //文件包名
//接口名 ObjService
String targetInfoName = targetInfo.getSimpleName();
// import ObjService 路径
String importContent = "import " + targetInfo.getName() + ";";
//声明代理类名$Proxy,并实现 ObjService接口
String classContent = "public class $Proxy implements " + targetInfoName + "{";
// 声明目标对象 private ObjService target
String fieldContent = "private " + targetInfoName + " target;";
//构造方法. public $Proxy(ObjService target){ this.target = target}
String constructContent = "public $Proxy(" + targetInfoName + " target){" +
"this.target = target;" +
"}";
//方法内容
String methodsContent = "";
//获取ObjService接口的所有方法
Method[] methods = targetInfo.getDeclaredMethods();
for(Method method:methods){
//方法名 echoUserName
String methodName = method.getName();
//返回类型 void
Class returnType = method.getReturnType();
//参数类型 这里是String
Class<?>[] paramTypes = method.getParameterTypes();
String argsContent = "";
String argsNames = "";
int i=0;
for(Class<?> paramType:paramTypes){
String simpleName = paramType.getSimpleName();
argsContent += simpleName+" a"+i+",";
argsNames+= "a"+i+",";
i++;
}
if(argsContent.length()>0){
//去掉最后的 逗号
argsContent = argsContent.substring(0,argsContent.length()-1);
argsNames = argsNames.substring(0,argsNames.length()-1);
}
//生成文件
methodsContent += "public "+returnType+" "+methodName+"("+argsContent+"){"
+"System.out.println(\"log log log start $$\");"
+"target."+methodName+"("+argsNames+");"
+"System.out.println(\"log log log end $$\");"
+"}";
}
//把所有的组件内容 合起来
content+=packageContent+importContent+classContent+fieldContent+constructContent+methodsContent+"}";
//生成 $Proxy.java文件。
File file = new File("D:\\tmp\\com\\example\\proxy\\$Proxy.java");
if(!file.exists()){
file.createNewFile();
}
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(content);
fileWriter.flush();
fileWriter.close();
return file;
}
3 编译java文件。代码如下。这里用系统自带的,不太建议使用,cglib 有更好的方式,大家可以先了解下这个。
private static void compile(File file) throws Exception{
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null,null,null);
Iterable units = fileManager.getJavaFileObjects(file);
JavaCompiler.CompilationTask task = compiler.getTask(null,fileManager,null,null,null,units);
task.call();
fileManager.close();
}
经过上面两步,我们这里生产的是这样的文件。
4 通过类加载器 URLClassLoader 加载 class文件。
URL[] urls = new URL[]{new URL("file:d:\\\\tmp\\\\")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class cl = urlClassLoader.loadClass("com.example.proxy.$Proxy");
Constructor constructor = cl.getConstructor(targetInfo);
Object result = constructor.newInstance(target);
return result;
5 修改测试代码如下。
public class ProxyTest {
public static void main(String[] args) throws Exception{
// 创建目标对象
ObjService objService = new ObjServiceImpl();
// //为对象加日志代理类
// ObjService logProxy = new LogProxyServiceImpl(objService);
// //再加入时间代理类
// ObjService timeProxy = new TimeProxyServiceImpl(logProxy);
//使用动态代理生成类.
ObjService proxy = (ObjService) DynamicProxy.newProxyInstance(objService);
proxy.echoUserName("小码哥");
}
}
运行结果如下。符合预期。
log log log start $$
hello,小码哥
log log log end $$
上面我们尽量用最简单的方式 写了一个 java动态代理类。真正用于生产实践 里面还有很多难点需要考虑处理。但万变不离其宗。
喜欢的小伙伴多多 关注 点赞 转发。后续会提供更多的技术文章。也希望大家多给点意见。咱们共同进步。