6

When class hierarchy is not linear, aspect is not triggered when defined on base interface.

The most interesting: when adding delegating implementation (see last code block) to the parent class of the implementation, the test becomes Green (Aspect is triggered as expected).

Question: Why doesn't it work as described in example and why does it work with delegating implementation?

Example (sorry, no shorter example found):

Test:

 @Autowired
private TheInterface underTest;
private static boolean aspectCalled;
private static boolean implementationCalled;

@Test
public void aspectTest() throws Exception {
    aspectCalled = false;
    implementationCalled = false;

    underTest.doSomething();

    assertTrue("Implementation not called!", implementationCalled);
    assertTrue("Aspect not called!", aspectCalled);
}

Aspect:

@Aspect
@Component
public static class MyAspect {

    @Before("execution(* *..SpecializedInterface+.doSomething())")
    public void applyAspect() {
        aspectCalled = true;
    }
}

Interfaces:

public static interface TheInterface {
    void doSomething();
}

public static interface SpecializedInterface extends TheInterface {
    // inherits doSomething
    // defines some other methods
}

Abstract implementations (Template pattern):

public static abstract class BaseTemplate implements TheInterface {
    abstract void doOneStep();

    @Override
    public void doSomething() {
        // do some stuff and
        doOneStep();
    }
}

public static abstract class SpecializedTemplate extends BaseTemplate implements SpecializedInterface {
    // some other methods
}

Implementing bean:

@Component
public static class TemplateImplementation extends SpecializedTemplate {
    @Override
    void doOneStep() {
        implementationCalled = true;
    }
}

(If you are interested: test setup:)

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MyConfig.class)
public class AopTest {
    @Configuration
    @EnableAspectJAutoProxy
    @ComponentScan(basePackageClasses = AopTest.class)
    public static class MyConfig {
    }
    ...

Ugly workaround: add this snippet to SpecializedTemplate

    @Override
    public void doSomething() {
        super.doSomething();
    }

So, why is this workaround necessary?

Sergej Werfel
  • 1,335
  • 2
  • 14
  • 25
  • 1
    I'm not too firm in the details, but this is my guess: @Before("execution(* *..SpecializedInterface+.doSomething())") tells it to apply the aspect to doSomething() in any class implementing SpecializedInterface. SpecializedTemplate does not contain doSomething() (bytecode-wise), so the aspect is not applied. Once you add your workaround, the class contains the method, and the aspect can be applied. – Thomas Stets Feb 04 '15 at 09:59
  • But what is about the TemplateImplementation (implementing bean)? Does it contains the doSomething()-method in bytecode? Does java compiler copy all implementations in abstract classes to the concrete classes, so it has the method on bytecode level, too? – Sergej Werfel Feb 04 '15 at 10:05
  • 1
    No, TemplateImplementation does not have bytecode for the inherited methods. If you call doSomething() on TemplateImplementation the Java VM during runtime goes backwards through the class hierarchy until it finds an implementation. If you use your workaround, it will find the implementation in SpecializedTemplate, where the aspect has been applied (since the pointcut matched). Without workaround it has to go back to BaseTemplete, where the aspect has not been applied (since it did not match the pointcut) – Thomas Stets Feb 04 '15 at 10:19

1 Answers1

0

Thomas Stets has already explained the bytecode and JVM stuff, so I will just provide a solution to your problem, see also my answer to a very similar question.

@Aspect
public static class MyAspect {
    @Before("execution(* *..TheInterface+.doSomething()) && target(specializedInterface)")
    public void applyAspect(SpecializedInterface specializedInterface) {
        aspectCalled = true;
    }
}

I.e. your pointcut targets the base interface actually defining the method, then you limit the target to the specialised sub-interface of your choice. This should make your test green.

Community
  • 1
  • 1
kriegaex
  • 63,017
  • 15
  • 111
  • 202