动态代理: 由程序帮助我们生成类,不需要我们自己手动写。
常规实现方式
有一个接口:
package proxy;
public interface MyInterface {
void fun1();
void fun2();
void fun3();
}
需求 1 :添加一个类,实现接口,方法主体是 打印方法名。
于是你写了 NameImpl
类:
package proxy;
public class NameImpl implements MyInterface{
@Override
public void fun1() {
System.out.println("fun1");
}
@Override
public void fun2() {
System.out.println("fun2");
}
@Override
public void fun3() {
System.out.println("fun3");
}
}
需求 2 :添加一个类,实现接口,方法主体是 打印 方法名的长度。
于是你写了 NameLengthImpl
类:
package proxy;
public class NameLengthImpl implements MyInterface{
@Override
public void fun1() {
String methodName = "fun1";
System.out.println(methodName);
System.out.println(methodName.length());
}
@Override
public void fun2() {
String methodName = "fun2";
System.out.println(methodName);
System.out.println(methodName.length());
}
@Override
public 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{
@Override
public void fun1() {
System.out.println("fun1");
}
@Override
public void fun2() {
System.out.println("fun2");
}
@Override
public 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的myInterface
default 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{
@Override
public String methodBody(String methodName) {
return "System.out.println(\"" + methodName + "\");";
}
}
// 打印1,打印方法名
private static class MyHandleImpl2 implements MyHandle{
@Override
public 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{
@Override
public 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;
}
@Override
public 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 底层是动态代理(反射生成)。
事务失效的本质: 动态代理失效了