AspectJ: ClassCastException when trying to interce

2019-07-23 12:01发布

问题:

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

回答1:

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.



回答2:

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


标签: aspectj