[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
Re: [aspectj-users] Advice on Constructors With Subclasses

Hi James,
In my humble opinion, AspectJ should offer a way to execute something
after the complete initialization cycle (which includes all calls to
super() or other this()), while still using an "execution like"
pointcut, and weaving the target classes and not the client classes.

I'll try to write what I understand about it, Andy or Andrew will then
correct my errors.

A naive approach could be to make an around advice on constructors,
build a stack of calls, and then execute the advice code only when the
stack is empty (aka, we are exiting from the last constructor).
Unfortunately this can not work, because the super() call (or the call
to another this() constructor) in java must be the first instruction of
a constructor, so declaring a before advice on a constructor pointcut
will execute before the constructor code, but after the call to super(),
so our stack would always contain only one element, and always execute
the code.

For the same reason, using cflow or cflowbelow does not work : cflow
uses a counter to determine if it has or has not yet executed a certain
method, but since the super() call must be executed before this counter
is set, it will not see the flow of calls.

Anyway, Java provides a way to inspect the current thread stack trace.
Having it it's possible to understand if we are in the "after" advice of
a constructor called by another constructor, and in that case avoid
executing potentially offending code.

This is how I solved my problem, a while ago, in first instance. Then I
refactored the code and removed the need for such an after advice.

Anyway, it would be great to have AspectJ implement such a check, and
provide a way to say "I want this to execute AFTER the object has been
completely initialized (all the constructors chain has finished)", and
make this while advicing and weaving target classes and not client classes.

For example, if I have the following classes :

public class A {
  public A() {
    System.out.println("I'm in A");
  }
}

public class B extends A {
  public B() {
    super(); // It's implicit anyway
    System.out.println("I'm in B");
  }
}

public class C extends B {
  public C() {
    super(); // It's implicit anyway
    System.out.println("I'm in C");
  }
}

Writing an advice like :

after() : hasBeenCompletelyInitialized(A+.new()) {
  System.out.println("Ok, I can run");
}

Should produce :

new A();

I'm in A
Ok, I can run

new B();

I'm in A
I'm in B
Ok, I can run

new C();

I'm in A
I'm in B
I'm in C
Ok, I can run

The simple way to implement it (both the AspectJ new pointcut, if there
are good usecases for it, or your temporary solution) is to inspect the
stack trace before executing the advice to see if this is the last call
to the initialization chain. Unfortunately simply checking for "the
number of <init> methods in the stack trace" if not enought, cause a
"new A()" could be called from inside the constructor of another class
Z, so inspection of the real A-B-C hierarchy is needed.

Anyway, if this was an AspectJ keyword, such a check could be optimized
performing part of the analysis at compile time.

To make things clear, the following aspect works, but it still misses a
special case where a new A() (or new B() or new C()) is called from
inside the constructor of A (or B or C) deliberately by the code. This
could be anyway prevented using common cflow pointcuts or adding a
before advice to manually implement a check for this case.

WARNING : ugly code follows, used only to demonstrate a proof of
concept, handle it with care!

package it.semeru.tests.initialization;

public aspect InitializationAspect {

    pointcut initializing() : execution(A+.new());
   
    after() : initializing() {
        if (isLastConstructorInChain(A.class)) System.out.println("Done");
    }
   
    public boolean isLastConstructorInChain(Class<?> clazz) {
        StackTraceElement[] stackTrace =
Thread.currentThread().getStackTrace();
        // find the first constructor, cause there are a few AspectJ
internal calls before it
        int acpos = 0;
        while (acpos < stackTrace.length &&
!stackTrace[acpos].getMethodName().equals("<init>")) {
            acpos++;
        }
        // Check if we run out, should never happen
        if (acpos >= stackTrace.length) return false;
        // Now we have the last call to <init>
        // if the following one is not a constructor (or is not present),
        // we can skip all the checks and return true
        if (acpos + 1 == stackTrace.length) return true;
        if (!stackTrace[acpos + 1].getMethodName().equals("<init>"))
return true;
        // Otherwise we have to check it the next constructor in the
        // chain is a proper subclass of the current class
        String callingclass = stackTrace[acpos + 1].getClassName();
        Class<?> forname = null;
        try {
            forname = Class.forName(callingclass);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return false;
        }
        if (clazz.isAssignableFrom(forname)) {
            return false;
        }
        return true;
    }
   
}

Called with the following main :

    public static void main(String[] args) {
        new A();
        new B();
        new C();
    }

Outputs :

I'm in A
Done
I'm in A
I'm in B
Done
I'm in A
I'm in B
I'm in C
Done

Hope this helps,
Simone



James Elliott wrote:
> In trying to achieve the same results after an object has been
> constructed using reflection, I have run into a wall.
>
> I can't use the call joinpoints for this case, since the pointcut
> obviously won't match on the newInstance() call, not to mention the
> client code weaving that Simone was talking about.
>
> I have tried using initialization joinpoints as Andrew suggested, but
> I keep running into the same problems that I had in my original
> posting, namely, the advice is being run after every super call.
>
> My first thought was to advise calls to Class.newInstance() as well,
> but this is problematic because some of the calls are being made from
> an external library that I am using, and I can't advise calls within
> that library.  Similarly, I can't advise the execution of
> newInstance() because that would be advising the standard library.
>
> My current pointcut looks like this:
> after(A a) returning: initialization(A+.new(..)) && this(a) {
>     System.out.println(thisJoinPoint.getSourceLocation());
> }
>
> And the output is back to the same as it was in my original question,
> for both normal construction, and reflection-style construction.
>
> Any suggestions?
> _______________________________________________
> aspectj-users mailing list
> aspectj-users@xxxxxxxxxxx
> https://dev.eclipse.org/mailman/listinfo/aspectj-users
>   


-- 
Simone Gianni
http://www.simonegianni.it/
CEO Semeru s.r.l.
Apache Committer