Justin Hong
Justin Hong

Reputation: 383

Using AspectJ annotations to provide logic following execution of an overridden method

The goal: Use AspectJ to call a static method following execution of a specific method.

For the sake of the question, let's call the static method System.out.println and the method onConfigurationChanged.

The constraints:

  1. The onConfigurationChanged method is declared, implemented, and invoked in a class I have no control over (cannot be weaved)*.
  2. Since onConfigurationChanged has an implementation in the base class, the subclasses may or may not override it (but System.out.println should still be called after the onConfigurationChanged executes in both cases).
  3. Use annotations syntax, as support for native aspect syntax does not appear to be supported in my development environment.

* Note: This is in the context of building an Android app, so the base class in question is actually android.app.Activity. It's apparent compile-time weaving is out of the question. I've looked into load-time weaving, but I'm a little unsure as to how I'd accomplish it in this context, and unsure if I even want to, since it's such a critical code path.

The main issue I'm currently facing is really the case where the subclass does not override the method.

What I've tried:

The above works in the case where the subclass overrides the onConfigurationChanged method.

Unfortunately, the above does not add the onConfigurationChanged implementation provided by the aspect to the subclass that does not override onConfigurationChanged. It does specify that the subclass implements the OnConfigurationChangedListener interface, but since the method is implemented in its parent class, the compiler doesn't complain. (If I slightly tweak the signature of the interface method, so that it no longer matches, I see that it does get added, but that's not the desired outcome.)

Any help is greatly appreciated, thanks.

Upvotes: 0

Views: 213

Answers (1)

kriegaex
kriegaex

Reputation: 67297

You have to use native syntax for technical reasons because declare parents is more powerful than @DeclareParents or @DeclareMixin.

Both Eclipse and IntelliJ IDEA support AspectJ native syntax. I do not know about NetBeans though. Which IDE do you use? The best support for advanced AspectJ syntax features you find in Eclipse, IDEA has a few shortcomings. But disregarding nice features like syntax highlighting, code completion and refactoring, you can write an aspect in any editor.

As for your problem, I want to emphasise that I am not an Android developer, so I just recreated the basic Android API features you use with these two dummy classes so as to make my aspect testable:

Put these into a plain Java project (or use the original Android API):

package android.app;

import android.content.res.Configuration;

public class Activity {
    public void onConfigurationChanged (Configuration newConfig) {
        System.out.println("onConfigurationChanged - default implementation");
    }
}
package android.content.res;

public class Configuration {}

Now we create another project in our IDE, this time an AspectJ project. It should reference the little dummy Android API emulation from above.

In the AspectJ project, we create two Activity subclasses for demonstration, one overriding onConfigurationChanged and one not overriding it:

package com.jkhong.app;

import android.app.Activity;
import android.content.res.Configuration;

public class OverridingActivity extends Activity {
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        System.out.println("onConfigurationChanged - subclass override");
    }
}
package com.jkhong.app;

import android.app.Activity;

public class NonOverridingActivity extends Activity {}

With a little driver application we can show what happens:

package com.jkhong.app;

import android.app.Activity;
import android.content.res.Configuration;

public class Application {
    public static void main(String[] args) {
        Configuration newConfig = new Configuration();
        System.out.println("Activity");
        new Activity().onConfigurationChanged(newConfig);
        System.out.println("\nOverridingActivity");
        new OverridingActivity().onConfigurationChanged(newConfig);
        System.out.println("\nNonOverridingActivity");
        new NonOverridingActivity().onConfigurationChanged(newConfig);
    }
}

The console log looks like this:

Activity
onConfigurationChanged - default implementation

OverridingActivity
onConfigurationChanged - default implementation
onConfigurationChanged - subclass override

NonOverridingActivity
onConfigurationChanged - default implementation

As you can see, OverridingActivity calls its super method before doing anything else. I recommend that you always do that. For NonOverridingActivity only the default implementation from the base class is executed, as expected.

Now here is the aspect solving your problem:

package com.jkhong.aspect;

import android.app.Activity;
import android.content.res.Configuration;

public aspect ConfigurationChangeInterceptor {
    public static class DefaultChangeListener extends Activity {
        @Override
        public void onConfigurationChanged (Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
            doDefaultOnConfigurationChanged();
        }
    }

    declare parents : Activity+ && !Activity extends DefaultChangeListener;

    private static void doDefaultOnConfigurationChanged() {
        System.out.println("onConfigurationChanged - aspect override");
    }
}

Please note how the aspect uses Activity+ && !Activity to specify "all Activity subclasses but not Activity itself" in order to avoid compiler errors because Activity cannot extend itself and is not exposed to the weaver anyway.

The console log now looks like this:

Activity
onConfigurationChanged - default implementation

OverridingActivity
onConfigurationChanged - default implementation
onConfigurationChanged - aspect override
onConfigurationChanged - subclass override

NonOverridingActivity
onConfigurationChanged - default implementation
onConfigurationChanged - aspect override

So now you have what you want. The log output it triggered for NonOverridingActivity and also for OverridingActivity.

Upvotes: 1

Related Questions