Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I want to be able to write an aspect to detect when I am casting something in one of my 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!
  }
}

How do you write a pointcut to express the casting operation, not just for Foo class, but for any class in org.mypackage?

Background: So Hibernate 5 + Spring Data JPA requires casting entities with inheritance:

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);
  ...
}

...which, on a large code base, is scary to consider because there are a lot of places this could break. So if it's possible to write an aspect/pointcut to at least detect it, then we can at least log it and also identify places in our tests where the issue needs addressing.

This technique is recommended in this question How to convert a Hibernate proxy to a real entity object by @Vlad-Mihalcea.

like image 830
JJ Zabkar Avatar asked Nov 21 '19 21:11

JJ Zabkar


1 Answers

For both Spring AOP and AspectJ the answer is: You cannot intercept casts. There is no pointcut as fine-granular as this.

Background: It also would not make sense because some casts are not even present in byte code because the compiler optimises them away. Others are represented by a checkcast in the byte code. Look at this example:

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 {}
}

If you disassemble it via javap -c Dummy.class, you get:

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
}

See? The cast from long to int is not even there even though it is necessary for compilation in the source code. So how would you intercept it? Sorry for this deep dive, the actual answer was in the first paragraph.


Update:

  • Given the fact that you cannot be sure the cast is even present in the byte code, something you can do is write a simple test scanning your source code and writing a report during each Maven or Gradle build.

  • Or if you want to get more sophisticated, you can write a compiler plugin which would inspect the AST (abstract syntax tree) representing the parsed source code and then issue compiler warnings if you found something you need reported.

  • With AspectJ (not Spring AOP) you could intercept calls to Hibernate.unproxy(), but what you need to detect is the absence of those calls, so you cannot write a pointcut for something that is not even there.

like image 181
kriegaex Avatar answered Sep 27 '22 17:09

kriegaex