I want to conditionally enable aspect like this:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        ApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
        TestService testService = ctx.getBean(TestService.class);
        testService.test();
    }

    @Bean
    TestService testService() {
        return new TestService();
    }

    @Bean
    TestAspect testAspect() {
        boolean enabled = false; // TODO
        return enabled ? new TestAspect() : null;
    }

    @Aspect
    public static class TestAspect {

        @Around("execution(public * test(..))")
        public Object test(ProceedingJoinPoint pjp) throws Throwable {
            return pjp.proceed();
        }
    }

    public static class TestService {

        public void test() {

        }
    }
}

It will raise exception when proxied method invoked:

Exception in thread "main" org.springframework.aop.AopInvocationException: Mismatch on arguments to advice method [public java.lang.Object com.example.demo.DemoApplication$TestAspect.test(org.aspectj.lang.ProceedingJoinPoint) throws java.lang.Throwable]; pointcut expression [org.aspectj.weaver.internal.tools.PointcutExpressionImpl@51e1e058]
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:647)
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:632)
    at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728)
    at com.example.demo.DemoApplication$TestService$$SpringCGLIB$$0.test(<generated>)
    at com.example.demo.DemoApplication.main(DemoApplication.java:18)
Caused by: java.lang.IllegalArgumentException: object is not an instance of declaring class
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.checkReceiver(DirectMethodHandleAccessor.java:197)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:99)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:642)
    ... 8 more

If the proposal is accepted, please note that application context failed to initialize if the aspect implements Ordered:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testService' defined in com.example.demo.DemoApplication: class org.springframework.beans.factory.support.NullBean cannot be cast to class org.springframework.core.Ordered (org.springframework.beans.factory.support.NullBean and org.springframework.core.Ordered are in unnamed module of loader 'app')
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:614) ~[spring-beans-6.2.7.jar:6.2.7]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) ~[spring-beans-6.2.7.jar:6.2.7]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.7.jar:6.2.7]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) ~[spring-beans-6.2.7.jar:6.2.7]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.7.jar:6.2.7]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.7.jar:6.2.7]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1222) ~[spring-beans-6.2.7.jar:6.2.7]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1188) ~[spring-beans-6.2.7.jar:6.2.7]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1123) ~[spring-beans-6.2.7.jar:6.2.7]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987) ~[spring-context-6.2.7.jar:6.2.7]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) ~[spring-context-6.2.7.jar:6.2.7]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.5.0.jar:3.5.0]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:753) ~[spring-boot-3.5.0.jar:3.5.0]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.5.0.jar:3.5.0]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) ~[spring-boot-3.5.0.jar:3.5.0]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1362) ~[spring-boot-3.5.0.jar:3.5.0]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1351) ~[spring-boot-3.5.0.jar:3.5.0]
    at com.example.demo.DemoApplication.main(DemoApplication.java:16) ~[main/:na]
Caused by: java.lang.ClassCastException: class org.springframework.beans.factory.support.NullBean cannot be cast to class org.springframework.core.Ordered (org.springframework.beans.factory.support.NullBean and org.springframework.core.Ordered are in unnamed module of loader 'app')
    at org.springframework.aop.aspectj.annotation.BeanFactoryAspectInstanceFactory.getOrder(BeanFactoryAspectInstanceFactory.java:133) ~[spring-aop-6.2.7.jar:6.2.7]
    at org.springframework.aop.aspectj.annotation.LazySingletonAspectInstanceFactoryDecorator.getOrder(LazySingletonAspectInstanceFactoryDecorator.java:95) ~[spring-aop-6.2.7.jar:6.2.7]
    at org.springframework.aop.aspectj.annotation.InstantiationModelAwarePointcutAdvisorImpl.getOrder(InstantiationModelAwarePointcutAdvisorImpl.java:184) ~[spring-aop-6.2.7.jar:6.2.7]
    at org.springframework.core.OrderComparator.findOrder(OrderComparator.java:145) ~[spring-core-6.2.7.jar:6.2.7]
    at org.springframework.core.annotation.AnnotationAwareOrderComparator.findOrder(AnnotationAwareOrderComparator.java:64) ~[spring-core-6.2.7.jar:6.2.7]
    at org.springframework.core.OrderComparator.getOrder(OrderComparator.java:128) ~[spring-core-6.2.7.jar:6.2.7]
    at org.springframework.core.OrderComparator.getOrder(OrderComparator.java:116) ~[spring-core-6.2.7.jar:6.2.7]
    at org.springframework.core.OrderComparator.doCompare(OrderComparator.java:86) ~[spring-core-6.2.7.jar:6.2.7]
    at org.springframework.core.OrderComparator.compare(OrderComparator.java:73) ~[spring-core-6.2.7.jar:6.2.7]
    at org.springframework.aop.aspectj.autoproxy.AspectJPrecedenceComparator.compare(AspectJPrecedenceComparator.java:83) ~[spring-aop-6.2.7.jar:6.2.7]
    at org.springframework.aop.aspectj.autoproxy.AspectJPrecedenceComparator.compare(AspectJPrecedenceComparator.java:51) ~[spring-aop-6.2.7.jar:6.2.7]
    at org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder.compareTo(AspectJAwareAdvisorAutoProxyCreator.java:143) ~[spring-aop-6.2.7.jar:6.2.7]
    at org.aspectj.util.PartialOrder$SortObject.addDirectedLinks(PartialOrder.java:71) ~[aspectjweaver-1.9.24.jar:na]
    at org.aspectj.util.PartialOrder.addNewPartialComparable(PartialOrder.java:92) ~[aspectjweaver-1.9.24.jar:na]
    at org.aspectj.util.PartialOrder.sort(PartialOrder.java:128) ~[aspectjweaver-1.9.24.jar:na]
    at org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator.sortAdvisors(AspectJAwareAdvisorAutoProxyCreator.java:79) ~[spring-aop-6.2.7.jar:6.2.7]
    at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findEligibleAdvisors(AbstractAdvisorAutoProxyCreator.java:103) ~[spring-aop-6.2.7.jar:6.2.7]
    at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean(AbstractAdvisorAutoProxyCreator.java:80) ~[spring-aop-6.2.7.jar:6.2.7]
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:367) ~[spring-aop-6.2.7.jar:6.2.7]
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:319) ~[spring-aop-6.2.7.jar:6.2.7]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:445) ~[spring-beans-6.2.7.jar:6.2.7]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1829) ~[spring-beans-6.2.7.jar:6.2.7]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607) ~[spring-beans-6.2.7.jar:6.2.7]
    ... 17 common frames omitted

