aopaspectjspring-aopspring-aspects

Spring AOP silently ignores pointcut errors instead of escalating them


I have one project using AOP - a sample program - which works. I created a project implementing AOP in (as far as I can see...) the same way which doesn't work.

pom.xml

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>6.0.11</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

AppConfig.java

@Configuration
@ComponentScan(basePackages = {
        "com.phil.cardgame.aspects",
        "com.phil.cardgame.service"
})
@EnableAspectJAutoProxy()
public class AppConfig {
}

EventAspect.java

@Aspect
@Component
public class EventAspect {
    @Autowired
    EventRepository eventRepository;

    //@Around("execute(* com.phil.cardgame.service.GameService.createGame())")
    //@Around("execute(* com.phil.cardgame.service.*.*(..))")
    @Around("invalid text")
    public long registerCreateGame(ProceedingJoinPoint joinPoint) throws Throwable{
        String action = joinPoint.getSignature().toString();
        long gameId = (long) joinPoint.proceed();
        eventRepository.addEvent(action,gameId,null);
        System.out.println(">>> got here");
        return gameId;
    }

In the EventAspect.java you can see the commented @Around advices I tried. The last one (invalid) produces no error. If I try the same in the working project I get an error: "Pointcut is not well-formed". So it looks like my EventAspect is not being inspected as an Aspect.

I've checked many other similar questions and I still can't see what I'm doing wrong - any ideas?


Solution

  • I cloned your project. Your aspect works. Despite being unrelated to your problem, just for fun let us

    package com.phil.cardgame.aspects;
    
    import com.phil.cardgame.model.Deck;
    import com.phil.cardgame.repository.EventRepository;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class EventAspect {
      @Autowired
      EventRepository eventRepository;
    
      @Pointcut("within(com.phil.cardgame.service.GameService)")
      public void inGameService() {}
    
      @AfterReturning(value = "inGameService() && execution(* createGame())", returning = "gameId")
      public void registerCreateGame(JoinPoint joinPoint, Long gameId) {
        addEvent(joinPoint, gameId, null);
      }
    
      @AfterReturning("inGameService() && execution(* *(..)) && args(gameId, playerId)")
      public void registerPlayerAction(JoinPoint joinPoint, long gameId, String playerId) {
        addEvent(joinPoint, gameId, playerId);
      }
    
      @AfterReturning("inGameService() && execution(* *(..)) && args(gameId, deck)")
      public void registerDeckAction(JoinPoint joinPoint, long gameId, Deck deck) {
        addEvent(joinPoint, gameId, null);
      }
    
      @AfterReturning("inGameService() && execution(* *(..)) && args(gameId)")
      public void registerGameAction(JoinPoint joinPoint, long gameId) {
        addEvent(joinPoint, gameId, null);
      }
    
      private void addEvent(JoinPoint joinPoint, long gameId, String playerId) {
        System.out.println(joinPoint);
        eventRepository.addEvent(joinPoint.getSignature().toString(), gameId, playerId);
      }
    }
    

    Now, change the main program like this:

    package com.phil.cardgame;
    
    import com.phil.cardgame.service.GameService;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ConfigurableApplicationContext;
    
    @SpringBootApplication
    public class CardgameApplication {
      public static void main(String[] args) {
        try (ConfigurableApplicationContext context = SpringApplication.run(CardgameApplication.class, args)) {
          GameService gameService = context.getBean(GameService.class);
          gameService.createGame();
          gameService.addDeckToGame(1, gameService.createDeck());
          gameService.addPlayerToGame(1, "jane");
          gameService.findGame(1);
        }
      }
    }
    

    The console log will show:

    execution(Long com.phil.cardgame.service.GameService.createGame())
    execution(void com.phil.cardgame.service.GameService.addDeckToGame(long,Deck))
    execution(Player com.phil.cardgame.service.GameService.addPlayerToGame(long,String))
    execution(Game com.phil.cardgame.service.GameService.findGame(long))
    

    To me, it looks like everything is working just fine.


    Update: In Spring Boot, your invalid pointcut syntax for something like @Around(crepe) is silently ignored, but if you wish to see the "Pointcut is not well-formed" log output, you can set

    logging.level.org.springframework.aop=debug
    

    in application.properties. Then, on the console you will see something like (stripped off timestamp):

    DEBUG 20508 --- [cardgame] [           main] o.s.a.aspectj.AspectJExpressionPointcut  : Pointcut parser rejected expression [crepe]: java.lang.IllegalArgumentException: Pointcut is not well-formed: expecting '(' at character position 0
    crepe
    ^
    

    Update 2: The reason your pointcut error escalates in your other application but not in your reproducer is that in the former you use Spring version < 6.1.8 without commit 617833b, "Defensively catch and log pointcut parsing exceptions". Previously, Spring was not catching and logging pointcut errors like it does in more recent versions. To reproduce the problem, simply switch to Spring Boot 3.2.5 or older in your POM.