Tomcat 编程式启动 JMX 监控

通过这篇文章,我们可以了解到,利用 JMX 技术可以方便获取 Tomcat 监控情况。但是我们采用自研的框架而非大家常见的 SpringBoot,于是就不能方便地通过设置配置开启 Tomcat 的 JMX,——尽管我们也是基于 Tomcat 的 Web 容器,而是还是 SpringMVC。

在笔者一番尝试下,终于实现了“Enable Embedded Tomcat JMX Programmatically”,所谓 Programmatically 就是编程式的用 Java 代码去配置。实际情况也很简单,就是在 Tomcat 启动的LifecycleEvent事件中加入:

context.addLifecycleListener((LifecycleEvent event) -> {
      if (isStatedSpring || (event.getLifecycle().getState() != LifecycleState.STARTING_PREP))
          return;

      BaseWebInitializer.coreStartup(context.getServletContext(), clz);
//			anotherWayToStartStrping();

      if (isEnableJMX) {
          try {
              LocateRegistry.createRegistry(9011);
              JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(
                      new JMXServiceURL("service:jmx:rmi://localhost/jndi/rmi://localhost:9011/jmxrmi"),
                      null,
                      ManagementFactory.getPlatformMBeanServer()
              );
              cs.start();
              LOGGER.info("成功启动 JMXConnectorServer");
          } catch (IOException e) {
              e.printStackTrace();
          }
      }

      isStatedSpring = true;
      springTime = System.currentTimeMillis() - startedTime;
  });

要注意的是端口的配置,当前是 9011。

另外如果要鉴权,把newJMXConnectorServer()的第二个参数environmentnull改为一个 map。

哦,对了,还有 Maven 的依赖,——貌似 8 最高的也就这个版本。

<!-- 监控用 -->
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina-jmx-remote</artifactId>
    <version>8.5.75</version>
    <type>jar</type>
</dependency>

后来测试好像不用这个依赖也行。

连接 JMX

刚才的配置就像一个服务端,而接着我们这一步就相当于是客户端的连接。先准备好连接地址:

String jmxURL = "service:jmx:rmi:///jndi/rmi://127.0.0.1:9011/jmxrmi";

// 如果要鉴权,还要配置下面的,传入到 environment
Map<String, String[]> map = new HashMap<>();
String[] credentials = new String[]{"monitorRole", "tomcat"};
map.put("jmx.remote.credentials", credentials);

进行连接:

MBeanServerConnection msc = JMXConnectorFactory.connect(new JMXServiceURL(jmxURL)).getMBeanServerConnection();

分门别类地查询

成功连接后,会返回大量的信息。JMX 提供了一种 Domain 命名空间的概念,是为第一的大分类。我们可以打印 Domains 出来,再用getObjectNamesByDomain()列出子类 :

for (String domain : msc.getDomains())
     System.out.println(domain);

List<Node> tomcat = MonitorUtils.getObjectNamesByDomain(msc, "Tomcat");

Node 是我们对结果的封装,实际最重要是里面的fullName即对应 JMX API 的CanonicalName,它就是对象名称 ObjectName,以此来获取具体的属性。

ObjectName threadObjName = new ObjectName("Tomcat:name=\"http-nio-8301\",type=ThreadPool");
System.out.println("currentThreadCount:" + msc.getAttribute(threadObjName, "currentThreadCount"));// tomcat的线程数对应的属性值

因为是自定义的 Tomcat,所以 ObjectName 会不一样。常见的 Tomcat 是

ObjectName threadObjName = new ObjectName("Catalina:type=ThreadPool,name=http-8301");// 端口最好是动态取得

于是就必须通过前面说的getObjectNamesByDomain()“人肉”查找。另外也要注意 Tomcat 的端口配置。

获取 Tomcat 监控

只要能成功连接并获取 JMX 信息,下一步就是将其转换为监控信息渲染到前端。

测试代码:

public class TestTomcat {
    String jmxURL = "service:jmx:rmi:///jndi/rmi://127.0.0.1:9011/jmxrmi";

    @Test
    public void testConnectJMX() {
        TomcatInfo info = new TomcatJmx().getInfo(jmxURL);
        TestHelper.printJson(info);
    }
}

返回结果

在这里插入图片描述

完整源码

package com.ajaxjs.developertools.monitor;

