John the Traveler
John the Traveler

Reputation: 514

Behaviours of new constructor added by AspectJ ITD

I am currently applying AspectJ to our project, and I found a behavior which is a bit strange to me.

Q1: I added a new constructor to my current class with inter-type declaration, and found that the class's member variable is not initialized if the new constructor is used to instantiate my class.

For example:

The class which I'll add a new constructor to:

public class Child {

    public String name = "John";

    public Child(String desc) {
        // TODO Auto-generated constructor stub
    }
} 

The aspectJ code:

public aspect MyTest {
    public Child.new(String desc, int num) {
        System.out.println("Child Name:" + this.name);
    }
}

If I instantiate the Child with the new constructor:

new Child("A child", 5)

the member variable this.name is not initialized as will be done with the original constructor.

But, if I call the original constructor:

new Child("A child") 

the member variable this.name will be initialized to "John" as usual

The result:

Child Name:null

Is this a limitation of AspectJ? Is there anyway to resolve this issue?

I don't really want to add the code for member variable initialization to the new constructor.

Q2: It seems in the newly added constructor, super.method() can not be correctly resolved.

The class which I'll add a new constructor to:

public class Child extends Parent{

    public String name = "John";

    public Child(String desc) {

    }
} 

Child extends Parent. Parent has a method init()

public class Parent {

    public void init() {
        //....
    }

}

I add a new constructor for the Child in my aspect.

public aspect MyTest {
    public Child.new(String desc, int num) {
        super.init();
    }
}

The above aspect code will trigger an exception.

Exception in thread "main" java.lang.NoSuchMethodError: com.test2.Child.ajc$superDispatch$com_test2_Child$init()V
    at MyTest.ajc$postInterConstructor$MyTest$com_test2_Child(MyTest.aj:19)
    at com.test2.Child.<init>(Child.java:1)
    at MainProgram.main(MainProgram.java:11)

My workaround is to define another method for my class Child, and indirectly call the super.method() within that method

For example, add a new method that calls super.init() for Child

public void Child.initState()
{
    super.init();
}

Now, I can call initState() in the newly added constructor like below:

public aspect MyTest {
    public Child.new(String desc, int num) {
        this.initState();
    }
}

Is this a limitation of AspectJ? Is this the only way to resolve this issue?

Thank you all for your time :)

Upvotes: 0

Views: 616

Answers (3)

WasiGG
WasiGG

Reputation: 26

For the second question, I think it's a bug of aspectJ. That decompile the woven target byte code will find that the method “com.test2.Child.ajc$superDispatch$com_test2_Child$init()V” will be inserted. It implies this method should be generate by aspectJ, but there is no such method in the byte code.

Upvotes: 1

Li-Wei Cheng
Li-Wei Cheng

Reputation: 31

Foe the first questions, it seems that the lint warning will appear when compiling: (unless you close the lint warning)

"inter-type constructor does not contain explicit constructor call: field initializers in the target type will not be executed [Xlint:noExplicitConstructorCall]"

Therefore I'd say it's an AspectJ's limitation.

The best way to do this might be call the other constructors of Child in the constructor added by AspectJ

For example:

public aspect MyTest {
    public Child.new(String desc, int num) {
        this("Hello"); // -> This will call the constructor of Child, and trigger fields initialization
        System.out.println("Child Name:" + this.name);
    }
}

Upvotes: 2

ramnivas
ramnivas

Reputation: 1284

The code for an ITD introduction is no different that the code that you would add to a class directly. So without member initialization code in your introduced constructor, members will , of course, remain uninitialized. So you need to change you code in Q1 as follows.

public Child.new(String name, int age) {
    this.name = name;
    this.age = age;
    System.out.println("Child Name:" + this.name);
}

As for Q2, it works fine for me.

class Parent {
    public void init() {
    System.out.println("P.init");
}
}

class Child extends Parent {
}

aspect Intro {
    public void Child.init(){
        super.init();
        System.out.println("C.init");
    }
}

public class Main {
    public static void main(String[] args) {
        Child c = new Child();
        c.init();
    }
}

prints:

P.init
C.init

Changing the introduced method to something other than init works too (to match your code).

Regarding your comment: I fail to see what difference you have made in Q1. Sorry, I don't get it.

As for Q2 part of your comment, constructor arrangement works for me:

class Parent {
    protected String name;

    public Parent(String name) {
        this.name = name;
    }
}

class Child extends Parent {
    int age;

    public Child(String name) {
        super(name);
    }
}

aspect Intro {
    public Child.new(String name, int age){
        super(name);
        this.age = age;
        System.out.println("this.name: " + this.name + " this.age: " + this.age);
    }
}

prints this.name: myname this.age: 2

Upvotes: 0

Related Questions