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.