手写动态代理

eve2333 发布于 4 小时前 2 次阅读


这是一个新的工程。我们有一个接口叫 `MyInterface`,里面定义了三个方法:`func1()`、`func2()` 和 `func3()`。

现在我们有一个需求:每个方法被调用时,都要打印出自己的方法名。最简单的做法是写一个实现类 `NameImpl`:

```java
public class NameImpl implements MyInterface {
    @Override
    public void func1() {
        System.out.println("func1");
    }

    @Override
    public void func2() {
        System.out.println("func2");
    }

    @Override
    public void func3() {
        System.out.println("func3");
    }
}
```

但很快需求变了:不仅要打印方法名,还要打印方法名的长度。于是我们再写一个新类 `NameAndLengthImpl`,同样实现 `MyInterface`,但内部逻辑变成了:

public class NameAndLengthImpl implements MyInterface {

    @Override
    public void func1() {
        String methodName = "func1";
        System.out.println(methodName);
        System.out.println(methodName.length());
    }

    @Override
    public void func2() {
        String methodName = "func2";
        System.out.println(methodName);
        System.out.println(methodName.length());
    }

    @Override
    public void func3() {
        String methodName = "func3";
        System.out.println(methodName);
        System.out.println(methodName.length());
    }
}

问题来了:如果我们需要为不同的行为(比如打印时间、加日志、计数等)创建多个实现类,代码会大量重复,且无法动态扩展。我们能否**在运行时动态生成类**?

答案是可以的。Java 允许我们在程序运行期间生成 Java 源码文件,编译成 `.class` 文件,并加载到 JVM 中使用。

为此,我们创建一个工厂类 `MyInterfaceFactory`,用来专门生成实现了 `MyInterface` 的代理类。

第一步:生成 Java 源文件

我们写一个方法 `createJavaFile()`,它将拼接出一个完整的 Java 类源码字符串,并写入文件:

public class MyInterfaceFactory {

    public static File createJavaFile() throws IOException {
        String context = "package tech.insight.proxy;\n" +
            "\n" +
            "public class NameImpl implements MyInterface {\n" +
            "    @Override\n" +
            "    public void func1() {\n" +
            "        System.out.println(\"func1\");\n" +
            "    }\n" +
            "\n" +
            "    @Override\n" +
            "    public void func2() {\n" +
            "        System.out.println(\"func2\");\n" +
            "    }\n" +
            "\n" +
            "    @Override\n" +
            "    public void func3() {\n" +
            "        System.out.println(\"func3\");\n" +
            "    }\n" +
            "}\n";

        File javaFile = new File("NameImpl.java");
        Files.write(javaFile.toPath(), context);
        return javaFile;
    }

    public static void main(String[] args) throw Exception {
        createJavaFile();
    }
}

