0

Below is my custom annotation SkipForTestProfile which I created to skip execution for "it" profile but the SkipForTestProfileAspect code is not execuuted, i appreciate if someone can help me fix the Aspect to execute around the annotation @SkipForTestProfile

package com.mp.annotations;

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 SkipForTestProfile {
}

Below is the aspect

package com.mp.aspect;


import java.util.Arrays;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import com.mp.annotations.SkipForTestProfile;

@Aspect
@Component
@Order(1) // Define the order of aspect execution
public class SkipForTestProfileAspect {

    @Autowired
    private Environment environment;

    @Around("@annotation(skipForTestProfile)")
    public Object skipMethodForTestProfile(ProceedingJoinPoint joinPoint, SkipForTestProfile skipForTestProfile) throws Throwable {
        if(isTestProfile()){
            return null;
        } else{
            return joinPoint.proceed();
        }
    }

    private boolean isTestProfile() {
        return Arrays.stream(environment.getActiveProfiles()).anyMatch(s -> s.equals("it"));
    }
}

Below is my custom annotation usage

package com.mp.healthIndicator;

@Component
@Slf4j
@Profile("!it")// this is not working in my case hence created custom annotation
public class HeartBeat{


    @PostConstruct
    @SkipForTestProfile
    protected void init(){          
        //do health check
    }
}
OTUser
  • 3,788
  • 19
  • 69
  • 127
  • `@Profile("!it")` works just fine for me, if I try to recreate your situation. Why would you need an extra aspect? The bean is not even created with an inactive profile `it`, therefore you do not need to skip its post-construct method either. – kriegaex Jun 11 '23 at 08:08
  • @kriegaex As mentioned in the question I have a different setup in my project and @Profile("!it") not working for me hence I need the custom annotation to make it work – OTUser Jun 13 '23 at 01:47
  • 1
    You are making a claim without proof. I copied and tried your code, and `@Profile` works nicely. If your sitution is different,please publish an [MCVE](https://stackoverflow.com/help/mcve) reproducing the situation, ideally a Maven project on GitHub. Before I explain to you why your custom annotation approach does not work and what the alternative would be, let us try to solve the problem with on-board means and find out why for you same are not working. – kriegaex Jun 13 '23 at 07:45
  • I do not find it particularly polite to ignore a comprehensive and extensive answer for weeks. You asked a question, I spent time trying to answer it. I would appreciate some feedbacks. – kriegaex Jul 09 '23 at 07:50

1 Answers1

0

With Spring AOP it is impossible to intercept a bean lifecycle method with an aspect. In order to achieve that, you would have to explicitly call the method on the bean instance, which is not what you want, because Spring does that for you internally. When debugging, you will also see that HeartBeat's lifecycle method has long since run before the aspect becomes active. You could mark HeartBeat as @Lazy, then the aspect bean is wired first, but still that does not mean you can intercept the post-construct method with Spring AOP.

Like I commented before, I see no reason why @Profile would not work. For me, it works beautifully. Chances are, that your Spring config needs fixing. You still did not provide any reproducer for your problem.

If you wish to intercept the post-construct method with an aspect, you need a more powerful AOP tool than Spring AOP, namely native AspectJ. I tried with load-time weaving, and it works, if configured correctly. The code looks like this:

package de.scrum_master.spring.q76435875;

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 SkipForTestProfile {}
package de.scrum_master.spring.q76435875;

import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
@Lazy
public class HeartBeat {
  @PostConstruct
  @SkipForTestProfile
  protected void init() {
    System.out.println("Doing health check");
  }
}

Please note that you need @Lazy, otherwise you cannot inject the Environment into the native aspect on time before HeartBeat.init() executes.

Without @Lazy, you have to parse the system property spring.profiles.active manually. That is also possible.

package de.scrum_master.spring.q76435875;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.core.env.Environment;

import java.util.Arrays;

@Aspect
//@Component
@Configurable
public class SkipForTestProfileAspect {
  @Autowired
  private Environment environment;

  @Around("@annotation(skipForTestProfile) && execution(* *(..))")
  public Object skipMethodForTestProfile(ProceedingJoinPoint joinPoint, SkipForTestProfile skipForTestProfile) throws Throwable {
    System.out.println(joinPoint + " ->  test profile " + (isTestProfile() ? "active" : "inactive"));
    return isTestProfile() ? null : joinPoint.proceed();
  }

  private boolean isTestProfile() {
    return Arrays.asList(environment.getActiveProfiles()).contains("it");
  }
}

Please note that a native AspectJ aspect should not carry any @Component annotation, because it is not a Spring component. You do, however, need @Configurable (from spring-aspects) to be able to inject the Spring environment.

Again, that is unnecessary, if you parse the system property manually.

package de.scrum_master.spring.q76435875;

import org.aspectj.lang.Aspects;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableLoadTimeWeaving;
import org.springframework.context.annotation.aspectj.EnableSpringConfigured;

@SpringBootApplication
@Configuration
//@EnableAspectJAutoProxy
@EnableLoadTimeWeaving
@EnableSpringConfigured
public class DemoApplication {
  public static void main(String[] args) {
    try (ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args)) {
      HeartBeat heartBeat = context.getBean(HeartBeat.class);
    }
  }
}

