AOP 检测所有 Class 转换为 Hibernate.unproxy()

AOP To Detect All Class Casts For Hibernate.unproxy()

我希望能够编写一个方面来检测我何时在我的 org.mypackage classes 中投射某些东西。

package org.mypackage;

class Foo {
  public static void main(String[] args) {
    Bar casted = (Bar) args[0]; // want to detect this casting action!
  }
}

如何编写切入点来表达转换操作,不仅针对 Foo class,而且针对 org.mypackage 中的任何 class?

背景:因此 Hibernate 5 + Spring Data JPA 需要使用继承来转换实体:

if (isInstanceOfMyEntity(someEntity)) {
  // formerly, this was sufficient:
  // MyEntity myEntity = (MyEntity) someEntity;
  // now, this is required *everywhere* it is casted:
  MyEntity myEntity = (MyEntity) Hibernate.unproxy(someEntity);
  ...
}

...在大型代码库中,考虑起来很可怕,因为有很多地方可能会中断。因此,如果可以编写一个 aspect/pointcut 来至少检测到它,那么我们至少可以记录它并确定我们的测试中需要解决问题的地方。

@Vlad-Mihalcea 在这个问题 How to convert a Hibernate proxy to a real entity object 中推荐了这种技术。

对于 Spring AOP 和 AspectJ,答案是:你不能拦截转换。没有像这样细粒度的切入点。

背景:这也没有意义,因为一些强制转换甚至不存在于字节码中,因为编译器将它们优化掉了。其他的在字节码中用一个checkcast表示。看这个例子:

package de.scrum_master.Whosebug.q58984334;

public class Dummy {
  public void foo() {
    int i = (int) 42L;
    System.out.println(i);
  }

  public void bar() {
    Base base = new Sub();
    Sub sub = (Sub) base;
    System.out.println(sub);
  }

  static class Base {}
  static class Sub extends Base {}
}

如果你通过javap -c Dummy.class反汇编它,你会得到:

Compiled from "Dummy.java"
public class de.scrum_master.Whosebug.q58984334.Dummy {
  public de.scrum_master.Whosebug.q58984334.Dummy();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public void foo();
    Code:
       0: bipush        42
       2: istore_1
       3: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
       6: iload_1
       7: invokevirtual #21                 // Method java/io/PrintStream.println:(I)V
      10: return

  public void bar();
    Code:
       0: new           #30                 // class de/scrum_master/Whosebug/q58984334/Dummy$Sub
       3: dup
       4: invokespecial #32                 // Method de/scrum_master/Whosebug/q58984334/Dummy$Sub."<init>":()V
       7: astore_1
       8: aload_1
       9: checkcast     #30                 // class de/scrum_master/Whosebug/q58984334/Dummy$Sub
      12: astore_2
      13: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
      16: aload_2
      17: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      20: return
}

看到了吗?从 longint 的转换甚至不存在,即使在源代码中编译是必要的。那么你将如何拦截它呢?很抱歉这次深入探讨,实际答案在第一段中。


更新:

  • 鉴于您无法确定转换是否存在于字节码中这一事实,您可以做的是编写一个简单的测试来扫描您的源代码并在每个 Maven 或 Gradle build.

  • 或者,如果你想变得更复杂,你可以编写一个编译器插件,它会检查表示已解析源代码的 AST(抽象语法树),然后在你发现一些你认为不正确的内容时发出编译器警告需要举报

  • 使用 AspectJ(不是 Spring AOP)你可以拦截对 Hibernate.unproxy() 的调用,但是你需要检测的是这些调用的缺失,所以你不能写一个甚至不存在的东西的切入点。