skyho
skyho

Reputation: 1903

AspectJ (Spring) + how to add the embedding of an aspect in a private method and get the specified data

I have created an aspect that should embed logging on methods marked with an annotation and with the private modifier.

In addition, I would like to add information to the log that will be available at the time of execution of the method (for example, the object with which the method works and the name of the method and the class with which it is currently working).


        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.5.3</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    ...
    
        <properties>
            <java.version>11</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-rest</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-cache</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.github.ben-manes.caffeine</groupId>
                <artifactId>caffeine</artifactId>
                <version>3.0.3</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
                <version>5.3.9</version>
                <scope>compile</scope>
            </dependency>
    
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.9.5</version>
                <scope>compile</scope>
            </dependency>
    
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
                <version>1.9.5</version>
            </dependency>
    
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
        </dependencies>


    public class UserController {
    
        private final UserService userService;
    
        public UserController(UserService userService) {
            this.userService = userService;
        }
    
        @GetMapping
        public List<User> getUsers() {
    
            List<User> userList = getUsersInternal();
    
            return userList;
        }
    
        @AuditAnnotation()
        private List<User> getUsersInternal() {
    
            List<User> allUsers = userService.getAllUsers();
            return allUsers;
        }
    }


    @Retention(RUNTIME)
    @Target(METHOD)
    @Documented
    public @interface AuditAnnotation {
    
        public String nameMethod() default "";
    }


    public interface LoggingService {
    
        void log(String message);
    }

    /**
     * A dummy implementation of logging service,
     * just to inject it in {@link com.aspectj.in.spring.boot.aop.aspect.auditlog.interceptor.LoggingInterceptorAspect}
     * that's managed by AspectJ
     */
    @Service
    public class DefaultLoggingService implements LoggingService {
    
        private static final Logger logger = LoggerFactory.getLogger("sample-spring-aspectj");
    
        @Override
        public void log(String message) {
            logger.info(message);
        }
    }


    @Aspect
    @Component
    public class LoggingInterceptorAspect {
    
        @Autowired
        private LoggingService loggingService;
    
        @Pointcut("execution(private * *(..))")
        public void privateMethod() {}

        @Pointcut("@annotation(com.aspectj.in.spring.boot.aop.aspect.auditlog.annotation.AuditAnnotation)")
        public void annotatedMethodCustom() {}

    
        @Before("annotatedMethodCustom() && privateMethod()")
        public void addCommandDetailsToMessage() throws Throwable {
    
            ZonedDateTime dateTime = ZonedDateTime.now(ZoneOffset.UTC);
            String message = String.format("User controller getUsers method called at %s", dateTime);
    
            System.out.println("+++++++++++++++++++++++++");
            loggingService.log(message);
        }
    }


    @Configuration
    public class LoggingInterceptorConfig {
    
        @Bean
        public LoggingInterceptorAspect getAutowireCapableLoggingInterceptor() {
    
            return Aspects.aspectOf(LoggingInterceptorAspect.class);
        }
    }


    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public abstract class AspectjInSpringBootApplicationTests {

   @Autowired
   protected TestRestTemplate testRestTemplate;

}


    class UserControllerTest extends AspectjInSpringBootApplicationTests {

    @Test
    void getUsers() {

        String url = "/v1/users";

        ParameterizedTypeReference<List<User>> typeReference =
                new ParameterizedTypeReference<>() {
                };

        ResponseEntity<List<User>> responseEntity =
                testRestTemplate.exchange(url, HttpMethod.GET, null, typeReference);

        HttpStatus statusCode = responseEntity.getStatusCode();

        assertThat(statusCode, is(HttpStatus.OK));

        List<User> employeeDtoList = responseEntity.getBody();

        System.out.println(employeeDtoList);

    }

 }


But at the moment I have no errors .

so far, I see that the aspect is embedded,

enter image description here

but I want it to be detailed so that the aspect is universal and I would not have to explicitly specify in the message in which class it works.

Maybe someone has ideas on how to fix it.

Upvotes: 0

Views: 1163

Answers (1)

kriegaex
kriegaex

Reputation: 67437

I suggest you to explore the AspectJ documentation and the JoinPoint API, too. Here is a little example in stand-alone AspectJ without Spring. You can adjust it to your needs:

package de.scrum_master.app;

public class Application {
  public static void main(String[] args) {
    Application application = new Application();
    System.out.println(application.add(4, application.multiply(2, 3)));
    application.divide(5, 0);
  }

  private int add(int i, int j) {
    return i + j;
  }

  private int multiply(int i, int j) {
    return i * j;
  }

  private double divide(int i, int j) {
    return i / j;
  }
}
package de.scrum_master.aspect;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspect {
  @Before("execution(private * *(..))")
  public void beforeAdvice(JoinPoint joinPoint) {
    System.out.println(joinPoint);
    System.out.println("  Signature: " + joinPoint.getSignature());
    System.out.println("  Target: " + joinPoint.getTarget());
    System.out.println("  Arguments: " + Arrays.deepToString(joinPoint.getArgs()));
  }

  @AfterReturning(pointcut = "execution(private * *(..))", returning = "result")
  public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
    System.out.println("  Result: " + result);
  }

  @AfterThrowing(pointcut = "execution(private * *(..))", throwing = "exception")
  public void afterThrowingAdvice(JoinPoint joinPoint, Throwable exception) {
    System.out.println("  Exception: " + exception);
  }
}

Console log:

Picked up _JAVA_OPTIONS: -Djava.net.preferIPv4Stack=true
execution(int de.scrum_master.app.Application.multiply(int, int))
  Signature: int de.scrum_master.app.Application.multiply(int, int)
  Target: de.scrum_master.app.Application@7cdbc5d3
  Arguments: [2, 3]
  Result: 6
execution(int de.scrum_master.app.Application.add(int, int))
  Signature: int de.scrum_master.app.Application.add(int, int)
  Target: de.scrum_master.app.Application@7cdbc5d3
  Arguments: [4, 6]
  Result: 10
10
execution(double de.scrum_master.app.Application.divide(int, int))
  Signature: double de.scrum_master.app.Application.divide(int, int)
  Target: de.scrum_master.app.Application@7cdbc5d3
  Arguments: [5, 0]
  Exception: java.lang.ArithmeticException: / by zero
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at de.scrum_master.app.Application.divide(Application.java:19)
    at de.scrum_master.app.Application.main(Application.java:7)

In addition to the context data I printed here, you can also get annotations and their properties, method parameter names (even though I think that is unnecessary and availability depends on compilation options) etc.

Upvotes: 1

Related Questions