Comment From: sbrannen

I want to conditionally enable aspect like this:

Please note that returning null from an @Bean factory method is highly discouraged, even if it is tolerated.

Thus, why not use @Conditional (or one of the @Conditional variants from Spring Boot)?

Comment From: quaff

I want to conditionally enable aspect like this:

Please note that returning null from an @Bean factory method is highly discouraged, even if it is tolerated.

Thus, why not use @Conditional (or one of the @Conditional variants from Spring Boot)?

This way is more straight forward than @Conditional, I'm OK if the proposal is rejected.

Comment From: sbrannen

If you're not interested in using @Conditional support, have you considered using the programmatic bean registration feature coming in 7.0?

That would allow you to programmatically decide whether or not you want to register the TestAspect.

Comment From: quaff

If you're not interested in using @Conditional support, have you considered using the programmatic bean registration feature coming in 7.0?

That would allow you to programmatically decide whether or not you want to register the TestAspect.

I like @Conditional, and I'll try BeanRegistrar later, maybe we should repurpose this issue to produce meaningful error if @Aspect bean is null?

Comment From: sbrannen

maybe we should repurpose this issue to produce meaningful error if @Aspect bean is null?

This actually all gets triggered because AbstractAutowireCapableBeanFactory.initializeBean(...) attempts to initialize a NullBean.

If we were to avoid initialization of a NullBean, AbstractAutoProxyCreator.postProcessAfterInitialization(...) would not be invoked to begin with.

Note, however, that AbstractAutowireCapableBeanFactory.invokeInitMethods(...) in fact has a bean.getClass() != NullBean.class check.

So, I'm wondering if we shouldn't add a bean.getClass() != NullBean.class check to AbstractAutowireCapableBeanFactory.initializeBean(...).

@jhoeller, thoughts?

Comment From: quaff

maybe we should repurpose this issue to produce meaningful error if @Aspect bean is null?

This actually all gets triggered because AbstractAutowireCapableBeanFactory.initializeBean(...) attempts to initialize a NullBean.

If we were to avoid initialization of a NullBean, AbstractAutoProxyCreator.postProcessAfterInitialization(...) would not be invoked to begin with.

Note, however, that AbstractAutowireCapableBeanFactory.invokeInitMethods(...) in fact has a bean.getClass() != NullBean.class check.

So, I'm wondering if we shouldn't add a bean.getClass() != NullBean.class check to AbstractAutowireCapableBeanFactory.initializeBean(...).

@jhoeller, thoughts?

@sbrannen Not sure I'm following, there are two different issues here, object is not an instance of declaring class and casting NullBean to Ordered if aspect is ordered, from my investigation, your suggestion doesn't tackle either one.

Comment From: sbrannen

Not sure I'm following, there are two different issues here, object is not an instance of declaring class and casting NullBean to Ordered if aspect is ordered, from my investigation, your suggestion doesn't tackle either one.

Indeed, you're correct, @quaff.

I took "too quick" a glance and barked up the wrong tree, so to speak.

The issue seems to be that the @Aspect metadata is prepared before the @Aspect bean is instantiated. Thus, the Aspect metadata for your TestAspect is registered eagerly even though the instance is later an instance of NullBean instead of TestAspect.

BeanFactoryAspectInstanceFactory.getAspectInstance() is responsible for looking up the actual instance at runtime, when invoking an advice method on the aspect (in AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs()).

Thus, one option would be to throw a dedicated exception from BeanFactoryAspectInstanceFactory.getAspectInstance() if the actual bean type is NullBean.

I'll discuss our options with Juergen once he's back from vacation.

Comment From: quaff

Thus, one option would be to throw a dedicated exception from BeanFactoryAspectInstanceFactory.getAspectInstance() if the actual bean type is NullBean.

This option doesn't tackle ClassCastException, I created https://github.com/spring-projects/spring-framework/pull/35097 to improve it.

https://github.com/spring-projects/spring-framework/blob/12146c4a1b90d2c173c907fd9f898672b63bd338/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java#L132

Comment From: jhoeller

It turns out to be straightforward enough to skip a null bean as aspect instance in AbstractAspectJAdvice where we call getAspectInstance(). In combination with a refined variant of getOrder(), this leniently tolerates a null advice bean now.