If you cannot rely on your library being used as it should via the application starting up with -javaagent:/path/to/aspectjweaver.jar
even though your library is a dependency, what kind of users does the library have? To me it seems that you want to modify the control flow of an application which its users or builders are unaware of. I call such a library a trojan horse. Anyway, as for your question:
- If you cannot rely on users remembering to use the Java Agent approach (LTW, load-time weaving), tell them to build their application with AspectJ Maven plugin and use compile-time weaving. This is pretty easy to use and during runtime all that is needed is a dependency on aspectjrt.jar (AspectJ runtime) which is just a normal library from their perspective.
- Using LTW, newer versions of AspectJ since 1.8.7 can dynamically start the load-time weaver if it is on the classpath, even if the JVM is not started as a Java Agent. (I know because I implemented this little feature myself.) Please read the description and be aware of the main disadvantage: It only works for classes loaded after your library has attached the weaver, so it is not reliable as a "hacking tool" if you want to hook into other people's classes.
I think your problem is not really a problem. Technology is not the bottleneck here, communication to your users is. Write a good documentation and put the LTW question on the top of your FAQ list.
Update:
You asked how you could write an aspect intercepting constructor calls, thus I am assuming that you want to keep weak references to instantiated (newly created) objects as long as they live and are not reclaimed by the garbage collector.
But first things first - we need an annotation:
package de.scrum_master.app;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface CatchMe {}
Please note @Inherited
. This means that if a class is annotated, all of its subclasses will inherit the annotation. According to the JDK documentation this does not work for annotated interfaces, by the way, i.e. classes do not inherit any annotations from implemented interfaces, as we will see in a moment.
Update: Because I have answered the question about annnotation inheritance several times before, I have just documented the problem and also a workaround in Emulate annotation inheritance for interfaces and methods with AspectJ.
Now we need a few sample classes:
package de.scrum_master.app;
@CatchMe
public class AnnotatedBase {
public void doSomething() {
System.out.println("Doing something");
}
}
package de.scrum_master.app;
public class Sub extends AnnotatedBase {
public void doSomethingElse() {
System.out.println("Doing something else");
}
}
As you can see, Sub
extends AnnotatedBase
and should inherit its @CatchMe
annotation.
Here is the counter-example with an annotated interface and an implementing class which should not inherit the annotation and thus not be caught by our aspect:
package de.scrum_master.app;
@CatchMe
public interface Greeter {
void sayHelloTo(String someone);
}
package de.scrum_master.app;
public class Other implements Greeter {
@Override
public void sayHelloTo(String someone) {
System.out.println("Hello " + someone + "!");
}
}
In order to test our yet to be written aspect we need a little driver application instantiating a few objects:
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
new AnnotatedBase().doSomething();
new Sub().doSomething();
System.gc();
System.out.println("\n----- GC -----\n");
new Sub().doSomethingElse();
new Other().sayHelloTo("world");
}
}
Please note that the application at one point performs a garbage collection. This way we can see what happens with the WeakHashMap
we are going to use in the upcoming aspect.
The console log without any aspects looks like this:
Doing something
Doing something
----- GC -----
Doing something else
Hello world!
Last, but not least, here is our aspect. It keeps a weak hash map of created objects still alive (i.e. unaffected by GC) pointing to their respective classes, so that we can create reports on both object instances and affected classes in the aspect.
package de.scrum_master.aspect;
import java.util.WeakHashMap;
import de.scrum_master.app.CatchMe;
public aspect AnnotatedConstructorInterceptor {
private WeakHashMap<Object, Class<?>> livingObjects = new WeakHashMap<>();
after(Object newObject) : execution((@CatchMe *).new(..)) && target(newObject) {
System.out.println(thisJoinPoint);
livingObjects.put(newObject, newObject.getClass());
printReport();
}
private void printReport() {
System.out.println(" Living objects:");
for (Object livingObject : livingObjects.keySet())
System.out.println(" " + livingObject);
System.out.println(" Affected classes:");
for (Class<?> clazz : livingObjects.values())
System.out.println(" " + clazz.getName());
System.out.println();
}
}
With the aspect in place, the log output becomes, step by step:
1.) new AnnotatedBase().doSomething();
execution(de.scrum_master.app.AnnotatedBase())
Living objects:
de.scrum_master.app.AnnotatedBase@452b3a41
Affected classes:
de.scrum_master.app.AnnotatedBase
Doing something
2.) new Sub().doSomething();
: You can see how both the sub and base class constructor are executed, but only one object is created and added to the hash map.
execution(de.scrum_master.app.AnnotatedBase())
Living objects:
de.scrum_master.app.Sub@4a574795
de.scrum_master.app.AnnotatedBase@452b3a41
Affected classes:
de.scrum_master.app.Sub
de.scrum_master.app.AnnotatedBase
execution(de.scrum_master.app.Sub())
Living objects:
de.scrum_master.app.Sub@4a574795
de.scrum_master.app.AnnotatedBase@452b3a41
Affected classes:
de.scrum_master.app.Sub
de.scrum_master.app.AnnotatedBase
Doing something
3.) System.gc();
: Perform garbage collection. After this, the weak hash map should be empty again.
----- GC -----
4.) new Sub().doSomethingElse();
: Now a subclass object is instantiated again, after the GC becoming the one and only object in the weak hash map.
execution(de.scrum_master.app.AnnotatedBase())
Living objects:
de.scrum_master.app.Sub@f6f4d33
Affected classes:
de.scrum_master.app.Sub
execution(de.scrum_master.app.Sub())
Living objects:
de.scrum_master.app.Sub@f6f4d33
Affected classes:
de.scrum_master.app.Sub
Doing something else
5.) new Other().sayHelloTo("world");
: This should have no effect on the weak hash map as the aspect is not triggered due to the non-inheritance of annotations from interfaced to implementing classes, as explained above.
Hello world!
Et voilà! :-)