import com.ajaxjs.developertools.monitor.model.tomcat.*;
import com.ajaxjs.util.DateUtil;
import com.ajaxjs.util.logger.LogHelper;
import com.ajaxjs.util.reflect.BeanUtils;
import org.springframework.util.CollectionUtils;

import javax.management.*;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.remote.JMXConnector;
import java.io.IOException;
import java.lang.management.MemoryUsage;
import java.util.*;

/**
 * 获取 Tomcat 的 JMX 信息
 */
public class TomcatJmx extends JmxHelper {
    private static final LogHelper LOGGER = LogHelper.getLog(TomcatJmx.class);

    public TomcatInfo getInfo(String jmxURL) {
        return getInfo(jmxURL, null);
    }

    public TomcatInfo getInfo(String jmxURL, Integer port) {
        TomcatInfo info = new TomcatInfo();

        try (JMXConnector connect = connect(jmxURL)) {
            assert connect != null;
            MBeanServerConnection msc = connect.getMBeanServerConnection();
            setMsc(msc);

            SystemInfo systemInfo = new SystemInfo();
            ThreadPool threadPool = new ThreadPool();
            info.jvmInfo = jvm();
            info.systemInfo = systemInfo;
            info.session = getSession();
            info.threadPool = threadPool;

            if (port == null) port = getTomcatPort(msc);

            everyAttribute(objectNameFactory("Tomcat:name=\"http-nio-" + port + "\",type=ThreadPool"), (key, value) -> BeanUtils.setBeanValue(threadPool, key, value));
            everyAttribute(objectNameFactory("java.lang:type=Runtime"), (key, value) -> {
                if ("StartTime".equals(key)) {
                    Date startTime = new Date((Long) value);
                    BeanUtils.setBeanValue(systemInfo, key, DateUtil.formatDate(startTime));
                } else if ("Uptime".equals(key)) {
                    Date startTime = new Date((Long) value);
                    BeanUtils.setBeanValue(systemInfo, key, formatTimeSpan((Long) value));
                } else BeanUtils.setBeanValue(systemInfo, key, value);
            });
        } catch (IOException e) {
            LOGGER.warning(e);
        }

        return info;
    }

    /**
     * 获取 tomcat 运行端口
     */
    private static int getTomcatPort(MBeanServerConnection msc) {
        try {
            Set<ObjectName> objectNames = queryNames(msc, "Tomcat:type=Connector,*");

            if (CollectionUtils.isEmpty(objectNames))
                throw new IllegalStateException("没有发现JVM中关联的MBeanServer : " + msc.getDefaultDomain() + " 中的对象名称.");

            for (ObjectName objectName : objectNames) {
                String protocol = (String) msc.getAttribute(objectName, "protocol");

                if (protocol.equals("HTTP/1.1")) return (Integer) msc.getAttribute(objectName, "port");
            }
        } catch (MBeanException | AttributeNotFoundException | ReflectionException | InstanceNotFoundException |
                 IOException e) {
            LOGGER.warning(e);
        }

        return 0;
    }

    private List<Session> getSession() {
        Set<ObjectName> objectNames = queryNames("Tomcat:type=Manager,*");
        List<Session> list = new ArrayList<>(objectNames.size());

        for (ObjectName obj : objectNames) {
//            List<Node> tomcat = JmxUtils.getObjectNamesByDomain(msc, "Tomcat");
//                System.out.println("应用名:" + obj.getKeyProperty("path"));
//                System.out.println("currentThreadCount:" + msc.getAttribute(threadObjName, "currentThreadCount"));// tomcat的线程数对应的属性值

            Session session = new Session();
            everyAttribute(objectNameFactory(obj.getCanonicalName()), (key, value) -> BeanUtils.setBeanValue(session, key, value));
            list.add(session);
        }

        return list;
    }

