From 1a1d17e13ad09c61c2bb23f43d366ee00b1e8bfe Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 26 Oct 2016 15:03:14 +0200 Subject: [PATCH] LiveBeansView does not rely on application contexts order anymore This commit makes sure that the `unregister` order of registered application contexts has no incidence on the removal of the LiveBeansView MBean. Rather than using the last application context's name to compute the identity of the MBean to remove, the identity is stored when the MBean is created. This commit also adds missing tests. Issue: SPR-14848 --- .../context/support/LiveBeansView.java | 9 +- .../context/support/LiveBeansViewTests.java | 121 ++++++++++++++++++ 2 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 spring-context/src/test/java/org/springframework/context/support/LiveBeansViewTests.java diff --git a/spring-context/src/main/java/org/springframework/context/support/LiveBeansView.java b/spring-context/src/main/java/org/springframework/context/support/LiveBeansView.java index abac5b2711d..8e539bade47 100644 --- a/spring-context/src/main/java/org/springframework/context/support/LiveBeansView.java +++ b/spring-context/src/main/java/org/springframework/context/support/LiveBeansView.java @@ -44,6 +44,7 @@ import org.springframework.util.StringUtils; * Spring Tool Suite 3.1 and higher. * * @author Juergen Hoeller + * @author Stephane Nicoll * @since 3.2 * @see #getSnapshotAsJson() * @see org.springframework.web.context.support.LiveBeansViewServlet @@ -57,6 +58,7 @@ public class LiveBeansView implements LiveBeansViewMBean, ApplicationContextAwar private static final Set applicationContexts = new LinkedHashSet(); + private static String applicationName; static void registerApplicationContext(ConfigurableApplicationContext applicationContext) { String mbeanDomain = applicationContext.getEnvironment().getProperty(MBEAN_DOMAIN_PROPERTY_NAME); @@ -65,8 +67,9 @@ public class LiveBeansView implements LiveBeansViewMBean, ApplicationContextAwar if (applicationContexts.isEmpty()) { try { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + applicationName = applicationContext.getApplicationName(); server.registerMBean(new LiveBeansView(), - new ObjectName(mbeanDomain, MBEAN_APPLICATION_KEY, applicationContext.getApplicationName())); + new ObjectName(mbeanDomain, MBEAN_APPLICATION_KEY, applicationName)); } catch (Exception ex) { throw new ApplicationContextException("Failed to register LiveBeansView MBean", ex); @@ -83,10 +86,12 @@ public class LiveBeansView implements LiveBeansViewMBean, ApplicationContextAwar try { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); String mbeanDomain = applicationContext.getEnvironment().getProperty(MBEAN_DOMAIN_PROPERTY_NAME); - server.unregisterMBean(new ObjectName(mbeanDomain, MBEAN_APPLICATION_KEY, applicationContext.getApplicationName())); + server.unregisterMBean(new ObjectName(mbeanDomain, MBEAN_APPLICATION_KEY, applicationName)); } catch (Exception ex) { throw new ApplicationContextException("Failed to unregister LiveBeansView MBean", ex); + } finally { + applicationName = null; } } } diff --git a/spring-context/src/test/java/org/springframework/context/support/LiveBeansViewTests.java b/spring-context/src/test/java/org/springframework/context/support/LiveBeansViewTests.java new file mode 100644 index 00000000000..358a56a0c3d --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/support/LiveBeansViewTests.java @@ -0,0 +1,121 @@ +/* + * Copyright 2002-2016 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 + * + * http://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.context.support; + +import java.lang.management.ManagementFactory; +import java.util.Set; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.mock.env.MockEnvironment; + +import static org.junit.Assert.*; +import static org.mockito.BDDMockito.*; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link LiveBeansView} + * + * @author Stephane Nicoll + */ +public class LiveBeansViewTests { + + @Rule + public TestName name = new TestName(); + + private final MockEnvironment environment = new MockEnvironment(); + + @Test + public void registerIgnoredIfPropertyIsNotSet() throws MalformedObjectNameException { + ConfigurableApplicationContext context = createApplicationContext("app"); + assertEquals(0, searchLiveBeansViewMeans().size()); + LiveBeansView.registerApplicationContext(context); + assertEquals(0, searchLiveBeansViewMeans().size()); + LiveBeansView.unregisterApplicationContext(context); + } + + @Test + public void registerUnregisterSingleContext() throws MalformedObjectNameException { + this.environment.setProperty(LiveBeansView.MBEAN_DOMAIN_PROPERTY_NAME, this.name.getMethodName()); + ConfigurableApplicationContext context = createApplicationContext("app"); + assertEquals(0, searchLiveBeansViewMeans().size()); + LiveBeansView.registerApplicationContext(context); + assertSingleLiveBeansViewMbean("app"); + LiveBeansView.unregisterApplicationContext(context); + assertEquals(0, searchLiveBeansViewMeans().size()); + } + + @Test + public void registerUnregisterServeralContexts() throws MalformedObjectNameException { + this.environment.setProperty(LiveBeansView.MBEAN_DOMAIN_PROPERTY_NAME, this.name.getMethodName()); + ConfigurableApplicationContext context = createApplicationContext("app"); + ConfigurableApplicationContext childContext = createApplicationContext("child"); + assertEquals(0, searchLiveBeansViewMeans().size()); + LiveBeansView.registerApplicationContext(context); + assertSingleLiveBeansViewMbean("app"); + LiveBeansView.registerApplicationContext(childContext); + assertEquals(1, searchLiveBeansViewMeans().size()); // Only one MBean + LiveBeansView.unregisterApplicationContext(childContext); + assertSingleLiveBeansViewMbean("app"); // Root context removes it + LiveBeansView.unregisterApplicationContext(context); + assertEquals(0, searchLiveBeansViewMeans().size()); + } + + @Test + public void registerUnregisterServeralContextsDifferentOrder() throws MalformedObjectNameException { + this.environment.setProperty(LiveBeansView.MBEAN_DOMAIN_PROPERTY_NAME, this.name.getMethodName()); + ConfigurableApplicationContext context = createApplicationContext("app"); + ConfigurableApplicationContext childContext = createApplicationContext("child"); + assertEquals(0, searchLiveBeansViewMeans().size()); + LiveBeansView.registerApplicationContext(context); + assertSingleLiveBeansViewMbean("app"); + LiveBeansView.registerApplicationContext(childContext); + assertSingleLiveBeansViewMbean("app"); // Only one MBean + LiveBeansView.unregisterApplicationContext(context); + LiveBeansView.unregisterApplicationContext(childContext); + assertEquals(0, searchLiveBeansViewMeans().size()); + } + + private ConfigurableApplicationContext createApplicationContext(String applicationName) { + ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class); + given(context.getEnvironment()).willReturn(this.environment); + given(context.getApplicationName()).willReturn(applicationName); + return context; + } + + public void assertSingleLiveBeansViewMbean(String applicationName) throws MalformedObjectNameException { + Set objectNames = searchLiveBeansViewMeans(); + assertEquals(1, objectNames.size()); + assertEquals("Wrong MBean name", + String.format("%s:application=%s", this.name.getMethodName(), applicationName), + objectNames.iterator().next().getCanonicalName()); + + } + + private Set searchLiveBeansViewMeans() + throws MalformedObjectNameException { + String objectName = String.format("%s:*,%s=*", this.name.getMethodName(), + LiveBeansView.MBEAN_APPLICATION_KEY); + return ManagementFactory.getPlatformMBeanServer().queryNames(new ObjectName(objectName), null); + } + +} \ No newline at end of file