Unable to get values from placeholders in BeanPostProcessor

Viewed 307

I am using Spring with a legacy Tomcat application (not Spring Boot) that comes with Spring Boot BOM 2.4.0, the question is similar to Spring Expression Language not working but I have a specific case.

If I have a dependent class with @Value("${spring.kafka.maximumRequestSize:15728640}") and the following

@Configuration
@Order
public class KafkaTracingDecorator implements  ApplicationContextAware {
    private KafkaTracing kafkaTracing;

    @Override
    public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {

        kafkaTracing = applicationContext.getBean(KafkaTracing.class);
    }
}

It works. The following also works, but I don't do anything on the post processor

@Configuration
@Order
public class KafkaTracingDecorator implements BeanPostProcessor, ApplicationContextAware {

    @Override
    public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {

        return bean;
    }

    @Override
    public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {

//        kafkaTracing = applicationContext.getBean(KafkaTracing.class);
    }
}

But when I try to implement the BeanPostProcessor to implement the decorator pattern like this

@Configuration
@Order
public class KafkaTracingDecorator implements BeanPostProcessor, ApplicationContextAware {

    private KafkaTracing kafkaTracing;

    @Override
    public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {

        if (bean instanceof KafkaProducer) {
            return kafkaTracing.producer((KafkaProducer) bean);
        } else if (bean instanceof KafkaConsumer) {
            return kafkaTracing.consumer((KafkaConsumer) bean);
        } else {
            return bean;
        }
    }

    @Override
    public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {

        kafkaTracing = applicationContext.getBean(KafkaTracing.class);
    }
}

I get Caused by: java.lang.NumberFormatException: For input string: "${spring.kafka.maximumRequestSize:15728640}" when the WebApplicationContext is being created.

This also happens even if I don't use the value so it's not going into the method

@Configuration
@Order
public class KafkaTracingDecorator implements BeanPostProcessor, ApplicationContextAware {

    private KafkaTracing kafkaTracing;

    @Override
    public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {

        return bean;
    }

    @Override
    public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {

        kafkaTracing = applicationContext.getBean(KafkaTracing.class);
    }
}

Here's a very simplified example that also fails the same way:

@Configuration
public class KafkaTracingDecorator implements BeanPostProcessor {

    @Value("${spring.kafka.maximumRequestSize:15728640}")
    private int maxRequestSize;

}

(Note this works as expected on Spring Boot, so it's likely something to do with the non-boot usage of Spring)

A partial stack trace that leads to the error does not list any custom code:

    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:660)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1413)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:601)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.registerBeanPostProcessors(PostProcessorRegistrationDelegate.java:270)
    at org.springframework.context.support.AbstractApplicationContext.registerBeanPostProcessors(AbstractApplicationContext.java:761)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:566)
    at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:401)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:292)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:103)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4689)

MVCE added: https://github.com/trajano/spring-beanpostproc-mvce linked to GitHub issue in case it is a bug https://github.com/spring-projects/spring-framework/issues/26571 rather than a usage issue.

1 Answers

You need to update the Decorator class by registering the PropertySourcesPlaceholderConfigurer bean to resolve your issue.

According to Spring Docs on PropertyPlaceholderConfigurer :

PropertyPlaceholderConfigurer is a child class of PlaceholderConfigurerSupport. This child class helps in resolving ${...} placeholders against local properties and/or system properties and environment variables.

Therefore, configuring this bean will load the application.properties from the classpath and it will be able to resolve ${..} used in @Value annotation to get the actual value set.

Updated Decorator Class :

@Configuration
public class Decorator implements BeanPostProcessor {
    
    @Value("${spring.kafka.maximumRequestSize}")
    private int maxRequestSize;
    
    @Bean
    public static PlaceholderConfigurerSupport placeholderConfigurerSupport() {
        PlaceholderConfigurerSupport support = new PropertySourcesPlaceholderConfigurer();
        support.setLocations(new ClassPathResource("application.properties"));
        return support;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(maxRequestSize);
        return bean;
    }
}

application.properties :

spring.kafka.maximumRequestSize=15728640

Console logs of Tomcat 8.5.57 running on AdoptedOpenJDK 11.0.6 :

...
.....
INFO: Initializing Spring root WebApplicationContext
Feb 20, 2021 9:21:41 AM org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization started
15728640
Feb 20, 2021 9:21:41 AM org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext initialized in 518 ms
Feb 20, 2021 9:21:41 AM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-nio-8080"]
Feb 20, 2021 9:21:41 AM org.apache.catalina.startup.Catalina start
INFO: Server startup in 1419 ms
Related