执行后,确实另外生成了一个 `NameImpl.java的文件。java虚拟机不能直接对完成

第二步:编译 Java 文件

Java 提供了 `JavaCompiler` 工具,可以在运行时动态编译 Java 文件:你可以看看Compiler文件

```java
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int result = compiler.run(null, null, null, javaFile.getPath());
```

我们将编译后的 `.class` 文件放在项目的 `target/classes` 目录下,确保类路径可访问。

Compiler.compile(javaFile);
得到了.class文件

接着作者通过自设getClassName(){

        return "MyInterface$proxy" + count.incrementAndGet();

}

这样获得某些内容,如何进一步动态呢?

functionBody(Stirng methodName){

        return "System.out .println" +methodName +"\");";

}

这样我们可以进一步

String func1Body = functionBody("func1");
String func2Body = functionBody("func2");
String func3Body = functionBody("func3");
String context = "package tech.insight.proxy;\n" +
    "\n" +
    "public class " + className + " implements MyInterface {\n" +
    "    @Override\n" +
    "    public void func1() {\n" +
    "        " + func1Body + "\n" +
    "    }\n" +
    "\n" +
    "    @Override\n" +
    "    public void func2() {\n" +
    "        " + func2Body + "\n" +
    "    }\n" +
    "\n" +
    "    @Override\n" +
    "    public void func3() {\n" +
    "        " + func3Body + "\n" +
    "    }\n" +
    "}";

第三步:加载并实例化类

private static MyInterface newInstance(String className) throws Exception {
    Class<?> aClass = MyInterfaceFactory.class.getClassLoader().loadClass(className);
    Constructor<?> constructor = aClass.getConstructor();
    MyInterface proxy = (MyInterface) constructor.newInstance();
    return proxy;
}

public static MyInterface createProxyObject() throws Exception {
    String className = getClassName();
    File javaFile = createJavaFile(className);
    Compiler.compile(javaFile);
    return newInstance("tech.insight.proxy." + className);
}

创建一个main方法,把上面的所有暂时统统私有,创建了本来在虚拟机中的没有的类;

public class Main {

    public static void main(String[] args) throws Exception {
        MyInterface proxyObject = MyInterfaceFactory.createProxyObject();
        proxyObject.func1();
        proxyObject.func2();
        proxyObject.func3();
    }
}使用类加载器将编译好的字节码加载进虚拟机:

```java
URLClassLoader classLoader = URLClassLoader.newInstance(new URL[]{new File("target/classes").toURI().toURL()});
Class<?> clazz = classLoader.loadClass("MyInterface$Proxy1");
Constructor<?> constructor = clazz.getConstructor();
Object instance = constructor.newInstance();
MyInterface proxy = (MyInterface) instance;
```

这样我们就得到了一个在运行时动态创建的对象。

第四步:让类名和方法体动态化

目前类名和方法体都是硬编码的。我们可以优化:

- 类名通过原子计数器自增,避免冲突,如 `MyInterface$Proxy1`、`MyInterface$Proxy2`。
- 方法体不再固定,而是由外部传入一个“处理器”来决定生成什么代码。

我们定义一个接口 `MyHandler`:

```java
public interface MyHandler {
    String getMethodBody(String methodName);
}
```

这个接口的作用是:给定方法名,返回该方法体内要执行的 Java 代码字符串。

 但是如何进一步的动态呢?就是把getClassName或者 functionBody 函数也动态,我们新建一个MyHandler的文件,获得一个方法名,返回一个函数体:动态代理的三个参数,加载器,类,handler都齐了

这样我们为原先的两个函数配置传递方法体:

public class MyInterfaceFactory {

    private static final AtomicInteger count = new AtomicInteger();

    private static File createJavaFile(String className, MyHandler handler) throws IOException {
        String func1Body = handler.functionBody("func1");
        String func2Body = handler.functionBody("func2");
        String func3Body = handler.functionBody("func3");
        String context = "package tech.insight.proxy;\n" +
            "\n" +
            "public class " + className + " implements MyInterface {\n" +
            "    @Override\n" +
            "    public void func1() {\n" +



...............................................

选择删掉我们的pxory了

private static MyInterface newInstance(String className) throws Exception {
    Class<?> aClass = MyInterfaceFactory.class.getClassLoader().loadClass(className);
    Constructor<?> constructor = aClass.getConstructor();
    MyInterface proxy = (MyInterface) constructor.newInstance();
    return proxy;
}

public static MyInterface createProxyObject(MyHandler myHandler) throws Exception {
    String className = getClassName();
    File javaFile = createJavaFile(className, myHandler);
    Compiler.compile(javaFile);
    return newInstance("tech.insight.proxy." + className);
}

然后在生成类时,把 `MyHandler` 传进去,用它来生成每种方法的函数体:

```java
for (Method method : MyInterface.class.getDeclaredMethods()) {
    String body = handler.getMethodBody(method.getName());
    content.append("    public void ").append(method.getName())
           .append("() { ").append(body).append(" }\n");
}
```

例如,我们可以实现一个只打印方法名的处理器:

```java
public class PrintMethodNameHandler implements MyHandler {
    @Override
    public String getMethodBody(String methodName) {
        return "System.out.println(\"" + methodName + "\");";
    }
}

也可以实现一个先打印 `"1"` 再打印方法名的处理器:

public class PrintOneThenNameHandler implements MyHandler {
    @Override
    public String getMethodBody(String methodName) {
        StringBuilder sb = new StringBuilder();
        sb.append("System.out.println(\"1\");");
        sb.append("System.out.println(\"").append(methodName).append("\");");
        return sb.toString();
    }
}

static class PrintFunctionName1 implements MyHandler {

    @Override
    public String functionBody(String methodName) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("System.out.println();")
                   .append(" System.out.println(\"" + methodName + "\");");
        return stringBuilder.toString();
    }
}

所有的对象都是代理拿到的

P 功能(前置/后置增强)

我们现在想实现类似 Spring AOP 的功能:在方法执行前后自动添加日志。

