实现思路
1: 定义一个字符串s
2:加载s利用流生成对应的java文件
3:通过类加载器加载java文件生成class文件
4:通过class生成代理对象
5:测试成功
我使用过jdk代理的场景
1:通过拦截request对象,代理其中的get参数的方法来过滤敏感词
2:通过阅读aop源码发现,底层用的也是动态代理(jdk,cglib)
3:jdk代理源码解析
jdk代理的使用
模拟之前我先聊一下jdk代理的使用,通过proxy.newProxyInstance(a,b,c)我们就可以生成一个代理对象了。
- 参数a含义是需要被代理对象的类加载器。
- 参数b含义是需要被代理对象所实现的所有接口。
- 参数c含义是需要一个实现InvocationHandler接口的类对象,
-
- invoke中的各个参数含义、
- proxy指生成的代理对象
- method指代理的具体方法。
在invoke里面我们可以实现我们自己的代理逻辑。这样我们就完成了对c对象的代理了,c对象实现了a接口。本文重点在于jdk代理的底层研究这里不做过多jdk代理的使用说明了
@Test
public void proxy(){
c target = new c();
System.out.println("target.hashCode: "+target.getClass());
a proxy = (a)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable {
System.out.println("jdk proxy");
System.out.println("proxy.hashCode: "+proxy.getClass());
return method.invoke(target);
}
});
proxy.say();
}
知道使用jdk动态代理了,我们这就来手写一个。
定义一个String s = “内容如下”
package com.zzh;
import proxy.a;
import proxy.myHandler;
import java.lang.reflect.Method;
public class myProxy implements a{
public myProxy(myHandler target){
this.h = target;
}
private myHandler h;
public String say(){
try{
Method method = Class.forName("proxy.a").getDeclaredMethod("say");
return (String)h.invoke(method);
}
catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
wc到底怎么定义能不能说清除呢。好了先定义包这一块吧,
String packageContent = "package com.zzh;" + line;
接着定义导入包这一块myHandler为我们自己写的一个接口,为的是模拟原版的InvocationHandler,需要导入java的包直接写名字就行了,导入自己定义的包把包名改掉就行了
String importContent = "import " + packageName + ";" + line
+ "import " + myHandler.getClass().getName() + ";" + line
+ "import java.lang.reflect.Method;" + line;
接着定义类名,需要变动的就是targetName,这个名字可以随意取
String classContent = "public class myProxy implements " + targetName;
接着定义构造函数
String constructContent = line + tab + "public myProxy" + "(" + "myHandler target" + "){" + line +
tab + "this.h = target;" + line + tab + "}" + line;
接着定义被代理对象中的所有方法,这里稍微有点长。我们先获取被代理对象中的所有方法
Method[] methods = target.getDeclaredMethods();
然后遍历这些方法,挨个定义,我们往字符串中传入一个方法需要Method method = Class.forName(“proxy.a”).getDeclaredMethod(“say”);然后再把这个方法才能被这个字符串类使用,而不是直接return (String)h.invoke(直接把method写在这);,鬼知道在编译期间method变成什么鬼名字了
for (Method method : methods) {
//方法的参数部分
String argsContent = "";
//方法的返回值
String returnArgs = "";
//获取代理方法的所有参数
Class<?>[] args = method.getParameterTypes();
//获取代理方法的返回值
String returnType = method.getReturnType().getSimpleName();
//遍历参数String p0 , String p1 这样拼接
for (Class arg : args) {
argsContent += arg.getSimpleName() + " p" + i + ",";
returnArgs += "p" + i + ",";
i++;
}
if (argsContent.length() > 0) {
//把最后多余的逗号去掉
argsContent = argsContent.substring(0, argsContent.lastIndexOf(",") - 1);
returnArgs = returnArgs.substring(0, returnArgs.lastIndexOf(",") - 1);
}
if (!returnType.equals("void")) {
methodContent += line + tab + "public " + returnType + " " + method.getName() + "(" + argsContent + ")"
+ "{" + line + tab + "try{" + line + "Method method = Class.forName(" + "\"" + target.getName() + "\"" + ").getDeclaredMethod(" + "\"" + method.getName() + "\"" + ");"
+ line + line + tab + "return (String)h.invoke(method);"
+ line + tab + "}"
+ line + "catch (Exception e) {\n" +
" e.printStackTrace();\n" +
" }" + line + "return null;" + line + tab + "}";
} else {
methodContent += line + tab + "public " + returnType + " " + method.getName() + "(" + argsContent + ")"
+ "{" + line + tab + "try{" + line + "Method method = Class.forName(" + "\"" + target.getName() + "\"" + ").getDeclaredMethod(" + "\"" + method.getName() + "\"" + ");"
+ line + line + tab + "(String)h.invoke(method);"
+ line + tab + "}"
+ line + "catch (Exception e) {\n" +
" e.printStackTrace();\n" +
" }" + line + "return null;" + line + tab + "}";
}
i++;
}
最后把之前定义的片段全部加起来
content = packageContent + importContent + classContent + "{" + line + tab + constructContent
+ line + tab + "private " + myHandlerName + " h;" + methodContent + line + "}";
ok到这一步一个对应java对象的字符串已经有了,接着就是想怎么把它变成 java文件,接而变成class文件,然后编译了,接着生效了。把字符串变成java文件好办,用流操作就行了。我们定义的文件的路径名需要和我们先前定义的包名保持一致
File file = new File("D:\\com\\zzh\\myProxy.java");
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(content);
fileWriter.flush();
fileWriter.close();
接着编译java,生成class文件
JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(null, null, null);
Iterable units = fileManager.getJavaFileObjects(file);
JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, null, null, null, units);
task.call();
fileManager.close();
最后加载我们的calss,变现成java对象,这个对象就是我们的代理对象了
URL[] urls = new URL[]{new URL("file:D:\\\\")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class<?> aClass = urlClassLoader.loadClass("com.zzh.myProxy");
Constructor<?> constructor = aClass.getConstructor(myHandler.getClass());
myProxy = constructor.newInstance(myHandler);
完整代码如下,测试没问题
package proxy;
import org.junit.Test;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
/**
* @author 张帅
* @class jdk代理实现
*/
public class proxyUtilsPlus {
/**
* @param target被代理的对象 myHandler被代理的逻辑
* @method
*/
public static Object proxy(Class target, myHadlerI myHandler) {
String line = "\n";
String tab = "\t";
int i = 0;
String content = "";
String packageName = target.getName();
String targetName = target.getSimpleName();
String packageContent = "package com.zzh;" + line;
String importContent = "import " + packageName + ";" + line
+ "import " + myHandler.getClass().getName() + ";" + line
+ "import java.lang.reflect.Method;" + line;
String classContent = "public class myProxy implements " + targetName;
String methodContent = "";
String constructContent = line + tab + "public myProxy" + "(" + "myHandler target" + "){" + line +
tab + "this.h = target;" + line + tab + "}" + line;
String myHandlerName = proxy.myHandler.class.getSimpleName();
Method[] methods = target.getDeclaredMethods();
for (Method method : methods) {
//方法的参数部分
String argsContent = "";
//方法的返回值
String returnArgs = "";
//获取代理方法的所有参数
Class<?>[] args = method.getParameterTypes();
//获取代理方法的返回值
String returnType = method.getReturnType().getSimpleName();
//遍历参数String p0 , String p1 这样拼接
for (Class arg : args) {
argsContent += arg.getSimpleName() + " p" + i + ",";
returnArgs += "p" + i + ",";
i++;
}
if (argsContent.length() > 0) {
//把最后多余的逗号去掉
argsContent = argsContent.substring(0, argsContent.lastIndexOf(",") - 1);
returnArgs = returnArgs.substring(0, returnArgs.lastIndexOf(",") - 1);
}
if (!returnType.equals("void")) {
methodContent += line + tab + "public " + returnType + " " + method.getName() + "(" + argsContent + ")"
+ "{" + line + tab + "try{" + line + "Method method = Class.forName(" + "\"" + target.getName() + "\"" + ").getDeclaredMethod(" + "\"" + method.getName() + "\"" + ");"
+ line + line + tab + "return (String)h.invoke(method);"
+ line + tab + "}"
+ line + "catch (Exception e) {\n" +
" e.printStackTrace();\n" +
" }" + line + "return null;" + line + tab + "}";
} else {
methodContent += line + tab + "public " + returnType + " " + method.getName() + "(" + argsContent + ")"
+ "{" + line + tab + "try{" + line + "Method method = Class.forName(" + "\"" + target.getName() + "\"" + ").getDeclaredMethod(" + "\"" + method.getName() + "\"" + ");"
+ line + line + tab + "(String)h.invoke(method);"
+ line + tab + "}"
+ line + "catch (Exception e) {\n" +
" e.printStackTrace();\n" +
" }" + line + "return null;" + line + tab + "}";
}
i++;
}
content = packageContent + importContent + classContent + "{" + line + tab + constructContent
+ line + tab + "private " + myHandlerName + " h;" + methodContent + line + "}";
Object myProxy = null;
try {
File file = new File("D:\\com\\zzh\\myProxy.java");
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(content);
fileWriter.flush();
fileWriter.close();
//生成class文件
JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(null, null, null);
Iterable units = fileManager.getJavaFileObjects(file);
JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, null, null, null, units);
task.call();
fileManager.close();
//加载class文件
URL[] urls = new URL[]{new URL("file:D:\\\\")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class<?> aClass = urlClassLoader.loadClass("com.zzh.myProxy");
Constructor<?> constructor = aClass.getConstructor(myHandler.getClass());
myProxy = constructor.newInstance(myHandler);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return myProxy;
}
@Test
public void test() {
a proxy = (a) proxy(a.class, new myHandler(new c()));
System.out.println("method return: " + proxy.say());
}
}