    private JvmInfo jvm() {
        try {
            // 堆使用率
            ObjectName heapObjName = objectNameFactory("java.lang:type=Memory");
            MemoryUsage heapMemoryUsage = MemoryUsage.from((CompositeDataSupport) getMsc().getAttribute(heapObjName, "HeapMemoryUsage"));

            // 堆当前分配
            long commitMemory = heapMemoryUsage.getCommitted(), usedMemory = heapMemoryUsage.getUsed();
            JvmInfo jvmInfo = new JvmInfo();
            jvmInfo.setMaxMemory(heapMemoryUsage.getMax());
            jvmInfo.setHeap(((Long) (usedMemory * 100 / commitMemory)).intValue());

            MemoryUsage nonheapMemoryUsage = MemoryUsage.from((CompositeDataSupport) getMsc().getAttribute(heapObjName, "NonHeapMemoryUsage"));
            long nonCommitMemory = nonheapMemoryUsage.getCommitted(), nonUsedMemory = heapMemoryUsage.getUsed();
            jvmInfo.setNonCommitMemory(nonCommitMemory);
            jvmInfo.setNonUsedMemory(nonUsedMemory);
            jvmInfo.setNonHeap(((Long) (nonUsedMemory * 100 / nonCommitMemory)).intValue());

//            ObjectName permObjName = new ObjectName("java.lang:type=MemoryPool,name=Perm Gen");
//            MemoryUsage permGenUsage = MemoryUsage.from((CompositeDataSupport) getMsc().getAttribute(permObjName, "Usage"));
//            long committed = permGenUsage.getCommitted();
//            long used = heapMemoryUsage.getUsed();
//
//            jvmInfo.setCommitted(committed);
//            jvmInfo.setUsed(used);
//            jvmInfo.setPermUse(((Long) (used * 100 / committed)).intValue());

            return jvmInfo;
        } catch (ReflectionException | AttributeNotFoundException | InstanceNotFoundException | MBeanException |
                 IOException e) {
            LOGGER.warning(e);
        }

        return null;
    }

    private static String formatTimeSpan(long span) {
        long minSeconds = span % 1000;

        span = span / 1000;
        long seconds = span % 60;

        span = span / 60;
        long min = span % 60;

        span = span / 60;
        long hours = span % 24;

        span = span / 24;
        long days = span;

        try (Formatter formatter = new Formatter()) {
            return formatter.format("%1$d天 %2$02d:%3$02d:%4$02d.%5$03d", days, hours, min, seconds, minSeconds).toString();
        }
    }
}

JmxHelpe:

package com.ajaxjs.developertools.monitor;

import com.ajaxjs.developertools.monitor.model.jvm.Node;
import com.ajaxjs.developertools.monitor.model.jvm.NodeType;
import com.ajaxjs.util.logger.LogHelper;
import org.springframework.util.StringUtils;

import javax.management.*;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.BiConsumer;

public class JmxHelper {
    private static final LogHelper LOGGER = LogHelper.getLog(JmxHelper.class);

    public JmxHelper() {
    }

    public JmxHelper(MBeanServerConnection msc) {
        this.msc = msc;
    }

    /**
     * 连接实例
     */
    private MBeanServerConnection msc;

    /**
     * 连接对象
     */
    private JMXConnector connector;

    public MBeanAttributeInfo[] getAttributes(ObjectName objName) {
        try {
            return msc.getMBeanInfo(objName).getAttributes();
        } catch (InstanceNotFoundException | IntrospectionException | ReflectionException | IOException e) {
            LOGGER.warning(e);
            return null;
        }
    }

    public void everyAttribute(ObjectName objName, BiConsumer<String, Object> fn) {
        MBeanAttributeInfo[] attributes = getAttributes(objName);

        try {
            for (MBeanAttributeInfo attribute : attributes) {
                String key = attribute.getName();
                Object value = msc.getAttribute(objName, key);
//                System.out.println("private " + attribute.getType() + " " + key + ";");

                fn.accept(key, value);
            }
        } catch (MBeanException | AttributeNotFoundException | InstanceNotFoundException | ReflectionException |
                 IOException e) {
            LOGGER.warning(e);
        }
    }

    /**
     * 列出所有的对象。
     * 下面方法同等效果
     * <code>
     * for (String domain : msc.getDomains())
     * System.out.println(domain);
     * List<Node> tomcat = MonitorUtils.getObjectNamesByDomain(msc, "Tomcat");
     * </code>
     */
    void listObjectInstance() {
        try {
            Set<ObjectInstance> MBeanset = msc.queryMBeans(null, null);

            for (ObjectInstance objectInstance : MBeanset) {
                ObjectName objectName = objectInstance.getObjectName();
                String canonicalName = objectName.getCanonicalName();
                System.out.println("canonicalName : " + canonicalName);

                // 可以查询集群
//                if (canonicalName.equals("Catalina:host=localhost,type=Cluster")) {
//                    // Get details of cluster MBeans
//                    // getMBeansDetails(canonicalName);
//                    String canonicalKeyPropList = objectName.getCanonicalKeyPropertyListString();
//                }
            }
        } catch (IOException e) {
            LOGGER.warning(e);
        }
    }

