studentttt
studentttt

Reputation: 71

Customize pointcut expression for 'declare @method'

Use case

I'd like to add programmatically an externally provided annotation named: @Trace

  1. to all public methods in the spring-boot project
  2. that are in a class annotated with @Controller
  3. only within a particular package (com.example.apectitddemo.controller)
  4. only if the method doesn't have a different custom annotation already applied, f.e. @Disable

Thanks to the above criteria, each newly added method to the project that meets them all will be @Trace annotated dynamically without any additional developer action, which is the main goal here.

My approach

I used Aspectj's ITD (inter type declaration) for this but it fulfills only 1st requirement and have no idea how to customize it for 2nd, 3rd and 4th. Tried several ways commented out in the below code snipped.

TracingAspect.aj:

package com.example.apectitddemo.aspect;
public aspect TracingAspect {
    declare @method : public * *(..) : @Trace; 
//[INFO] 'public void com.example.apectitddemo.controller.ControllerPing.ping()' (ControllerPing.java) is annotated with @Trace method annotation from 'com.example.apectitddemo.aspect.TracingAspect' (TracingAspect.aj)

//    declare @method : public * ((@Controller *)).*(..) : @Trace;
//    declare @method : public * ((@Controller *)).*(..) && !@Disabled : @Trace;
//    declare @method : public com.example.apectitddemo.controller.* :@Trace;
//    declare @method : public * com.example.apectitddemo.controller+ : @Trace;
//    declare @method : public * *(com.example.apectitddemo.controller.*) : @Trace;
//    declare @method : public * controller..* : @Trace;
//    declare @method : public * *(..) : @Trace;
}

BTW is it possible to use pure java here (TracingAspect.java) and not as .aj file?

ControllerPing.java (sample method which should be annotated by an aspect)

package com.example.apectitddemo.controller
@Controller
public class ControllerPing {

    //@Trace annotation should be added here by ITD
    public void ping() {
        log.info("ok");
    }

    @Disable
    public void pingDisabled() {
        log.info("ok");
    }
}

Misc

I was searching the internet but haven't found much documentation and even couldn't encounter any other code samples except below. The above solution is based on this finding:

Other pages found, related:

Maybe there is another better way to complete the requirements?

Upvotes: 1

Views: 143

Answers (1)

kriegaex
kriegaex

Reputation: 67437

This is a native AspectJ example. You can run it completely without Spring or from within a Spring application - up to you:

Annotations:

package de.scrum_master.stackoverflow.q73270343;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target({ METHOD })
public @interface Trace {}
package de.scrum_master.stackoverflow.q73270343;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target({ METHOD })
public @interface Disable {}

Non-controller class (negative test):

package de.scrum_master.stackoverflow.q73270343;

public class NoControllerPing {
  // @Trace annotation should NOT be added here by ITD
  public void ping() {
    System.out.println("No controller ping");
  }

  @Disable
  public void pingDisabled() {
    System.out.println("No controller pingDisabled");
  }
}

Controller class and driver application:

package de.scrum_master.stackoverflow.q73270343;

import org.springframework.stereotype.Controller;

@Controller
public class ControllerPing {
  // @Trace annotation should be added here by ITD
  public void ping() {
    System.out.println("Controller ping");
  }

  @Disable
  public void pingDisabled() {
    System.out.println("Controller pingDisabled");
  }

  public static void main(String[] args) {
    new ControllerPing().ping();
    new ControllerPing().pingDisabled();
    new NoControllerPing().ping();
    new NoControllerPing().pingDisabled();
  }
}

Native syntax AspectJ aspect:

package de.scrum_master.stackoverflow.q73270343;

public aspect TracingAspect {
  declare @method : !@Disable public !static * (@org.springframework.stereotype.Controller *..*).*(..) : @Trace; 
 
  before() : @annotation(Trace) && execution(* *(..)) {
    System.out.println(thisJoinPoint);
  }
}

Console log:

execution(void de.scrum_master.stackoverflow.q73270343.ControllerPing.ping())
Controller ping
Controller pingDisabled
No controller ping
No controller pingDisabled

See AspectJ manual.

BTW is it possible to use pure java here (TracingAspect.java) and not as .aj file?

No, it is not. Quote from the AspectJ manual:

Inter-type declarations are challenging to support using an annotation style. For code style aspects compiled with the ajc compiler, the entire type system can be made aware of inter-type declarations (new supertypes, new methods, new fields) and the completeness and correctness of it can be guaranteed. Achieving this with an annotation style is hard because the source code may simply be compiled with javac where the type system cannot be influenced and what is compiled must be 'pure java'.

You only have @DeclareParents, @DeclareMixin, @DeclarePrecedence, @DeclareWarning, @DeclareError at your disposal. See also the AspectJ 5 quick reference, page 4.

Upvotes: 1

Related Questions