From 628c2cd0b17702dfaf3c2bf3a439409f674c7ba5 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 19 Jul 2022 21:37:47 +0100 Subject: [PATCH] Ensure JMX endpoints are uniquely named in a context hierarchy Closes gh-31718 --- .../jmx/JmxEndpointAutoConfiguration.java | 5 ++-- .../JmxEndpointAutoConfigurationTests.java | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java index 35e50b199b..51c5ca234a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -43,6 +43,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; @@ -86,7 +87,7 @@ public class JmxEndpointAutoConfiguration { } @Bean - @ConditionalOnMissingBean(EndpointObjectNameFactory.class) + @ConditionalOnMissingBean(value = EndpointObjectNameFactory.class, search = SearchStrategy.CURRENT) public DefaultEndpointObjectNameFactory endpointObjectNameFactory(MBeanServer mBeanServer, Environment environment) { String contextId = ObjectUtils.getIdentityHexString(this.applicationContext); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfigurationTests.java index eedf2db398..e963d0a921 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfigurationTests.java @@ -16,9 +16,13 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.jmx; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import java.util.function.Function; import javax.management.MBeanServer; +import javax.management.ObjectName; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -35,8 +39,11 @@ import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; /** * Tests for {@link JmxEndpointAutoConfiguration}. @@ -78,6 +85,26 @@ class JmxEndpointAutoConfigurationTests { }); } + @Test + void jmxEndpointWithContextHierarchyGeneratesUniqueNamesForEachEndpoint() throws Exception { + given(this.mBeanServer.queryNames(any(), any())) + .willReturn(new HashSet<>(Arrays.asList(new ObjectName("test:test=test")))); + ArgumentCaptor objectName = ArgumentCaptor.forClass(ObjectName.class); + this.contextRunner.withPropertyValues("spring.jmx.enabled=true").with(mockMBeanServer()).run((parent) -> { + this.contextRunner.withPropertyValues("spring.jmx.enabled=true").withParent(parent).run((child) -> { + }); + this.contextRunner.withPropertyValues("spring.jmx.enabled=true").withParent(parent).run((child) -> { + }); + }); + then(this.mBeanServer).should(times(3)).registerMBean(any(Object.class), objectName.capture()); + Set uniqueValues = new HashSet<>(objectName.getAllValues()); + assertThat(uniqueValues).hasSize(3); + assertThat(uniqueValues).allMatch((name) -> name.getDomain().equals("org.springframework.boot")); + assertThat(uniqueValues).allMatch((name) -> name.getKeyProperty("type").equals("Endpoint")); + assertThat(uniqueValues).allMatch((name) -> name.getKeyProperty("name").equals("Test")); + assertThat(uniqueValues).allMatch((name) -> name.getKeyProperty("context") != null); + } + private Function mockMBeanServer() { return (ctxRunner) -> ctxRunner.withBean("mbeanServer", MBeanServer.class, () -> this.mBeanServer); }