Reputation: 383
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:
onConfigurationChanged
method is declared, implemented, and invoked in a class I have no control over (cannot be weaved)*.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).* 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:
Execution pointcut specified with the signature of onConfigurationChanged
, with @After
advice defined, which calls System.out.println
.
@After("execution(void com.jkhong..*.onConfigurationChanged()) "
+ "&& !within(DefaultOnConfigurationChangedAspect)")
public void onConfigurationChangedExecution() {
doDefaultOnConfigurationChanged();
}
private static void doDefaultOnConfigurationChanged() {
System.out.println("Default onConfigurationChanged (mixed in)");
}
The above works in the case where the subclass overrides the onConfigurationChanged
method.
@DeclareMixin
targeting the subclasses, returning an anonymous class implementation that calls System.out.println
. The returned interface has a single method with the exact same signature as onConfigurationChanged
from the base class.
public interface OnConfigurationChangedListener {
void onConfigurationChanged();
}
@DeclareMixin("com.jkhong..*.*Activity")
public static OnConfigurationChangedListener createDefaultListener() {
return new OnConfigurationChangedListener() {
@Override
public void onConfigurationChanged() {
doDefaultOnConfigurationChanged();
}
};
}
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
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