In this driver application, you see that we need @EnableLoadTimeWeaving rather than @EnableAspectJAutoProxy for native load-time weaving. We also need @EnableSpringConfigured in order to auto-wire a Spring bean (the environment) into a non-Spring component (the aspect).

We also explicitly instantiate a HeartBeat bean in order to see the effect of the aspect.

Now start the application with JVM parameters (on a single line)

--add-opens java.base/java.lang=ALL-UNNAMED
-javaagent:/path/to/aspectjweaver-1.9.19.jar
-javaagent:/path/to/spring-instrument-5.3.16.jar
-Dspring.profiles.active=it

to see log output like

execution(void de.scrum_master.spring.q76435875.HeartBeat.init()) ->  test profile active

Without the active it profile, it says:

execution(void de.scrum_master.spring.q76435875.HeartBeat.init()) ->  test profile inactive
Doing health check

Is that really worth the effort? Would it not be better to get @Profile and Spring AOP working?


Update: If you want to work without @EnableSpringConfigured, @Configurable and @Lazy, simply instantiate a StandardEnvironment by yourself inside the aspect, and the sitution is simplified as follows:

package de.scrum_master.spring.q76435875;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.env.Environment;
import org.springframework.core.env.StandardEnvironment;

import java.util.Arrays;

@Aspect
//@Component
public class SkipForTestProfileAspect {
  private Environment environment = new StandardEnvironment();

  @Around("@annotation(skipForTestProfile) && execution(* *(..))")
  public Object skipMethodForTestProfile(ProceedingJoinPoint joinPoint, SkipForTestProfile skipForTestProfile) throws Throwable {
    System.out.println(joinPoint + " ->  test profile " + (isTestProfile() ? "active" : "inactive"));
    return isTestProfile() ? null : joinPoint.proceed();
  }

  private boolean isTestProfile() {
    return Arrays.asList(environment.getActiveProfiles()).contains("it");
  }
}
package de.scrum_master.spring.q76435875;

import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class HeartBeat {
  @PostConstruct
  @SkipForTestProfile
  protected void init() {
    System.out.println("Doing health check");
  }
}
package de.scrum_master.spring.q76435875;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableLoadTimeWeaving;

@SpringBootApplication
@Configuration
//@EnableAspectJAutoProxy
@EnableLoadTimeWeaving
public class DemoApplication {
  public static void main(String[] args) {
    try (ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args)) {
//      HeartBeat heartBeat = context.getBean(HeartBeat.class);
    }
  }
}

In this configuration, it is also no longer necessary to instantiate the HeartBeat bean in the application starter in order to see the aspect log, because there is no @Lazy bean instantiation anymore. Everything works automatically during application start.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • Please note my update, describing an alternative approach without `Environment` injection but with explicit instantiation. It is a bit more lightweight. – kriegaex Jun 15 '23 at 13:51