Reputation: 999
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
Upvotes: 1
Views: 971
Reputation: 67297
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
Upvotes: 1
Reputation: 3825
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.
Upvotes: 1