From 2a23727bb80ebb1e58230e7c36930800fe08f2d7 Mon Sep 17 00:00:00 2001 From: nsingh Date: Tue, 24 Apr 2018 12:52:12 +0200 Subject: [PATCH 1/5] Replace Jackson ObjectMapper with Gson in SpringBootApp --- .../commons/commons-boot-app-cli/pom.xml | 6 ---- .../commons/boot/app/cli/SpringBootApp.java | 33 +++++++++++++------ 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/headless-services/commons/commons-boot-app-cli/pom.xml b/headless-services/commons/commons-boot-app-cli/pom.xml index a3dbddf3e..19690bda7 100644 --- a/headless-services/commons/commons-boot-app-cli/pom.xml +++ b/headless-services/commons/commons-boot-app-cli/pom.xml @@ -18,12 +18,6 @@ json 20160810 - - - com.fasterxml.jackson.core - jackson-databind - 2.8.8.1 - commons-io commons-io diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java index 33e8b8638..766bc0fdf 100644 --- a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 2018 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -38,10 +38,11 @@ import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.Reque import org.springframework.ide.vscode.commons.util.CollectorUtil; import org.springframework.ide.vscode.commons.util.Log; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.sun.tools.attach.AttachNotSupportedException; import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachineDescriptor; @@ -58,6 +59,18 @@ public class SpringBootApp { private Boolean isSpringBootApp; + // NOTE: Gson-based serialisation replaces the old Jackson ObjectMapper. Not sure if this makes a difference in the long run, but to retain the same output that Jackson Object Mapper + // was generating during serialisatino, some configuration in Gson is required, as the default behaviour of Gson is different than Object Mapper. + // Namely: + // 1. Object Mapper serialises null, but not Gson by default + // 2. Object Mapper does not escape Html, whereas Gson does by default (for example + // '=' in Gson appears as '\u003d') + private Gson gson = new GsonBuilder() + .serializeNulls() + .disableHtmlEscaping() + .create(); + + private final Supplier jmxConnect = Suppliers.memoize(() -> { String address = null; try { @@ -157,13 +170,13 @@ public class SpringBootApp { public String getEnvironment() throws Exception { Object result = getActuatorDataFromAttribute("org.springframework.boot:type=Endpoint,name=environmentEndpoint", "Data"); if (result != null) { - String environment = new ObjectMapper().writeValueAsString(result); + String environment = gson.toJson(result); return environment; } result = getActuatorDataFromOperation("org.springframework.boot:type=Endpoint,name=Env", "environment"); if (result != null) { - String environment = new ObjectMapper().writeValueAsString(result); + String environment = gson.toJson(result); return environment; } @@ -173,13 +186,13 @@ public class SpringBootApp { private String getBeansJson() throws Exception { Object result = getActuatorDataFromAttribute("org.springframework.boot:type=Endpoint,name=beansEndpoint", "Data"); if (result != null) { - String beans = new ObjectMapper().writeValueAsString(result); + String beans = gson.toJson(result); return beans; } result = getActuatorDataFromOperation("org.springframework.boot:type=Endpoint,name=Beans", "beans"); if (result != null) { - String beans = new ObjectMapper().writeValueAsString(result); + String beans = gson.toJson(result); return beans; } @@ -216,14 +229,14 @@ public class SpringBootApp { //Boot 1.x Object result = getActuatorDataFromAttribute("org.springframework.boot:type=Endpoint,name=requestMappingEndpoint", "Data"); if (result != null) { - String mappings = new ObjectMapper().writeValueAsString(result); + String mappings = gson.toJson(result); return parseRequestMappingsJson(mappings, "1.x"); } //Boot 2.x result = getActuatorDataFromOperation("org.springframework.boot:type=Endpoint,name=Mappings", "mappings"); if (result != null) { - String mappings = new ObjectMapper().writeValueAsString(result); + String mappings = gson.toJson(result); return parseRequestMappingsJson(mappings, "2.x"); } @@ -252,14 +265,14 @@ public class SpringBootApp { //Boot 1.x Object result = getActuatorDataFromAttribute("org.springframework.boot:type=Endpoint,name=autoConfigurationReportEndpoint", "Data"); if (result != null) { - String report = new ObjectMapper().writeValueAsString(result); + String report = gson.toJson(result); return report; } //Boot 2.x result = getActuatorDataFromOperation("org.springframework.boot:type=Endpoint,name=Conditions", "applicationConditionEvaluation"); if (result != null) { - String report = new ObjectMapper().writeValueAsString(result); + String report = gson.toJson(result); return report; } From 65667f42647e03d694b59f73c73ceb62936deb71 Mon Sep 17 00:00:00 2001 From: nsingh Date: Tue, 24 Apr 2018 13:31:03 +0200 Subject: [PATCH 2/5] PT 156072399 - Refactor to support additional JMX MBean domains --- .../commons/boot/app/cli/SpringBootApp.java | 106 +++++++++++++----- 1 file changed, 75 insertions(+), 31 deletions(-) diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java index 766bc0fdf..cb0913130 100644 --- a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java @@ -12,6 +12,7 @@ package org.springframework.ide.vscode.commons.boot.app.cli; import java.io.File; import java.io.IOException; +import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -32,11 +33,12 @@ import javax.management.remote.JMXServiceURL; import org.json.JSONArray; import org.json.JSONObject; import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; -import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping; import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.Boot1xRequestMapping; +import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping; import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMappingsParser20; import org.springframework.ide.vscode.commons.util.CollectorUtil; import org.springframework.ide.vscode.commons.util.Log; +import org.springframework.ide.vscode.commons.util.StringUtil; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; @@ -56,8 +58,10 @@ public class SpringBootApp { private VirtualMachineDescriptor vmd; private static final String LOCAL_CONNECTOR_ADDRESS = "com.sun.management.jmxremote.localConnectorAddress"; + private static final String SPRING_FRAMEWORK_BOOT_DOMAIN = "org.springframework.boot"; private Boolean isSpringBootApp; + private String domain; // NOTE: Gson-based serialisation replaces the old Jackson ObjectMapper. Not sure if this makes a difference in the long run, but to retain the same output that Jackson Object Mapper // was generating during serialisatino, some configuration in Gson is required, as the default behaviour of Gson is different than Object Mapper. @@ -158,8 +162,7 @@ public class SpringBootApp { public String getPort() throws Exception { JMXConnector jmxConnector = null; try { - JMXServiceURL serviceUrl = new JMXServiceURL(jmxConnect.get()); - jmxConnector = JMXConnectorFactory.connect(serviceUrl, null); + jmxConnector = getJmxConnector(); return getPort(jmxConnector); } finally { @@ -168,13 +171,13 @@ public class SpringBootApp { } public String getEnvironment() throws Exception { - Object result = getActuatorDataFromAttribute("org.springframework.boot:type=Endpoint,name=environmentEndpoint", "Data"); + Object result = getActuatorDataFromAttribute(getObjectName("type=Endpoint,name=environmentEndpoint"), "Data"); if (result != null) { String environment = gson.toJson(result); return environment; } - result = getActuatorDataFromOperation("org.springframework.boot:type=Endpoint,name=Env", "environment"); + result = getActuatorDataFromOperation(getObjectName("type=Endpoint,name=Env"), "environment"); if (result != null) { String environment = gson.toJson(result); return environment; @@ -184,13 +187,13 @@ public class SpringBootApp { } private String getBeansJson() throws Exception { - Object result = getActuatorDataFromAttribute("org.springframework.boot:type=Endpoint,name=beansEndpoint", "Data"); + Object result = getActuatorDataFromAttribute(getObjectName("type=Endpoint,name=beansEndpoint"), "Data"); if (result != null) { String beans = gson.toJson(result); return beans; } - result = getActuatorDataFromOperation("org.springframework.boot:type=Endpoint,name=Beans", "beans"); + result = getActuatorDataFromOperation(getObjectName("type=Endpoint,name=Beans"), "beans"); if (result != null) { String beans = gson.toJson(result); return beans; @@ -227,14 +230,14 @@ public class SpringBootApp { public Collection getRequestMappings() throws Exception { //Boot 1.x - Object result = getActuatorDataFromAttribute("org.springframework.boot:type=Endpoint,name=requestMappingEndpoint", "Data"); + Object result = getActuatorDataFromAttribute(getObjectName("type=Endpoint,name=requestMappingEndpoint"), "Data"); if (result != null) { String mappings = gson.toJson(result); return parseRequestMappingsJson(mappings, "1.x"); } //Boot 2.x - result = getActuatorDataFromOperation("org.springframework.boot:type=Endpoint,name=Mappings", "mappings"); + result = getActuatorDataFromOperation(getObjectName("type=Endpoint,name=Mappings"), "mappings"); if (result != null) { String mappings = gson.toJson(result); return parseRequestMappingsJson(mappings, "2.x"); @@ -263,14 +266,14 @@ public class SpringBootApp { private String getAutoConfigReport() throws Exception { //Boot 1.x - Object result = getActuatorDataFromAttribute("org.springframework.boot:type=Endpoint,name=autoConfigurationReportEndpoint", "Data"); + Object result = getActuatorDataFromAttribute(getObjectName("type=Endpoint,name=autoConfigurationReportEndpoint"), "Data"); if (result != null) { String report = gson.toJson(result); return report; } //Boot 2.x - result = getActuatorDataFromOperation("org.springframework.boot:type=Endpoint,name=Conditions", "applicationConditionEvaluation"); + result = getActuatorDataFromOperation(getObjectName("type=Endpoint,name=Conditions"), "applicationConditionEvaluation"); if (result != null) { String report = gson.toJson(result); return report; @@ -279,19 +282,19 @@ public class SpringBootApp { return null; } - protected Object getActuatorDataFromAttribute(String actuatorID, String attribute) throws Exception { + protected Object getActuatorDataFromAttribute(ObjectName objectName, String attribute) throws Exception { JMXConnector jmxConnector = null; try { - JMXServiceURL serviceUrl = new JMXServiceURL(jmxConnect.get()); - jmxConnector = JMXConnectorFactory.connect(serviceUrl, null); - MBeanServerConnection connection = jmxConnector.getMBeanServerConnection(); + if (objectName != null) { + jmxConnector = getJmxConnector(); + MBeanServerConnection connection = jmxConnector.getMBeanServerConnection(); - try { - ObjectName objectName = new ObjectName(actuatorID); - Object result = connection.getAttribute(objectName, "Data"); - return result; - } - catch (InstanceNotFoundException e) { + try { + Object result = connection.getAttribute(objectName, "Data"); + return result; + } + catch (InstanceNotFoundException e) { + } } return null; } @@ -300,19 +303,19 @@ public class SpringBootApp { } } - protected Object getActuatorDataFromOperation(String actuatorID, String operation) throws Exception { + protected Object getActuatorDataFromOperation(ObjectName objectName, String operation) throws Exception { JMXConnector jmxConnector = null; try { - JMXServiceURL serviceUrl = new JMXServiceURL(jmxConnect.get()); - jmxConnector = JMXConnectorFactory.connect(serviceUrl, null); - MBeanServerConnection connection = jmxConnector.getMBeanServerConnection(); + if (objectName != null) { + jmxConnector = getJmxConnector(); + MBeanServerConnection connection = jmxConnector.getMBeanServerConnection(); - try { - ObjectName objectName = new ObjectName(actuatorID); - Object result = connection.invoke(objectName, operation, null, null); - return result; - } - catch (InstanceNotFoundException e) { + try { + Object result = connection.invoke(objectName, operation, null, null); + return result; + } + catch (InstanceNotFoundException e) { + } } return null; } @@ -320,6 +323,11 @@ public class SpringBootApp { if (jmxConnector != null) jmxConnector.close(); } } + protected JMXConnector getJmxConnector() throws MalformedURLException, IOException { + JMXServiceURL serviceUrl = new JMXServiceURL(jmxConnect.get()); + JMXConnector jmxConnector = JMXConnectorFactory.connect(serviceUrl, null); + return jmxConnector; + } protected boolean contains(String[] cpElements, String element) { for (String cpElement : cpElements) { @@ -428,6 +436,42 @@ public class SpringBootApp { return null; } + protected ObjectName getObjectName(String keyProperties) throws Exception { + String domain = getMBeanActuatorDomain(); + if (StringUtil.hasText(domain) && StringUtil.hasText(keyProperties)) { + String fullName = domain + ":" + keyProperties; + return ObjectName.getInstance(fullName); + } + return null; + } + + /** + * PT 156072399: Actuator information can be defined using a different JMX MBean domain. + * By default, Spring Boot exposes management endpoints as JMX MBeans under the 'org.springframework.boot' domain. + * Users can however define another domain in the app's application.properties, for example using this property: + * management.endpoints.jmx.domain=com.example.myapp + * + * Therefore we need to support other domains than just: 'org.springframework.boot' + * @return JMX MBean domain containing actuator information, or null if not resolved. + * @throws Exception when resolving domain from JMX + */ + protected String getMBeanActuatorDomain() throws Exception { + if (this.domain == null) { + JMXConnector jmxConnector = getJmxConnector(); + MBeanServerConnection connection = jmxConnector.getMBeanServerConnection(); + String[] domains = connection.getDomains(); + if (domains != null) { + for (String domain : domains) { + if (SPRING_FRAMEWORK_BOOT_DOMAIN.equals(domain)) { + this.domain = SPRING_FRAMEWORK_BOOT_DOMAIN; + break; + } + } + } + } + return this.domain; + } + @Override public String toString() { return "Process [id=" +getProcessID() + ", name=`"+getProcessName()+"`]"; From e02c7e4a1ea809b8115c02d1aa0a6d1128603998 Mon Sep 17 00:00:00 2001 From: nsingh Date: Tue, 24 Apr 2018 13:36:08 +0200 Subject: [PATCH 3/5] Ensure jmx connector is closed after getting domain --- .../commons/boot/app/cli/SpringBootApp.java | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java index cb0913130..c16f2cf22 100644 --- a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java @@ -61,7 +61,7 @@ public class SpringBootApp { private static final String SPRING_FRAMEWORK_BOOT_DOMAIN = "org.springframework.boot"; private Boolean isSpringBootApp; - private String domain; + private String jmxMBeanDomain; // NOTE: Gson-based serialisation replaces the old Jackson ObjectMapper. Not sure if this makes a difference in the long run, but to retain the same output that Jackson Object Mapper // was generating during serialisatino, some configuration in Gson is required, as the default behaviour of Gson is different than Object Mapper. @@ -437,7 +437,7 @@ public class SpringBootApp { } protected ObjectName getObjectName(String keyProperties) throws Exception { - String domain = getMBeanActuatorDomain(); + String domain = getJmxMbeanDomain(); if (StringUtil.hasText(domain) && StringUtil.hasText(keyProperties)) { String fullName = domain + ":" + keyProperties; return ObjectName.getInstance(fullName); @@ -455,21 +455,28 @@ public class SpringBootApp { * @return JMX MBean domain containing actuator information, or null if not resolved. * @throws Exception when resolving domain from JMX */ - protected String getMBeanActuatorDomain() throws Exception { - if (this.domain == null) { - JMXConnector jmxConnector = getJmxConnector(); - MBeanServerConnection connection = jmxConnector.getMBeanServerConnection(); - String[] domains = connection.getDomains(); - if (domains != null) { - for (String domain : domains) { - if (SPRING_FRAMEWORK_BOOT_DOMAIN.equals(domain)) { - this.domain = SPRING_FRAMEWORK_BOOT_DOMAIN; - break; + protected String getJmxMbeanDomain() throws Exception { + if (this.jmxMBeanDomain == null) { + JMXConnector jmxConnector = null; + try { + jmxConnector = getJmxConnector(); + MBeanServerConnection connection = jmxConnector.getMBeanServerConnection(); + String[] domains = connection.getDomains(); + if (domains != null) { + for (String domain : domains) { + if (SPRING_FRAMEWORK_BOOT_DOMAIN.equals(domain)) { + this.jmxMBeanDomain = SPRING_FRAMEWORK_BOOT_DOMAIN; + break; + } } } + } finally { + if (jmxConnector != null) { + jmxConnector.close(); + } } } - return this.domain; + return this.jmxMBeanDomain; } @Override From ba58b946f38741adf50af319c7fbdbb19b3d5bd6 Mon Sep 17 00:00:00 2001 From: nsingh Date: Tue, 24 Apr 2018 13:44:32 +0200 Subject: [PATCH 4/5] Use org.slf4j.Logger --- .../commons/boot/app/cli/SpringBootApp.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java index c16f2cf22..cb20feef0 100644 --- a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java @@ -32,12 +32,13 @@ import javax.management.remote.JMXServiceURL; import org.json.JSONArray; import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.Boot1xRequestMapping; import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping; import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMappingsParser20; import org.springframework.ide.vscode.commons.util.CollectorUtil; -import org.springframework.ide.vscode.commons.util.Log; import org.springframework.ide.vscode.commons.util.StringUtil; import com.google.common.base.Supplier; @@ -54,6 +55,8 @@ import com.sun.tools.attach.VirtualMachineDescriptor; */ public class SpringBootApp { + private Logger logger = LoggerFactory.getLogger(SpringBootApp.class); + private VirtualMachine vm; private VirtualMachineDescriptor vmd; @@ -86,7 +89,7 @@ public class SpringBootApp { try { address = vm.startLocalManagementAgent(); } catch (IOException e) { - Log.log(e); + logger.error("Error starting local management agent", e); } } return address; @@ -107,7 +110,7 @@ public class SpringBootApp { public SpringBootApp(VirtualMachineDescriptor vmd) throws AttachNotSupportedException, IOException { this.vmd = vmd; this.vm = VirtualMachine.attach(vmd); - Log.info("SpringBootApp created: "+this); + logger.info("SpringBootApp created: "+this); } public String getProcessID() { @@ -207,7 +210,7 @@ public class SpringBootApp { String json = getBeansJson(); return LiveBeansModel.parse(json); } catch (Exception e) { - Log.log(e); + logger.error("Error parsing beans", e); return LiveBeansModel.builder().build(); } } @@ -526,14 +529,14 @@ public class SpringBootApp { } } } catch (Exception e) { - Log.log(e); + logger.error("error resolving profiles from env", e); } return null; } public void dispose() { if (vm!=null) { - Log.info("SpringBootApp disposed: "+this); + logger.info("SpringBootApp disposed: "+this); try { vm.detach(); } catch (Exception e) { From 76b35ab76d76528e4c173f31aa5109bc1714d62d Mon Sep 17 00:00:00 2001 From: nsingh Date: Tue, 24 Apr 2018 15:09:37 +0200 Subject: [PATCH 5/5] For efficient way to determine actuator domain from JMX --- .../commons/boot/app/cli/SpringBootApp.java | 56 +++++++++++++------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java index cb20feef0..d71d82fa1 100644 --- a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java @@ -55,16 +55,17 @@ import com.sun.tools.attach.VirtualMachineDescriptor; */ public class SpringBootApp { + private static final String SPRINGFRAMEWORK_BOOT_DOMAIN = "org.springframework.boot"; + private Logger logger = LoggerFactory.getLogger(SpringBootApp.class); - + private VirtualMachine vm; private VirtualMachineDescriptor vmd; private static final String LOCAL_CONNECTOR_ADDRESS = "com.sun.management.jmxremote.localConnectorAddress"; - private static final String SPRING_FRAMEWORK_BOOT_DOMAIN = "org.springframework.boot"; private Boolean isSpringBootApp; - private String jmxMBeanDomain; + private String jmxMbeanActuatorDomain; // NOTE: Gson-based serialisation replaces the old Jackson ObjectMapper. Not sure if this makes a difference in the long run, but to retain the same output that Jackson Object Mapper // was generating during serialisatino, some configuration in Gson is required, as the default behaviour of Gson is different than Object Mapper. @@ -189,14 +190,14 @@ public class SpringBootApp { return null; } - private String getBeansJson() throws Exception { - Object result = getActuatorDataFromAttribute(getObjectName("type=Endpoint,name=beansEndpoint"), "Data"); + private String getBeansFromActuator(String domain) throws Exception { + Object result = getActuatorDataFromAttribute(getObjectName(domain, "type=Endpoint,name=beansEndpoint"), "Data"); if (result != null) { String beans = gson.toJson(result); return beans; } - result = getActuatorDataFromOperation(getObjectName("type=Endpoint,name=Beans"), "beans"); + result = getActuatorDataFromOperation(getObjectName(domain, "type=Endpoint,name=Beans"), "beans"); if (result != null) { String beans = gson.toJson(result); return beans; @@ -207,7 +208,8 @@ public class SpringBootApp { public LiveBeansModel getBeans() { try { - String json = getBeansJson(); + String domain = getDomainForActuator(); + String json = getBeansFromActuator(domain); return LiveBeansModel.parse(json); } catch (Exception e) { logger.error("Error parsing beans", e); @@ -440,7 +442,11 @@ public class SpringBootApp { } protected ObjectName getObjectName(String keyProperties) throws Exception { - String domain = getJmxMbeanDomain(); + String domain = getDomainForActuator(); + return getObjectName(domain, keyProperties); + } + + protected ObjectName getObjectName(String domain, String keyProperties) throws Exception { if (StringUtil.hasText(domain) && StringUtil.hasText(keyProperties)) { String fullName = domain + ":" + keyProperties; return ObjectName.getInstance(fullName); @@ -458,18 +464,34 @@ public class SpringBootApp { * @return JMX MBean domain containing actuator information, or null if not resolved. * @throws Exception when resolving domain from JMX */ - protected String getJmxMbeanDomain() throws Exception { - if (this.jmxMBeanDomain == null) { + protected String getDomainForActuator() throws Exception { + if (this.jmxMbeanActuatorDomain == null) { JMXConnector jmxConnector = null; try { jmxConnector = getJmxConnector(); MBeanServerConnection connection = jmxConnector.getMBeanServerConnection(); - String[] domains = connection.getDomains(); - if (domains != null) { - for (String domain : domains) { - if (SPRING_FRAMEWORK_BOOT_DOMAIN.equals(domain)) { - this.jmxMBeanDomain = SPRING_FRAMEWORK_BOOT_DOMAIN; - break; + // To be more efficient in finding the domain containing actuator information, + // and avoid many JMX connections + // first check the default springframework boot domain: + String beansJson = getBeansFromActuator(SPRINGFRAMEWORK_BOOT_DOMAIN); + if (StringUtil.hasText(beansJson)) { + this.jmxMbeanActuatorDomain = SPRINGFRAMEWORK_BOOT_DOMAIN; + } + + if (this.jmxMbeanActuatorDomain == null) { + String[] domains = connection.getDomains(); + if (domains != null) { + for (String domain : domains) { + // we already checked default boot domain, no need to check it again + // Note that default spring boot domain may still appear even if another + // domain contains actuator Beans (for example, "Admin" will be under default spring framework domain) + if (!SPRINGFRAMEWORK_BOOT_DOMAIN.equals(domain)) { + beansJson = getBeansFromActuator(domain); + if (StringUtil.hasText(beansJson)) { + this.jmxMbeanActuatorDomain = domain; + break; + } + } } } } @@ -479,7 +501,7 @@ public class SpringBootApp { } } } - return this.jmxMBeanDomain; + return this.jmxMbeanActuatorDomain; } @Override