【手写系列】手写动态代理
动态代理: 由程序帮助我们生成类,不需要我们自己手动写。
常规实现方式
有一个接口:
package proxy;public interface MyInterface {void fun1();void fun2();void fun3();}
需求 1 :添加一个类,实现接口,方法主体是 打印方法名。
于是你写了 NameImpl
类:
package proxy;public class NameImpl implements MyInterface{@Overridepublic void fun1() {System.out.println("fun1");}@Overridepublic void fun2() {System.out.println("fun2");}@Overridepublic void fun3() {System.out.println("fun3");}
}
需求 2 :添加一个类,实现接口,方法主体是 打印 方法名的长度。
于是你写了 NameLengthImpl
类:
package proxy;public class NameLengthImpl implements MyInterface{@Overridepublic void fun1() {String methodName = "fun1";System.out.println(methodName);System.out.println(methodName.length());}@Overridepublic void fun2() {String methodName = "fun2";System.out.println(methodName);System.out.println(methodName.length());}@Overridepublic void fun3() {String methodName = "fun3";System.out.println(methodName);System.out.println(methodName.length());}
}
是否可以 动态 创建类?
思考一个问题:上述的需求都是根据 方法的名字 做一些操作,那我们是否可以动态地创建一些类?
可以的。无非就是用程序 写一个文件,使之符合 Java 文件规范。
生成 .java 文件
public class MyInterfaceFactory {public static File createJavaFile() throws IOException {String context = """package proxy;public class NameImpl implements MyInterface{@Overridepublic void fun1() {System.out.println("fun1");}@Overridepublic void fun2() {System.out.println("fun2");}@Overridepublic void fun3() {System.out.println("fun3");}}""";File javaFile = new File("NameImpl.java");Files.writeString(javaFile.toPath(),context);return javaFile;}public static void main(String[] args) throws IOException {createJavaFile();}
}
运行完之后,出现 。
我们成功用代码,动态地生成了一个类。
问题 1:虚拟机只能运行 .class
文件,生成的 .java
文件 JVM 如何运行?
- 在程序运行的时候,编译这个文件。
问题 2:一个类中,哪些东西是动态的,哪些是是不变的?
- 假设 类中的包名,实现的接口 是不变的。
- 类名、函数体 是动态的。
package proxy;
public class Compiler {public static void compile(File javaFile) {JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();try(StandardJavaFileManager fileManager =compiler.getStandardFileManager(null,null,null)){Iterable<? extends JavaFileObject> compilerUnits =fileManager.getJavaFileObjectsFromFiles(List.of(javaFile));// 设置编译选项 (指定编译路径)List<String> options = Arrays.asList("-d", "./out/production/LXDemo");JavaCompiler.CompilationTask compilerTask =compiler.getTask(null, fileManager, null, options, null, compilerUnits);Boolean success = compilerTask.call();if (success){System.out.println("编译成功");}else {System.out.println("编译失败");}} catch (IOException e) {throw new RuntimeException(e);}}
}
上述代码作用:编译 .java
文件 到 classPath
路径。
类名、函数体如何是动态的呢?
package proxy;
public class MyInterfaceFactory {public static File createJavaFile() throws IOException {// 动态获取类名String className = getClassName();// 动态获取函数体String funBody1 = functionBody("fun1");String funBody2 = functionBody("fun2");String funBody3 = functionBody("fun3");String context = "package proxy;\n" +"\n" +"public class "+ className +" implements MyInterface{\n" +" @Override\n" +" public void fun1() {\n" +" "+funBody1+"\n" +" }\n" +"\n" +" @Override\n" +" public void fun2() {\n" +" "+funBody2+"\n" +" }\n" +"\n" +" @Override\n" +" public void fun3() {\n" +" "+funBody3+"\n" +" }\n" +"}\n";File javaFile = new File(className + ".java");Files.writeString(javaFile.toPath(),context);return javaFile;}private static String functionBody(String methodName) {return "System.out.println(\"" + methodName +"\");";}private static final AtomicInteger count = new AtomicInteger(0);private static String getClassName() {return "MyInterface$proxy" + count.incrementAndGet();}public static void main(String[] args) throws IOException {File javaFile = createJavaFile();Compiler.compile(javaFile);}
}
编译完之后,我们是不是可以使用 类加载器 ,把 .class
文件加载到虚拟机中。
// 加载类、反射构造对象
private static MyInterface newInstance(String className) throws Exception {Class<?> aClass = MyInterfaceFactory.class.getClassLoader().loadClass(className);Constructor<?> constructor = aClass.getConstructor();MyInterface instance = (MyInterface)constructor.newInstance();return instance;
}
程序运行时,编译.java
文件
就这样我们 使用代码,创建了一个类,编译它,并加载到 JVM 中,获取对象。
我们创建了一个原本 .java
中没有的类,是程序在运行时,帮助我们创建的。
package proxy;
public class MyInterfaceFactory {private static File createJavaFile(String className) throws IOException {String funBody1 = functionBody("fun1");String funBody2 = functionBody("fun2");String funBody3 = functionBody("fun3");String context = "package proxy;\n" +"\n" +"public class "+ className +" implements MyInterface{\n" +" @Override\n" +" public void fun1() {\n" +" "+funBody1+"\n" +" }\n" +"\n" +" @Override\n" +" public void fun2() {\n" +" "+funBody2+"\n" +" }\n" +"\n" +" @Override\n" +" public void fun3() {\n" +" "+funBody3+"\n" +" }\n" +"}\n";File javaFile = new File(className + ".java");Files.writeString(javaFile.toPath(),context);return javaFile;}private static String functionBody(String methodName) {return "System.out.println(\"" + methodName +"\");";}private static final AtomicInteger count = new AtomicInteger(0);private static String getClassName() {return "MyInterface$proxy" + count.incrementAndGet();}private static MyInterface newInstance(String className) throws Exception {Class<?> aClass = MyInterfaceFactory.class.getClassLoader().loadClass(className);Constructor<?> constructor = aClass.getConstructor();MyInterface instance = (MyInterface)constructor.newInstance();return instance;}public static MyInterface createProxyObject() throws Exception {String className = getClassName();// 创建 .java文件File javaFile = createJavaFile(className);// 编译Compiler.compile(javaFile);// 加载、创建对象return newInstance("proxy." + className);}
}public class Main {public static void main(String[] args) throws Exception {MyInterface obj = MyInterfaceFactory.createProxyObject();obj.fun1();obj.fun2();obj.fun3();}
}
问题:functionBody()
返回的 方法体 是固定的,不能满足我们需求
- 写一个 MyHandle 接口,具体实现交给使用者。
- 让使用者 自己传入 方法体。
动态生成 方法体
现在我不固定方法体,而是让 开发者 实现。
动态生成的类 要求实现:
- 打印 方法名
System.out.println("fun1");
- 打印 1;打印 方法名
System.out.println(1);
System.out.println("fun1");
- 包装一个 MyInterface
MyInterface myInterface;public void fun1() {System.out.println("before");myInterface.fun1();System.out.println("after");
}
最终的实现:
public interface MyHandle {// 根据方法名,返回方法体String methodBody(String methodName);// set proxy的myInterfacedefault void setMyInterface(MyInterface proxy) throws Exception {}
}public class MyInterfaceFactory {private static File createJavaFile(String className,MyHandle myHandle) throws IOException {String funBody1 = myHandle.methodBody("fun1");String funBody2 = myHandle.methodBody("fun2");String funBody3 = myHandle.methodBody("fun3");String context = "package proxy;\n" +"\n" +"public class "+ className +" implements MyInterface{\n" +" MyInterface myInterface;\n"+" @Override\n" +" public void fun1() {\n" +" "+funBody1+"\n" +" }\n" +"\n" +" @Override\n" +" public void fun2() {\n" +" "+funBody2+"\n" +" }\n" +"\n" +" @Override\n" +" public void fun3() {\n" +" "+funBody3+"\n" +" }\n" +"}\n";File javaFile = new File(className + ".java");Files.writeString(javaFile.toPath(),context);return javaFile;}private static final AtomicInteger count = new AtomicInteger(0);private static String getClassName() {return "MyInterface$proxy" + count.incrementAndGet();}private static MyInterface newInstance(String className,MyHandle myHandle) throws Exception {Class<?> aClass = MyInterfaceFactory.class.getClassLoader().loadClass(className);Constructor<?> constructor = aClass.getConstructor();MyInterface proxy = (MyInterface)constructor.newInstance();// 给对象赋值myHandle.setMyInterface(proxy);return proxy;}public static MyInterface createProxyObject(MyHandle myHandle) throws Exception {String className = getClassName();File javaFile = createJavaFile(className,myHandle);Compiler.compile(javaFile);return newInstance("proxy." + className,myHandle);}
}
public class Main {public static void main(String[] args) throws Exception {MyInterface proxy1 = MyInterfaceFactory.createProxyObject(new MyHandleImpl1());proxy1.fun1();proxy1.fun2();proxy1.fun3();System.out.println("-----");MyInterface proxy2 = MyInterfaceFactory.createProxyObject(new MyHandleImpl2());proxy2.fun1();proxy2.fun2();proxy2.fun3();System.out.println("-----");MyInterface logProxy = MyInterfaceFactory.createProxyObject(new LogHandle(proxy1));logProxy.fun1();logProxy.fun2();logProxy.fun3();}// 打印方法名private static class MyHandleImpl1 implements MyHandle{@Overridepublic String methodBody(String methodName) {return "System.out.println(\"" + methodName + "\");";}}// 打印1,打印方法名private static class MyHandleImpl2 implements MyHandle{@Overridepublic String methodBody(String methodName) {StringBuilder stringBuilder = new StringBuilder();stringBuilder.append("System.out.println(1);").append("\n").append("System.out.println(\"" + methodName + "\");");return stringBuilder.toString();}}// 包装myInterface,private static class LogHandle implements MyHandle{@Overridepublic String methodBody(String methodName) {StringBuilder stringBuilder = new StringBuilder();stringBuilder.append("System.out.println(\"before\");\n").append("myInterface."+ methodName +"();\n").append("System.out.println(\"after\");");return stringBuilder.toString();}private final MyInterface myInterface;public LogHandle(MyInterface myInterface){this.myInterface = myInterface;}@Overridepublic void setMyInterface(MyInterface proxy) throws Exception {Class<? extends MyInterface> aClass = proxy.getClass();Field field = aClass.getDeclaredField("myInterface");field.set(proxy,myInterface);}}}
// out:
// 编译成功
// fun1
// fun2
// fun3
// -----
// 编译成功
// 1
// fun1
// 1
// fun2
// 1
// fun3
// -----
// 编译成功
// before
// fun1
// after
// before
// fun2
// after
// before
// fun3
// after
解释:因为并不是所有的 proxy
需要 setInterface。所以 setMyInterface()
为 default
。
只有需要 setInterface 的 Handle 才去实现。
public interface MyHandle {String methodBody(String methodName);default void setMyInterface(MyInterface proxy) throws Exception {}
}
自己写的类:
动态代理生成的类:
编译完之后的字节码文件:
总结
我们是如何动态生成类的呢?
生成 .java 文件 -> 编译为字节码文件 -> ClassLoader 加载到 JVM -> 反射创建对象
不足:
- 生成的类只能是 实现 MyInterface 接口的,不能动态地 传递接口。
- 生成 .java 文件,再编译为 字节码文件。 而不是直接生成字节码文件。
Spring 中的事务
Spring 的事务底层是 AOP,AOP 底层是动态代理(反射生成)。
事务失效的本质: 动态代理失效了