    public List<Node> getObjectNamesByDomain(String domain) {
        return getObjectNamesByDomain(msc, domain);
    }

    public static JMXConnector connect(String jmxUrl) {
        try {
            return JMXConnectorFactory.connect(new JMXServiceURL(jmxUrl));
        } catch (IOException e) {
            LOGGER.warning(e);
            return null;
        }
    }

    public static JMXConnector connect(String host, String port) {
        String jmxUrl = String.format("service:jmx:rmi:///jndi/rmi://%s:%s/jmxrmi", host, port);

        return connect(jmxUrl);
    }

    /**
     * 根据命名空间获取对象名称列表
     * 可以先打印出所有的命名空间:
     * <code>
     * for (String domain : msc.getDomains())
     * System.out.println(domain);
     * </code>
     *
     * @param msc    MBeanServerConnection
     * @param domain 命名空间
     * @return 对象名称列表
     */
    public static List<Node> getObjectNamesByDomain(MBeanServerConnection msc, String domain) {
        List<Node> nodes = new ArrayList<>();
        Map<String, ObjectName> types = new HashMap<>();

        for (ObjectName objectName : Objects.requireNonNull(queryNames(msc, domain + ":*"))) {
            String type = objectName.getKeyProperty("type");
            types.put(type, objectName);
        }

        for (Map.Entry<String, ObjectName> entry : types.entrySet()) {
            Node node = new Node();
            node.setTitle(StringUtils.hasText(entry.getKey()) ? entry.getKey() : entry.getValue().getCanonicalName());
            node.setKey(entry.getValue().getCanonicalName());
            node.setNodeType(NodeType.OBJECTNAME.getName());
            node.setFullName(entry.getValue().getCanonicalName());
            List<Node> subNodes = getObjectNamesByType(msc, domain, entry.getKey());

            if (subNodes.size() > 1) node.setChildren(subNodes);

            nodes.add(node);
        }

        return nodes;
    }

    private static List<Node> getObjectNamesByType(MBeanServerConnection msc, String domain, String type) {
        List<Node> nodes = new ArrayList<>();

        for (ObjectName objectName : Objects.requireNonNull(queryNames(msc, domain + ":type=" + type + ",*"))) {
            Node node = new Node();
            String fullName = objectName.getCanonicalName(), name = objectName.getKeyProperty("name");

            node.setTitle(StringUtils.hasText(name) ? name : objectName.getCanonicalName());
            node.setFullName(fullName);
            node.setKey(fullName);
            node.setNodeType(NodeType.OBJECTNAME.getName());
            nodes.add(node);
        }

        return nodes;
    }

    public static SortedMap<String, Object> analyzeCompositeData(Object compositeData) {
        try {
            if (compositeData instanceof CompositeDataSupport) {
                CompositeDataSupport support = (CompositeDataSupport) compositeData;
                Field field = support.getClass().getDeclaredField("contents");
                field.setAccessible(true);

                return (SortedMap<String, Object>) field.get(support);
            }
        } catch (IllegalAccessException | NoSuchFieldException e) {
            LOGGER.warning(e);
        }

        return null;
    }

    /**
     * 实例化都抛出异常,麻烦
     */
    public static ObjectName objectNameFactory(String name) {
        try {
            return new ObjectName(name);
        } catch (MalformedObjectNameException e) {
            LOGGER.warning(e);
            return null;
        }
    }

    public Set<ObjectName> queryNames(String name) {
        return queryNames(msc, name);
    }

    public static Set<ObjectName> queryNames(MBeanServerConnection msc, String name) {
        try {
            return msc.queryNames(objectNameFactory(name), null);
        } catch (IOException e) {
            LOGGER.warning(e);
            return null;
        }
    }

    public MBeanInfo getMBeanInfo(ObjectName o) {
        return getMBeanInfo(msc, o);
    }

    public static MBeanInfo getMBeanInfo(MBeanServerConnection msc, ObjectName o) {
        try {
            return msc.getMBeanInfo(o);
        } catch (InstanceNotFoundException | IntrospectionException | ReflectionException | IOException e) {
            LOGGER.warning(e);
            return null;
        }
    }

    public void setMsc(MBeanServerConnection msc) {
        this.msc = msc;
    }

    public MBeanServerConnection getMsc() {
        return msc;
    }

