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
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.
What you want does not work with sibling classes, but if you have the option of making YourImpl
a subclass of MyImpl
instead, it works:
Driver application:
package de.scrum_master.app;
public class ObjectCreationTest {
public interface A {
String say();
}
public static class MyImpl implements A {
@Override
public String say() {
return "MyImpl";
}
}
public static class YourImpl extends MyImpl {
@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:
package de.scrum_master.aspect;
import de.scrum_master.app.ObjectCreationTest.*;
public aspect MyAspect {
Object around() : !within(MyAspect) && call(A+.new(..)) {
System.out.println(thisJoinPoint);
return new YourImpl();
}
}
Console output:
call(de.scrum_master.app.ObjectCreationTest.MyImpl())
class de.scrum_master.app.ObjectCreationTest$YourImpl
YourImpl