Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better error reporting for @Bean creating when bean instance has been replaced with an incompatible type #28897

Closed
junyongz opened this issue Aug 1, 2022 · 1 comment
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Milestone

Comments

@junyongz
Copy link

junyongz commented Aug 1, 2022

This would be even tougher to spot the error if the BeanPostProcessor instance is from other libraries.

12:43:19.439 [main] WARN org.springframework.context.annotation.AnnotationConfigApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'taskExecutor' defined in com.welcome.samples.junit.BeanPostProcessorComponentTest$CustomAsyncConfigurer: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [java.util.concurrent.Executor]: Illegal arguments to factory method 'getAsyncExecutor'; args: ; nested exception is java.lang.IllegalArgumentException: object is not an instance of declaring class

class BeanPostProcessorComponentTest {

    @Test
    void endToEnd() {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.register(CustomAsyncConfigurer.class, ProcessingAsyncConfigurerPostProcessor.class);
        ctx.refresh();
        ctx.close();
    }
    
    
    @Configuration
    public static class CustomAsyncConfigurer implements AsyncConfigurer {
        
        @Bean("taskExecutor")
        public Executor getAsyncExecutor() {
            return new ConcurrentTaskExecutor();
        }
    }

    public static class ProcessingAsyncConfigurerPostProcessor implements BeanPostProcessor {
        public Object postProcessBeforeInitialization(Object bean, String beanName)
                throws BeansException {
            if (bean instanceof AsyncConfigurer && !(bean instanceof FullyCustomAsyncConfigurer)) {
                return new FullyCustomAsyncConfigurer((AsyncConfigurer) bean);
            }
            
            return bean;
        }
    }
    
    public static class FullyCustomAsyncConfigurer extends AsyncConfigurerSupport {
        
        private AsyncConfigurer delegate;
        
        public FullyCustomAsyncConfigurer(AsyncConfigurer delegate) {
            this.delegate = delegate;
        }
        
        @Override
        public Executor getAsyncExecutor() {
            return new Executor() {
                
                @Override
                public void execute(Runnable command) {
                    FullyCustomAsyncConfigurer.this.delegate.getAsyncExecutor().execute(command);
                }
            };
        }
        
        @Override
        public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
            return this.delegate.getAsyncUncaughtExceptionHandler();
        }
    }

}
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Aug 1, 2022
@rstoyanchev rstoyanchev added the in: core Issues in core modules (aop, beans, core, context, expression) label Feb 9, 2023
@snicoll snicoll self-assigned this Oct 3, 2023
@snicoll
Copy link
Member

snicoll commented Oct 3, 2023

I agree that the exception could be improved but the fact that you replace a bean instance (CustomAsyncConfigurer) by an instance that is not assignable to it is really breaking the post-processor contract. FullyCustomAsyncConfigurer does not extend CustomAsyncConfigurer so the override of getAsyncExecutor is not overriding that method.

Looking at the catch block, it looks like it was primarily designed for providing an error report if the arguments specified for the method invocation has some sort of mismatch. We'll add an extra check there to throw a more specific exception so that we only pay the cost of the check if the exception is thrown.

@snicoll snicoll added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Oct 3, 2023
@snicoll snicoll added this to the 6.1.x milestone Oct 3, 2023
@snicoll snicoll changed the title Better error reporting for @Bean of factoryMethod from factoryBean that went through BeanPostProcessor Better error reporting for @Bean creating when bean instance has been replaced with an incompatible type Oct 3, 2023
@snicoll snicoll modified the milestones: 6.1.x, 6.1.0-RC2 Oct 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

4 participants