    public void setConnector(JMXConnector connector) {
        this.connector = connector;
    }

    public JMXConnector getConnector() {
        return connector;
    }
}

使用限制

Tomcat 返回的监控信息比较完整,其实可以说覆盖了全部。但是有个致命的问题是,注册到 MBeanServer 需要一个端口!在旧时代的一个 Tomcat 挂多个服务,这个本不是问题,但刻下多流行 Spring Boot 的程序整合了 Tomcat,一个服务就是一个端口。分配端口也是个麻烦事情。

后来我在这篇文章中找一个获取 Tomcat 信息的方法,它是通过 Tomcat 自身 API 获取的,不是很全,只有寥寥几笔而已。
在这里插入图片描述
只能说聊胜于无。

/**
 * 通过 Tomcat 自身 API 获取的实时信息
 *
 * @param tomcat Tomcat 实例
 * @return
 */
public static Map<String, String> getTomcatSimplyInfo(Tomcat tomcat) {
    ThreadPoolExecutor executor = (ThreadPoolExecutor) tomcat.getConnector().getProtocolHandler().getExecutor();

    Map<String, String> returnMap = new LinkedHashMap<>();
    returnMap.put("核心线程数(corePoolSize)", String.valueOf(executor.getCorePoolSize()));
    returnMap.put("最大线程数(maximumPoolSize)", String.valueOf(executor.getMaximumPoolSize()));
    returnMap.put("活跃线程数(activeCount)", String.valueOf(executor.getActiveCount()));
    returnMap.put("池中当前线程数(poolSize)", String.valueOf(executor.getPoolSize()));
    returnMap.put("历史最大线程数(largestPoolSize)", String.valueOf(executor.getLargestPoolSize()));
    returnMap.put("工作队列任务数量(workQueue.size)", String.valueOf(executor.getQueue().size()));
    returnMap.put("线程允许空闲时间/s(keepAliveTime)", String.valueOf(executor.getKeepAliveTime(TimeUnit.SECONDS)));
    returnMap.put("核心线程数是否允许被回收(allowCoreThreadTimeOut)", String.valueOf(executor.allowsCoreThreadTimeOut()));
    returnMap.put("提交任务总数(submittedCount)", String.valueOf(executor.getSubmittedCount()));
    returnMap.put("历史执行任务的总数(近似值)(taskCount)", String.valueOf(executor.getTaskCount()));
    returnMap.put("历史完成任务的总数(近似值)(completedTaskCount)", String.valueOf(executor.getCompletedTaskCount()));
    returnMap.put("拒绝策略(ejectedExecutionHandler.class)", executor.getRejectedExecutionHandler().getClass().getCanonicalName());

    return returnMap;
}

上述参数 Tomcat 可以通过 Spring Boot 的 ((TomcatWebServer) webServerApplicationContext.getWebServer()).getTomcat()获取。

Tomcat JDBC Pool 监控

我们使用了 Tomcat JDBC Pool 作为数据连接池。Tomcat JDBC Pool 本身默认支持通过 JMX 获取监控信息,按照此文的方式即可简单获取之。可是我们通过独立创建了 DataSource 调用 JDBC Pool 就不会自动注册 JMX 组件。幸好官网文档介绍了一下,可以手动登记注册 JMX。

/**
 * 手动创建连接池。这里使用了 Tomcat JDBC Pool
 *
 * @param driver   驱动程序,如 com.mysql.cj.jdbc.Driver
 * @param url      数据库连接字符串
 * @param userName 用户
 * @param password 密码
 * @return 数据源
 */
public static DataSource setupJdbcPool(String driver, String url, String userName, String password) {
    PoolProperties p = new PoolProperties();
    p.setDriverClassName(driver);
    p.setUrl(url);
    p.setUsername(userName);
    p.setPassword(password);
    p.setMaxActive(100);
    p.setInitialSize(10);
    p.setMaxWait(10000);
    p.setMaxIdle(30);
    p.setJmxEnabled(true);
    p.setMinIdle(5);
    p.setTestOnBorrow(true);
    p.setTestWhileIdle(true);
    p.setTestOnReturn(true);
    p.setValidationInterval(18800);
    p.setDefaultAutoCommit(true);
    org.apache.tomcat.jdbc.pool.DataSource ds = new org.apache.tomcat.jdbc.pool.DataSource();
    ds.setPoolProperties(p);

    // 一般来说 Tomcat 会自动注册。但是我们现在手动使用 Pool,于是也得手动地注册到 MBean
    try {
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        ObjectName on = new ObjectName("org.apache.tomcat.jdbc.pool.jmx.ConnectionPool:type=Logging2");
        server.registerMBean(ds.getPool().getJmxPool(), on);
    } catch (Exception e) {
        LOGGER.warning(e);
    }

    return ds;
}

