springspring-bootaspectjspring-aopaspect

Annotations with Spring Boot AOP do not work


I have been trying to get Spring AOP to work for the last day or two and have looked at many different resources but can't seem to get it working. I must be missing something obvious but I am spinning my wheels. Here is what I currently have.

My custom annotation:

CheckFeatureEnabled:

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckFeatureEnabled {
  String propertyName() default "";
}

My Aspect: CheckFeatureEnabledAspect:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
@Component
public class CheckFeatureEnabledAspect {

   // I have tried both short and fully qualified, neither seem to work in my project
  @Pointcut("@annotation(com.example.CheckFeatureEnabled)") 
  public void annotatedWithCheckFeatureEnabled() {}

  @Before("annotatedWithCheckFeatureEnabled() && @annotation(checkFeatureEnabled)")
  public void beforeMethodWithCheckFeatureEnabled(CheckFeatureEnabled checkFeatureEnabled) {
    String propertyName = checkFeatureEnabled.propertyName();
    System.out.println("AspectJ: Intercepted method with property: " + propertyName);

    // Add your logic here
    // Example: Check if the feature is enabled based on the propertyName
    // boolean featureEnabled = checkFeature(propertyName);
    // if (!featureEnabled) {
    //     throw new RuntimeException("Feature " + propertyName + " is not enabled!");
    // }
  }

My method that should be advised:

import com.example.InjectableProperties;
import com.google.inject.Inject;
import org.springframework.stereotype.Component;

@Component
public class AppHomeFeatureEnabler {
  private final InjectableProperties properties;

  @Inject
  public AppHomeFeatureEnabler(InjectableProperties properties) {
    this.properties = properties;
  }

  @CheckFeatureEnabled(propertyName = "test")
  public boolean isEnabled() {
    return this.properties.getBoolean("AppHome.enabled").orElse(false);
  }
}

I have added @EnableAspectJAutoProxy to my @SpringBootApplication class and added the dependencies (both with AspectJ and not)

pom.xml

        <!-- Spring AOP -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!-- AspectJ Runtime -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
        </dependency>

        <!-- AspectJ Weaver (for load-time weaving) -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

Any ideas what I might be missing here that would cause this not to work? If I swap out my custom annotation with something like @Deprecated, it seems to work during boot. But I wasn't able to get any other annotation working after Spring fully starts up so it seems like something there but I can't figure it out.

For example, trying @Deprecated in my App runtime:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class CheckFeatureEnabledAspect {

  @Pointcut("execution(* com.example..*(..)) && @annotation(java.lang.Deprecated)")
  public void deprecatedMethodWithinApp() {}

  // Advice to be executed before the annotated method is called
  @Before("deprecatedMethodWithinApp()")
  public void beforeDeprecatedMethod() {
    System.out.println("Warning: A deprecated method is being executed within your application!");
  }
}

Does not work, but if I do just @Pointcut("@annotation(java.lang.Deprecated)") it works when starting the app (but does throw an error and kill the app ultimately)

Any suggestions as to what might be going wrong here?

Update 1:

So it looks like this is something about the way it is being called, I moved all the files over from a new project it's working in and it works here.

The way I am calling this is:

HomeOpenedHandler

public class HomeOpenedHandler
...
    public List<LayoutBlock> buildBlocks() {
    if (!new AppHomeFeatureEnabler(properties).isEnabled()) return List.of();
...
}

I also tried

@CheckFeatureEnabled(propertyName = "myFeature")
  public List<LayoutBlock> buildBlocks() {
...
}

Neither worked? If it helps I am building a Slack app, so I wonder if something is getting called outside of the Spring context? And thats why its not working.

AppHomeFeatureEnabler

@Component
public class AppHomeFeatureEnabler {

  @Autowired
  InjectableProperties properties;

  @Inject
  public AppHomeFeatureEnabler(InjectableProperties properties) {
    this.properties = properties;
  }

  @CheckFeatureEnabled(propertyName = "myFeature")
  public boolean isEnabled() {
    return this.properties.getBoolean("AppHome.enabled").orElse(false);
  }
}

Why is isEnabled() not working when calling it? But it works in a @RestController? calls it via a @GetMapping?


Solution

  • I your code, I just noticed this:

    new AppHomeFeatureEnabler(properties).isEnabled()
    

    I.e., you are creating an instance manually instead from a Spring context, e.g. via getBean(..). This way, Spring AOP cannot intercept any methods. Only native AspectJ could. Simply create instances via Spring, and you should be fine.