diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingsLifecycleController.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingsLifecycleController.java index d78738908..5f01ee48f 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingsLifecycleController.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingsLifecycleController.java @@ -19,6 +19,7 @@ package org.springframework.cloud.stream.binding; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.stream.Stream; import com.fasterxml.jackson.databind.ObjectMapper; @@ -44,12 +45,13 @@ public class BindingsLifecycleController { private final ObjectMapper objectMapper; public BindingsLifecycleController(List inputBindingLifecycles, - List outputBindingsLifecycles, ObjectMapper objectMapper) { + List outputBindingsLifecycles) { Assert.notEmpty(inputBindingLifecycles, "'inputBindingLifecycles' must not be null or empty"); this.inputBindingLifecycles = inputBindingLifecycles; this.outputBindingsLifecycles = outputBindingsLifecycles; - this.objectMapper = objectMapper; + this.objectMapper = new ObjectMapper(); //see https://github.com/spring-cloud/spring-cloud-stream/issues/2253 + // we need to use ObjectMapper that could not be modified by the user. } /** @@ -118,7 +120,7 @@ public class BindingsLifecycleController { * @return the list of {@link Binding}s */ @SuppressWarnings("unchecked") - public List> queryStates() { + public List> queryStates() { List> bindings = new ArrayList<>(gatherInputBindings()); bindings.addAll(gatherOutputBindings()); return this.objectMapper.convertValue(bindings, List.class); diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java index 5e8c52818..d5d26d07d 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java @@ -243,9 +243,8 @@ public class BindingServiceConfiguration { @Bean public BindingsLifecycleController bindingsLifecycleController(List inputBindingLifecycles, - List outputBindingsLifecycles, @Nullable ObjectMapper objectMapper) { - objectMapper = objectMapper == null ? new ObjectMapper() : objectMapper; - return new BindingsLifecycleController(inputBindingLifecycles, outputBindingsLifecycles, objectMapper); + List outputBindingsLifecycles) { + return new BindingsLifecycleController(inputBindingLifecycles, outputBindingsLifecycles); } @Bean diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/endpoint/ActuatorBindingsTest.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/endpoint/ActuatorBindingsTest.java new file mode 100644 index 000000000..5164bfd23 --- /dev/null +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/endpoint/ActuatorBindingsTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.stream.endpoint; + +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration; +import org.springframework.cloud.stream.binding.BindingsLifecycleController; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + */ +public class ActuatorBindingsTest { + + /* + * Even though this test performs some simple assertions, the main purpose for it is to validate that + * it does not result in recursive exception described in https://github.com/spring-cloud/spring-cloud-stream/issues/2253 + */ + @Test + public void test_2253() { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder( + TestChannelBinderConfiguration.getCompleteConfiguration(Bindings.class)) + .web(WebApplicationType.NONE).run("--spring.jmx.enabled=false", + "--spring.cloud.function.definition=consume", + "--spring.jackson.visibility.field=ANY" // see https://github.com/spring-cloud/spring-cloud-stream/issues/2253 + // we need the above just to verify that such action does not + // interfere with instance of ObjectMapper inside of BindingsLifecycleController + )) { + + BindingsLifecycleController controller = context + .getBean(BindingsLifecycleController.class); + List> bindings = controller.queryStates(); + assertThat(bindings.size()).isEqualTo(1); + assertThat(bindings.get(0).get("bindingName")).isEqualTo("consume-in-0"); + } + + } + + @EnableAutoConfiguration + public static class Bindings { + + @Bean + public Consumer consume() { + return message -> System.out.println("Received message " + message); + } + + } + +}