While working on a project with multi spring context (one parent and multi child) to expose actuators.
@SpringBootApplication(scanBasePackages = {"com.test.parent.app"})
public class ReproduceApplication {
public static void main(String[] args) {
SpringApplicationBuilder app =
new SpringApplicationBuilder(ReproduceApplication.class)
.bannerMode(Banner.Mode.LOG)
.web(WebApplicationType.NONE);
app.run(args);
app.child(ChildOneApplication.class)
.profiles("one")
.web(WebApplicationType.SERVLET)
.bannerMode(Banner.Mode.OFF)
.run(args);
app.child(ChildTwoApplication.class)
.web(WebApplicationType.NONE)
.profiles("two")
.bannerMode(Banner.Mode.OFF)
.run(args);
}
}
@SpringBootApplication(scanBasePackages = {"com.test.child.one"})
@EnableWebMvc
public class ChildOneApplication {
}
@SpringBootApplication(scanBasePackages = {"com.test.child.two"})
public class ChildTwoApplication {
}
spring-boot-actuator-2.6.5
It occurs the javax.management.InstanceAlreadyExistsException.
Connected to the target VM, address: '127.0.0.1:62526', transport: 'socket'
2022-07-14 17:11:23.299 INFO 8565 --- [ main] o.s.boot.SpringApplication :
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.6.5)
2022-07-14 17:11:23.341 INFO 8565 --- [ main] c.test.parent.app.ReproduceApplication : Starting ReproduceApplication using Java 11.0.2 ...
2022-07-14 17:11:23.342 INFO 8565 --- [ main] c.test.parent.app.ReproduceApplication : No active profile set, falling back to 1 default profile: "default"
2022-07-14 17:11:24.185 INFO 8565 --- [ main] c.test.parent.app.ReproduceApplication : Started ReproduceApplication in 1.15 seconds (JVM running for 1.885)
2022-07-14 17:11:24.220 INFO 8565 --- [ main] c.test.parent.app.ReproduceApplication : The following 1 profile is active: "one"
2022-07-14 17:11:24.717 INFO 8565 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 10010 (http)
2022-07-14 17:11:24.725 INFO 8565 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2022-07-14 17:11:24.725 INFO 8565 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.60]
2022-07-14 17:11:24.819 INFO 8565 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2022-07-14 17:11:24.819 INFO 8565 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 596 ms
2022-07-14 17:11:25.027 INFO 8565 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 2 endpoint(s) beneath base path '/actuator'
2022-07-14 17:11:25.061 INFO 8565 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 10010 (http) with context path ''
2022-07-14 17:11:25.070 INFO 8565 --- [ main] c.test.parent.app.ReproduceApplication : Started ReproduceApplication in 0.88 seconds (JVM running for 2.771)
2022-07-14 17:11:25.102 INFO 8565 --- [ main] c.test.parent.app.ReproduceApplication : The following 1 profile is active: "two"
2022-07-14 17:11:25.234 WARN 8565 --- [ main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jmxMBeanExporter' defined in class path resource [org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.class]: Invocation of init method failed; nested exception is org.springframework.jmx.export.MBeanExportException: Failed to register MBean for endpoint 'beans'; nested exception is javax.management.InstanceAlreadyExistsException: org.springframework.boot:type=Endpoint,name=Beans,context=7e38a7fe
2022-07-14 17:11:25.242 INFO 8565 --- [ main] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-07-14 17:11:25.263 ERROR 8565 --- [ main] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jmxMBeanExporter' defined in class path resource [org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.class]: Invocation of init method failed; nested exception is org.springframework.jmx.export.MBeanExportException: Failed to register MBean for endpoint 'beans'; nested exception is javax.management.InstanceAlreadyExistsException: org.springframework.boot:type=Endpoint,name=Beans,context=7e38a7fe
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1804) ~[spring-beans-5.3.17.jar:5.3.17]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:620) ~[spring-beans-5.3.17.jar:5.3.17]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.17.jar:5.3.17]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.17.jar:5.3.17]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.17.jar:5.3.17]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.17.jar:5.3.17]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.17.jar:5.3.17]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:953) ~[spring-beans-5.3.17.jar:5.3.17]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.17.jar:5.3.17]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.17.jar:5.3.17]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:740) ~[spring-boot-2.6.5.jar:2.6.5]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:415) ~[spring-boot-2.6.5.jar:2.6.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:303) ~[spring-boot-2.6.5.jar:2.6.5]
at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:164) ~[spring-boot-2.6.5.jar:2.6.5]
at com.test.parent.app.ReproduceApplication.main(ReproduceApplication.java:35) ~[classes/:na]
Caused by: org.springframework.jmx.export.MBeanExportException: Failed to register MBean for endpoint 'beans'; nested exception is javax.management.InstanceAlreadyExistsException: org.springframework.boot:type=Endpoint,name=Beans,context=7e38a7fe
at org.springframework.boot.actuate.endpoint.jmx.JmxEndpointExporter.register(JmxEndpointExporter.java:105) ~[spring-boot-actuator-2.6.5.jar:2.6.5]
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) ~[na:na]
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133) ~[na:na]
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) ~[na:na]
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) ~[na:na]
at org.springframework.boot.actuate.endpoint.jmx.JmxEndpointExporter.register(JmxEndpointExporter.java:90) ~[spring-boot-actuator-2.6.5.jar:2.6.5]
at org.springframework.boot.actuate.endpoint.jmx.JmxEndpointExporter.afterPropertiesSet(JmxEndpointExporter.java:81) ~[spring-boot-actuator-2.6.5.jar:2.6.5]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1863) ~[spring-beans-5.3.17.jar:5.3.17]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1800) ~[spring-beans-5.3.17.jar:5.3.17]
... 14 common frames omitted
Caused by: javax.management.InstanceAlreadyExistsException: org.springframework.boot:type=Endpoint,name=Beans,context=7e38a7fe
at java.management/com.sun.jmx.mbeanserver.Repository.addMBean(Repository.java:436) ~[na:na]
at java.management/com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerWithRepository(DefaultMBeanServerInterceptor.java:1855) ~[na:na]
at java.management/com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerDynamicMBean(DefaultMBeanServerInterceptor.java:955) ~[na:na]
at java.management/com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerObject(DefaultMBeanServerInterceptor.java:890) ~[na:na]
at java.management/com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerMBean(DefaultMBeanServerInterceptor.java:320) ~[na:na]
at java.management/com.sun.jmx.mbeanserver.JmxMBeanServer.registerMBean(JmxMBeanServer.java:522) ~[na:na]
at org.springframework.boot.actuate.endpoint.jmx.JmxEndpointExporter.register(JmxEndpointExporter.java:98) ~[spring-boot-actuator-2.6.5.jar:2.6.5]
... 26 common frames omitted
I found each time scan and exposed endpoints use the same org.springframework.boot.actuate.autoconfigure.endpoint.jmx.DefaultEndpointObjectNameFactory with same contextId, which will generate the same ObjectName.
@Bean
@ConditionalOnMissingBean({EndpointObjectNameFactory.class})
public DefaultEndpointObjectNameFactory endpointObjectNameFactory(MBeanServer mBeanServer, Environment environment) {
// It only set once with the parent context as a singleton, although it has been changed to child's application context.
String contextId = ObjectUtils.getIdentityHexString(this.applicationContext);
return new DefaultEndpointObjectNameFactory(this.properties, environment, mBeanServer, contextId);
}
@Bean
@ConditionalOnSingleCandidate(MBeanServer.class)
public JmxEndpointExporter jmxMBeanExporter(MBeanServer mBeanServer, EndpointObjectNameFactory endpointObjectNameFactory, ObjectProvider<ObjectMapper> objectMapper, JmxEndpointsSupplier jmxEndpointsSupplier) {
JmxOperationResponseMapper responseMapper = new JacksonJmxOperationResponseMapper((ObjectMapper)objectMapper.getIfAvailable());
// it always use the DefaultEndpointObjectNameFactory with parent contextId.
return new JmxEndpointExporter(mBeanServer, endpointObjectNameFactory, responseMapper, jmxEndpointsSupplier.getEndpoints());
}
So I just create my own EndpointObjectNameFactory bean to provide unique ObjectName, everything works fine.
@Component
public class CustomEndpointFactory implements EndpointObjectNameFactory {
@Override
public ObjectName getObjectName(ExposableJmxEndpoint endpoint) throws MalformedObjectNameException {
StringBuilder builder = new StringBuilder();
builder.append(":type=Endpoint");
builder.append(",name=").append(StringUtils.capitalize(endpoint.getEndpointId().toString()));
builder.append(",unique=").append(UUID.randomUUID().toString());
return ObjectNameManager.getInstance(builder.toString());
}
}
I guess it seems like register the same bean twice like this one #6378.
Comment From: ilharp
I've ran into the same problem using spring-boot-actuator:2.7.1, thanks for the temporary workaround!
Besides, I found that modifying spring.jmx.unique-names, management.endpoints.jmx.domain or management.endpoints.jmx.static-names has no effect - all bean factories try to load using the parent application's configuration. If the actuator can identify the currently loaded (child) application, then adding some fields like "application-unique id" inside the EndpointObjectNameFactory will be simple. Will this be implemented later?