这里的ObjectName好像随便填都可以,但一定要按照其格式。成功注册后,查询MBeanClassName=org.apache.tomcat.jdbc.pool.jmx.ConnectionPool就是连接池当前的情况。

原先文章介绍是 JSP 显示的。我们改为返回 Map。

@GetMapping("/jdbc_pool_status")
public Map<String, Object> jdbcPoolStatus() {
    MBeanServer server = ManagementFactory.getPlatformMBeanServer();
    Set<ObjectName> objectNames = server.queryNames(null, null);
    Map<String, Object> result = new HashMap<>();

    try {
        for (ObjectName name : objectNames) {
            MBeanInfo info = server.getMBeanInfo(name);

            if (info.getClassName().equals("org.apache.tomcat.jdbc.pool.jmx.ConnectionPool")) {
                for (MBeanAttributeInfo mf : info.getAttributes()) {
                    Object attributeValue = server.getAttribute(name, mf.getName());

                    if (attributeValue != null)
                        result.put(mf.getName(), attributeValue);
                }

                break;
            }
        }
    } catch (InstanceNotFoundException | IntrospectionException | ReflectionException | MBeanException |
             AttributeNotFoundException e) {
        throw new RuntimeException(e);
    }

    return result;
}

返回如下:

在这里插入图片描述
如果 JMX 不凑效,有没有其他手段呢?有人介绍说用 JDBC Pool 的拦截器,——那样也行。还有个大神的方法《自定义带监控的数据库连接池》,比较深入底层的说,没试过。

分享一个 MBeanInfo 浏览器

JSP 写的,简单易用,无它,就是暴力遍历所有的 Mean 及其字段。可以传入如?name=ConnectionPool的参数指定某个 MBean。

<%@ page contentType="text/plain; charset=UTF-8" pageEncoding="ISO-8859-1" session="false"
  import="java.io.*, java.util.*, java.net.*, javax.management.*, java.lang.management.ManagementFactory"
%>

<%!
private void dumpMBean(MBeanServer server, ObjectName objName, MBeanInfo mbi, Writer writer) throws Exception {
    writer.write(String.format("MBeanClassName=%s%n", mbi.getClassName()));
    Map<String,String> props=new HashMap<String,String>();
    int idx=0;

    for(MBeanAttributeInfo mf : mbi.getAttributes()) {
        idx++;

        try {
            Object attr = server.getAttribute(objName, mf.getName());
            if (attr!=null)
                props.put(mf.getName(), attr.toString());
        } catch(Exception ex) {
            // sun.management.RuntimeImpl: java.lang.UnsupportedOperationException(Boot class path mechanism is not supported)
            props.put("error_"+idx, ex.getClass().getName()+" "+ex.getMessage());
        }
    }

    // sort by hashmap keys
    for(String sKey : new TreeSet<String>(props.keySet()))
        writer.write(String.format("%s=%s%n", sKey, props.get(sKey)));
}

%><%
    // Dump MBean management properties, all beans or named beans
    // dumpMBean.jsp?name=ConnectionPool,ContainerMBean
    // dumpMBean.jsp?name=

    if (request.getCharacterEncoding()==null)
        request.setCharacterEncoding("UTF-8");

    String val = request.getParameter("name");
    String[] names = val!=null ? val.trim().split(",") : new String[0];
    if (names.length==1 && names[0].isEmpty()) names=new String[0];

    MBeanServer server = ManagementFactory.getPlatformMBeanServer();

    for(ObjectName objName : server.queryNames(null,null)) {
        MBeanInfo mbi = server.getMBeanInfo(objName);
        boolean match = names.length<1;
        String name = mbi.getClassName();

        for(int idx=0; idx<names.length; idx++) {
            if (name.endsWith(names[idx])) {
                match=true;
                break;
            }
        }

        if (match) {
            dumpMBean(server, objName, mbi, out);
            out.println("");
        }
    }

    out.flush();

%>

返回如下:

在这里插入图片描述

参考

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sp42a

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值