我们需要生成这样的代码:

```java
public void func1() {
    System.out.println("before");
    myInterface.func1();
    System.out.println("after");
}
```

但这里有个问题:生成的类需要持有一个真实的 `MyInterface` 实例,并调用它的方法。

因此,`MyHandler` 需要能设置目标对象。我们扩展接口:避免空指针,这样

```java
public interface MyHandler {
    String getMethodBody(String methodName);
    
    default void setProxy(Object proxy) {}
}
```

public interface MyHandler {
    String functionBody(String methodName);

    default void setProxy(MyInterface proxy) {
    }
}

默认方法表示大多数处理器不需要设置代理对象。

只有像 `LogHandler` 这样的处理器才需要:

```java
public class LogHandler implements MyHandler {
    private final MyInterface target;

    public LogHandler(MyInterface target) {
        this.target = target;
    }

    @Override
    public String getMethodBody(String methodName) {
        return "System.out.println(\"before\");" +
               "myInterface." + methodName + "();" +
               "System.out.println(\"after\");";
    }

    @Override
    public void setProxy(Object proxy) {
        try {
            Field field = proxy.getClass().getDeclaredField("myInterface");
            field.setAccessible(true);
            field.set(proxy, target);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
```

static class LogHandler implements MyHandler {

    MyInterface myInterface;

    public LogHandler(MyInterface myInterface) {
        this.myInterface = myInterface;
    }

    @Override
    public void setProxy(MyInterface proxy) {
        Class<?> extends MyInterface aClass = proxy.getClass();
        Field field = null;
        try {
            field = aClass.getDeclaredField("myInterface");
            field.setAccessible(true);
            field.set(proxy, myInterface);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

我们在生成的类中添加字段:

```java
content.append("    private MyInterface myInterface;\n");
```

private static MyInterface newInstance(String className, MyHandler handler) throws Exception {
    Class<?> aClass = MyInterfaceFactory.class.getClassLoader().loadClass(className);
    Constructor<?> constructor = aClass.getConstructor();
    MyInterface proxy = (MyInterface) constructor.newInstance();
    handler.setProxy(proxy);
    return proxy;
}

public static MyInterface createProxyObject(MyHandler myHandler) throws Exception {
    String className = getClassName();
    File javaFile = createJavaFile(className, myHandler);
    Compiler.compile(javaFile);
    return newInstance("tech.insight.proxy." + className, myHandler);
}

并在 `newInstance()` 时调用 `handler.setProxy(proxy)` 完成赋值。

proxyObject= MyInterfaceFactory.createProxyObject(new LogHandler(proxyObject));

最终,我们成功实现了:

- 动态生成实现了 `MyInterface` 的类;
- 方法体内容由 `MyHandler` 控制;
- 支持在方法前后插入逻辑,实现 AOP 增强。

这就是我们写的动态代理 那么再回顾一下 我们是如何写我们的动态代理的呢 首先我们生成了一个文字版的java类 然后我们在运行时把它编译 再用通过class loader去把这个编译出来的字节码 文件加载到虚拟机中 然后再通过反射去创建这个类 然后再调用这个类对应的方法 这就是我们写的动态代理

虽然功能完整,但它有明显缺陷:

1. 只能代理 `MyInterface`,无法泛化;
2. 依赖磁盘 IO 和编译过程,性能差;
3. 生成的是 Java 源码,而不是直接操作字节码。

而 JDK 自带的动态代理(`java.lang.reflect.Proxy`)和 CGLIB 则更高效,它们可以直接在内存中生成字节码,无需写文件和编译。

Spring 的事务管理底层就是基于动态代理。当你在一个加了 `@Transactional` 的方法上执行数据库操作时,Spring 会生成一个代理对象,在方法执行前后自动开启事务、提交或回滚:

```java
try {
    开启事务();
    target.insert(); // 真实业务逻辑
    提交事务();
} catch (RuntimeException e) {
    回滚事务();
    throw e;
}
```

理解这一点后,就能明白为什么有些情况下事务会失效:

- 如果你捕获了异常并“吞掉”,代理层无法感知异常,就不会回滚;
- 如果抛出的是 checked exception(非 `RuntimeException`),默认不会触发回滚,除非显式配置;
- 如果自调用(this.method()),绕过了代理对象,AOP 不生效。

思考题:  
你能基于以上思路,自己实现一个支持 `before`、`after`、`around` 的通用增强处理器吗? ​