spring-projects / spring-boot

Spring Boot
https://spring.io/projects/spring-boot
Apache License 2.0
74.39k stars 40.51k forks source link

Configuration properties enabled in a child context are ignored if an ancestor context has already enabled the properties #41487

Closed miladamery closed 1 month ago

miladamery commented 1 month ago

Hi, We have setup a spring boot application with context hierarchy according to this baeldung article. github Given code was pretty simple but expectation is that we can configure each context management.server.port in its own .properties file. but in action only management.server.port is being read only from parent .properties file. (article doesnt have one but in our app we gave a .properties for parent too). hence when one of contexts e.g. Ctx1 gets started and gets running on port for example 9090 (when we define a separate port for actuator) and management 9095 second context (Ctx2) runs on a separate web port e.g 9091 but management port is being set to 9095 for second context too. Or if we do not set any port in parent .properties file we get bellow error:

Caused by: java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties.getPort()" is null

In ManagementWebServerFactoryCustomizer we have:

@Override
public final void customize(T factory) {
        ManagementServerProperties managementServerProperties = BeanFactoryUtils
            .beanOfTypeIncludingAncestors(this.beanFactory, ManagementServerProperties.class);
        // Customize as per the parent context first (so e.g. the access logs go to
        // the same place)
        customizeSameAsParentContext(factory);
        // Then reset the error pages
        factory.setErrorPages(Collections.emptySet());
        // and add the management-specific bits
        ServerProperties serverProperties = BeanFactoryUtils.beanOfTypeIncludingAncestors(this.beanFactory,
                ServerProperties.class);
        customize(factory, managementServerProperties, serverProperties);
}

managementServerProperties reads from parent context (in case we didnt set and is null) and actual port is set in .properties file of child context, applies to serverProperties. here is customize

protected void customize(T factory, ManagementServerProperties managementServerProperties,
            ServerProperties serverProperties) {
        factory.setPort(managementServerProperties.getPort());
        Ssl ssl = managementServerProperties.getSsl();
        if (ssl != null) {
            factory.setSsl(ssl);
        }
        factory.setServerHeader(serverProperties.getServerHeader());
        factory.setAddress(managementServerProperties.getAddress());
    }

In here factory.setPort is being set only from managementServerProperties which in this case is null and actual port is set in serverProperties. and we get the

Caused by: java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties.getPort()" is null

error

wilkinsona commented 1 month ago

Thanks for the report but it does not include enough information for us to be able to help you. If you would like us to spend some more time investigating, please spend some time providing a complete yet minimal sample that reproduces the problem. You can share it with us by pushing it to a separate repository on GitHub or by zipping it up and attaching it to this issue.

miladamery commented 1 month ago

Thanks. Here is an example project that reproduces the problem actuator-port-problem.zip

The thing that I noticed in time providing this example is when @EnableAutoConfiguration is not present on ParentConfig nothing breaks and everything works fine. but in this example our parent has almost nothing real in it. in our production app our parent uses redis, database, kafka, rabbit etc and we need all those beans through spring boot autoconfiguration and if we don't put that annotation in parent we get an error for example

required a bean of type 'org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory' that could not be found.

Action:

Consider defining a bean of type 'org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory' in your configuration.
"}

meaning our redis didnt get configured, so removing @EnableAutoConfiguration from ParentConfig is not an option.

wilkinsona commented 1 month ago

Thanks for the sample. You can work around the problem by adding the following @Bean definition to both Ctx1Config and Ctx2Config:

@Bean(name = "management.server-org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties")
ManagementServerProperties managementServerProperties() {
    return new ManagementServerProperties();
}
wilkinsona commented 1 month ago

The management server settings in the child context are ignored due to https://github.com/spring-projects/spring-boot/commit/1061afbe005dd29b938c96e9ca3d20819985e1b3 which fixed https://github.com/spring-projects/spring-boot/issues/8187.

It's not clear to me what the underlying problem was that motivated #8187. When the properties in the parent and the child are the same, defining the same bean in the child is inefficient but I can't think of a scenario where it may break something. When the properties in the parent and child differ, as they do in the case described in this issue, not defining the bean in the child causes the child's properties to be ignored and for the parent's to be reused.

philwebb commented 1 month ago

We discussed this today and consider it a bug. We're going to try to revert #8187. This is a little risky so we're going to target 3.4.x and suggest the workaround for existing versions.