Search code examples
aopaspectj

AspectJ: ClassCastException when trying to intercept object creation


I'm trying to intercept object creation in legacy code to return another object.

My sample code:

public class ObjectCreationTest {

interface A {
    String say();
}

public static class MyImpl implements A {

    @Override
    public String say() {
        return "MyImpl";
    }
}

public static class YourImpl implements A {

    @Override
    public String say() {
        return "YourImpl";
    }
}

public static void main(String[] args) {
    A obj = new MyImpl();
    System.out.println(obj.getClass());
    System.out.println(obj.say());

}
}

@Aspect
public class MyAspect {

@Around(value = "call(com.leon.test.ObjectCreationTest$MyImpl.new(..))")
public Object initAdvice(ProceedingJoinPoint pjp) throws Throwable {
    return new ObjectCreationTest.YourImpl();
}

}

However, I got:

Exception in thread "main" java.lang.ClassCastException: com.leon.test.ObjectCreationTest$YourImpl cannot be cast to com.leon.test.ObjectCreationTest$MyImpl
    at com.leon.test.ObjectCreationTest.main(ObjectCreationTest.java)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

Only when I change YourImpl to extends from MyImpl, it works. (but this is not what I expected)

Just wondering is there anything wrong or just not doable?

Thanks


Solution

  • Appears to be impossible I'm afraid. The cast seems to be happening between object creation and assignment to the right hand side of the line A obj = new MyImpl();, since returning null works 'fine'. That would also explain why extending fixes the problem, since the right side will then still have the correct type (MyImpl).

    This means a workaround exists by hiding the constructor and offering a static instantiation method which returns an object of the Interface type as the only way of instantiating MyImpl. This ends up looking rather crude though:

    public class ObjectCreationTest {
    
        public static void main(final String[] args) {
            A obj = MyImpl.instance();
            System.out.println(obj.getClass());
            System.out.println(obj.say());
        }
    }
    
    public class MyImpl implements A {
    
        public static A instance() {
            return new MyImpl();
        }
    
        private MyImpl() {
        }   
    
        @Override
        public String say() {
            return "MyImpl";
        }
    }
    

    Then you let your aspect catch calls to that instance()-method:

    @Aspect
    public class MyAspect {
        @Around(value = "call(A com.oneandone.MyImpl.instance(..))")
        public Object initAdvice(final ProceedingJoinPoint pjp) throws Throwable {
            return new YourImpl();
        }
    }
    

    Not sure if this is still applicable to your usecase, but I think it's the closest working thing to what you were attempting in the first place.