Search code examples
aopaspectj

AspectJ, Intertype Definition


I am receiving this error when I compile

The type XXX must implement the inherited abstract method

I have three files

A default implementation [com.SafeReaderIMPL.java]

public class SafeReaderIMPL implements ISafeReader {

    private boolean successfulRead;

    public SafeReaderIMPL() {
        successfulRead = true;
    }

    protected void fail() {
        successfulRead = false;
    }

    @Override
    public boolean isSuccessfulRead() {
        return successfulRead;
    }
}

An interface file [com.ISafeReader.java]

public interface ISafeReader {

    public boolean isSuccessfulRead();
}

An apsect (using annotations) [com.SafeReaderAspect.java]

@Aspect
public class SafeReaderAspect {

    @DeclareParents(value = "com.BadReader", defaultImpl = SafeReaderIMPL.class)
    public ISafeReader implementedInterface;

    @AfterThrowing(pointcut = "execution(* *.*(..)) && this(m)", throwing = "e")
    public void handleBadRead(JoinPoint joinPoint, ISafeReader m, Throwable e) {
        ((SafeReaderIMPL)m).fail();
    }
}

And a Test Class [com.BadReader]

public class BadReader {
    public void fail() throws Throwable {
        throw new Throwable();
    }
}

I compile the first three files in a separate jar using

ajc -source 1.8 -sourceroots . -outjar aspectLib.jar

I then compile the second file using the aspectLib like so

ajc -source 1.8 -sourceroots . -aspectpath ./aspectLib.jar -outjar common.jar

When I go to compile the second jar I get the error. I am using the latest stable version of AspectJ 1.8.3

BadReader.java:10 [error] The type BadReader must implement the inherited abstract method ISafeReader.isSuccessfulRead() public class BadReader { ^^^^^^^^


Solution

  • The problem is not two-step compilation as such, but the fact that @DeclareParents in @AspectJ syntax in not 100% compatible with declare parents in native syntax. Actually, @DeclareParents for introducing default interface implementations is superseded by @DeclareMixin (see this bug ticket), but the downside of the mixin approach is that you do not have a real A implements B scenario there, i.e. you cannot cast as you wish in your after-throwing advice, so this is also not a good option in your case.

    So what do you do if you want to keep the two-step compilation approach? Just use native syntax:

    Interface:

    package com;
    
    public interface ISafeReader {
        boolean isSuccessfulRead();
    }
    

    Default implementation:

    package com;
    
    public class SafeReaderIMPL implements ISafeReader {
        private boolean successfulRead;
    
        public SafeReaderIMPL() { successfulRead = true; }
        public void fail() { successfulRead = false; }
        @Override public boolean isSuccessfulRead() { return successfulRead; }
    }
    

    ITD aspect:

    package com;
    
    public aspect SafeReaderAspect {
        declare parents : com.BadReader implements SafeReaderIMPL;
    
        after(ISafeReader safeReader) throwing : execution(* *(..)) && this(safeReader) {
            System.out.println(thisJoinPoint + " - calling 'fail()' before rethrowing error");
            ((SafeReaderIMPL) safeReader).fail();
        }
    }
    

    ITD target class with sample main method:

    package com;
    
    public class BadReader {
        public void doSomething() {
            throw new RuntimeException("my error");
        }
    
        public static void main(String[] args) {
            BadReader badReader = new BadReader();
            System.out.println("badReader.isSuccessfulRead() = " + badReader.isSuccessfulRead());
            try { badReader.doSomething(); }
            catch(Throwable t) { System.out.println(t); }
            System.out.println("badReader.isSuccessfulRead() = " + badReader.isSuccessfulRead());
        }
    }
    

    Now you can use the two-stage compilation approach.

    Console output:

    badReader.isSuccessfulRead() = true
    execution(void com.BadReader.doSomething()) - calling 'fail()' before rethrowing error
    java.lang.RuntimeException: my error
    badReader.isSuccessfulRead() = false