How to add a field to a custom-annotated class using AspectJ

后端 未结 2 825
北海茫月
北海茫月 2021-02-11 03:49

To add a field to some specific class with aspectj we do

package com.test;

public class MyClass {
    private String myField;
}

public aspect MyAspect
{
    pr         


        
相关标签:
2条回答
  • 2021-02-11 04:35

    Actually you cannot make inter-type declarations (ITD) on annotation types, i.e. you need to know concrete class names in order to declare static or non-static members or methods directly.

    The usual workaround is:

    • Create an interface with all the methods you need.
    • Provide implementations for each interface method.
    • Make each annotated type implement the interface via ITD.

    Now if you also want to add a static member such as a logger to all annotated types, again if you do not know the exact class names you need to use a workaround:

    • Create an aspect holding the desired member(s). Let's call it LoggerHolder in this example.
    • Make sure that one aspect instance per target class is created instead of the default singleton aspect instance. This is done via pertypewithin.
    • In order to avoid runtime exceptions you must not initialise the members directly via Logger logger = ... but need to do it lazily, waiting until after the target type's static initialisation phase is finished.
    • You also need to provide an accessor method like LoggerHolder.getLogger() in the aspect and call it whenever necessary.
    • In order to hide all the ugly aspect stuff from the end user I recommend to add yet another accessor method LoggableAspect.getLogger() (same method name for convenience) to the ITD interface mentioned above and provide a method implementation extracting the member reference from the aspect instance via LoggerHolder.aspectOf(this.getClass()).getLogger().

    Attention: I am using two concepts at once here, mixing them in one application because you asked for both static members and non-static methods added to annotated classes:

    • Helper interface + implementation added to your core code via ITD
    • Holder aspect declaring member(s) and associated with target classes via pertypewithin in order to emulate static members

    Now here is some sample code:

    Annotation:

    package de.scrum_master.app;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    @Retention(RetentionPolicy.RUNTIME)
    public @interface CustomLoggable {}
    

    Two classes, one bearing the annotation and one not bearing it:

    package de.scrum_master.app;
    
    public class OrdinaryClass {
        public void doSomething() {
            System.out.println("Logging some action directly to console");
        }
    }
    
    package de.scrum_master.app;
    
    import java.util.logging.Level;
    
    @CustomLoggable
    public class AnnotatedClass {
        public void doSomething() {
            getLogger().log(Level.INFO, "Logging some action via ITD logger");
            getLogger().log(Level.INFO, someOtherMethod(11));
        }
    }
    

    As you can see, the second class uses two methods which have not been declared directly within the class: getLogger() and someOtherMethod(int). Both of them will be declared via ITD further below, the former providing access to the pseudo-static member and the latter being just another method you want declared on each annotated class.

    Aspect holding an instance of the pseudo-static member:

    package de.scrum_master.aspect;
    
    import java.util.logging.Logger;
    import de.scrum_master.app.CustomLoggable;
    
    public aspect LoggerHolder
        pertypewithin(@CustomLoggable *)
    {
        private Logger logger;
    
        after() : staticinitialization(*) {
            logger = Logger.getLogger(getWithinTypeName());
        }
    
        public Logger getLogger() {
            return logger;
        }
    }
    

    As I said earlier, please note the usage of pertypewithin and staticinitialization. Another convenient thing is to use the aspect's getWithinTypeName() method in order to get the target class name for naming the logger.

    Aspect declaring an interface + implementation and applying it to all target types:

    package de.scrum_master.aspect;
    
    import java.util.logging.Logger;
    import de.scrum_master.app.CustomLoggable;
    
    public aspect LoggableAspect {
        public static interface Loggable {
            Logger getLogger();
            String someOtherMethod(int number);
        }
    
        declare parents : (@CustomLoggable *) implements Loggable;
    
        public Logger Loggable.getLogger() {
            return LoggerHolder.aspectOf(this.getClass()).getLogger();
        }
    
        public String Loggable.someOtherMethod(int number) {
            return ((Integer) number).toString();
        }
    }
    

    For simplicity, I just declared the interface as a static nested type within the aspect. You can also declare the interface separately, but here you see it in its context which for me is preferable.

    The key thing here is the declare parents statement making each target class implement the interface. The two method implementations at the end show how to provide "normal" method implementations as well as how to access the logger from the holder aspect via aspectOf.

    Driver class with entry point:

    Last, but not least, we want to run the code and see if it does what we want.

    package de.scrum_master.app;
    
    public class Application {
        public static void main(String[] args) {
            new OrdinaryClass().doSomething();
            new AnnotatedClass().doSomething();
        }
    }
    

    Console output:

    Logging some action directly to console
    Mrz 15, 2015 11:46:12 AM de.scrum_master.app.AnnotatedClass doSomething
    Information: Logging some action via ITD logger
    Mrz 15, 2015 11:46:12 AM de.scrum_master.app.AnnotatedClass doSomething
    Information: 11
    

    Voilà! Logging works, the Logger has a nice name de.scrum_master.app.AnnotatedClass and calling the two interface methods works as expected.

    Alternative approach:

    Since AspectJ 1.8.2 annotation processing is supported, see also this blog post. I.e. you could use APT in order to generate one aspect per annotated type and introduce static members and additional methods directly without any tricks such as per-type instantiation, accessor methods members within holder aspect instances and interfaces. This comes at the cost of an additional build step, but I think it would be a very neat and straightforward way to solve your problem. Let me know if you have any difficulty understanding the examples and need more help.

    0 讨论(0)
  • 2021-02-11 04:42

    You can create a pointcut for any type with a particular annotation. See Join Point Matching based on Annotations.

    0 讨论(0)
提交回复
热议问题