AOP 检测 Hibernate.unproxy() 的所有类转换

JJ *_*kar 6 java aop hibernate spring-aop spring-data-jpa

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

package org.mypackage;

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

你如何编写一个切入点来表达强制转换操作,不仅仅是为了Foo类,而是对于org.mypackage.

背景:所以 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);
  ...
}
Run Code Online (Sandbox Code Playgroud)

...在大型代码库中,考虑这点很可怕,因为这可能会破坏很多地方。因此,如果可以编写一个切面/切入点来至少检测它,那么我们至少可以记录它并确定测试中需要解决问题的位置。

@Vlad-Mihalcea在这个问题如何将 Hibernate 代理转换为真实的实体对象中推荐了这种技术。

kri*_*aex 5

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

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

package de.scrum_master.stackoverflow.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 {}
}
Run Code Online (Sandbox Code Playgroud)

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

Compiled from "Dummy.java"
public class de.scrum_master.stackoverflow.q58984334.Dummy {
  public de.scrum_master.stackoverflow.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/stackoverflow/q58984334/Dummy$Sub
       3: dup
       4: invokespecial #32                 // Method de/scrum_master/stackoverflow/q58984334/Dummy$Sub."<init>":()V
       7: astore_1
       8: aload_1
       9: checkcast     #30                 // class de/scrum_master/stackoverflow/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
}
Run Code Online (Sandbox Code Playgroud)

看?即使需要在源代码中进行编译,从longto的强制转换int也不存在。那么如何拦截呢?很抱歉这次深入探讨,实际答案在第一段中。


更新:

  • 鉴于您无法确定强制转换是否存在于字节码中,您可以做的是编写一个简单的测试,扫描您的源代码并在每个 Maven 或 Gradle 构建期间编写报告。

  • 或者,如果您想变得更复杂,您可以编写一个编译器插件,该插件将检查表示已解析源代码的 AST(抽象语法树),然后在发现需要报告的内容时发出编译器警告。

  • 使用 AspectJ(不是 Spring AOP),您可以拦截对 的调用Hibernate.unproxy(),但是您需要检测的是这些调用是否缺失,因此您无法为甚至不存在的内容编写切入点。