美文网首页
Tomcat获取MBean

Tomcat获取MBean

作者: AxisX | 来源:发表于2022-02-28 16:27 被阅读0次

    在了解MBean之前需要了解一下JMX,它是java1.5中引入的新特性。JMX全称为“Java Management Extension”,即Java管理扩展。Tomcat就是利用JMX来实现组件管理的,StandardContext、StandardEngine、StandardHost、StandardWrapper等容器都继承自LifecycleMBeanBase,它是JMX生命周期的实现类。

    JMX的核心是MBeanServer(代理服务器),用来管理各类MBean(可以理解为组件/对象)。想要对组件实现统一的管理,首先要将MBean注册到MBeanServer。既然JMX应用时要将MBean注册到MBeanServer,那么反过来想,从MBeanServer中可以获取到很多对象。能不能从MBeanServer入手最终获取StandardContext?

    // 提供MBean服务器引用
    MBeanServerFactory{
      // 构造MBeanServer的Builder
      private static MBeanServerBuilder builder = null;
    }
    

    a. Tomcat中如何获取MBeanServer

    Tomcat提供的获取MBeanServer的方法位于org.apache.tomcat.util.modeler.Registry类,该类有个getMBeanServer()方法。

    // org.apache.tomcat.util.modeler.Registry
    public class Registry implements RegistryMBean, MBeanRegistration {
            private static Registry registry = null;
        private volatile MBeanServer server = null;
        private final Hashtable<String, Hashtable<String, Integer>> idDomains = new Hashtable();
      
        public static synchronized Registry getRegistry(Object key, Object guard) {
            if (registry == null) {
                registry = new Registry();
            }
    
            return registry.guard != null && registry.guard != guard ? null : registry; // 返回对象
        }
        
        public MBeanServer getMBeanServer() {
            ...
            return this.server;
        }
    

    要想调用该方法首先要获取Registry对象,可以通过该类的getRegistry方法,当传入的参数为null时,会直接返回Registry对象。通过Registry获取MBeanServer代码如下:

    Registry.getRegistry(null, null).getMBeanServer();
    

    MBeanServer是个接口,上述代码获取的具体实现类实际上是JmxMBeanServer。打开源码就能发现,这是个基础类,对于方法的实现基本上都是由成员变量实现的,主要方法都是由mbsInterceptor对象来调用。该对象对应的默认操作类是DefaultMBeanServerInterceptor

    // JmxMBeanServer:MBean操作的基类,提供MBean的操作方法
    JmxMBeanServer{
            // 实例化工具
            private final MBeanInstantiator instantiator;
            // MBean服务器委托对象
            private final MBeanServerDelegate mBeanServerDelegateObject;
            // 拦截器 -> 对应实现类为DefaultMBeanServerInterceptor
            private volatile MBeanServer mbsInterceptor = null;
    }
    

    DefaultMBeanServerInterceptor是MBean操作的默认类,成员变量如下。类中包含了对MBean操作的方法实现,例如createMBean、queryMBeans等。

    // MBean操作的默认类
    DefaultMBeanServerInterceptor{
        // 实例化工具
        private final transient MBeanInstantiator instantiator;
    
        // MBeanServer 对象
        private transient MBeanServer server = null;
            // 委托对象
        private final transient MBeanServerDelegate delegate;
    
        // 存储注册的MBean
        private final transient Repository repository;
    
        /** The default domain of the object names */
        private final String domain;
    }
    

    类中的成员变量包含了MBeanServer的引用,还有Repository。这个Repository是com.sun.jmx.mbeanserver.Repository,JMX的存储库对象,里面包含了很多MBean

    // 存储库对象
    Repository{
            // 存放MBean的Map,外层Map的key是域,默认是字符串类型的"domain"。里层的Map的key是MBean的name,value是MBean对象。
            private final Map<String,Map<String,NamedObject>> domainTb;
            // 存储库连接到的服务器的域名。
            private final String domain;
    }
    

    那么想要获取MBean对象,大致思路就是从获取的JmxMBeanServer->DefaultMBeanServerInterceptor->Repository。接下来的问题是,Repository中的MBean具体有哪些对象?哪个对象可以获取到StandardContext?

    Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor");
    field.setAccessible(true);
    Object obj = field.get(mbeanServer);
    
    field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository");
    field.setAccessible(true);
    Repository repository  = (Repository) field.get(obj);
    

    b. 如何查看已有的MBean

    更改tomcat-user.xml,将角色设计如下,重启tomcat

      <role rolename="manager-script"/>
      <role rolename="manager-gui"/>
      <role rolename="manager-jmx"/>
      <role rolename="manager-status"/>
      <user username="tomcat" password="tomcat" roles="manager-gui,manager-jmx,manager-script,manager-status,admin-gui,admin-scrip"/>
    

    访问http://localhost:8080/manager/jmxproxy,即可看到许多MBean,快速搜StandardContext,会发现某些MBean中的managedResource或container等字段,存在StandardContext

    寻找与StandardContext相关的MBean

    大致搜索到相关的如下列表(省略了部分),另外如果仔细观察列表的话,发现很多modelerType都是BaseModelMBean。

    // #1
    Name: Catalina:j2eeType=WebModule,name=//localhost/MemshellTest_war_exploded,J2EEApplication=none,J2EEServer=none
    modelerType: org.apache.catalina.mbeans.ContextMBean
    managedResource
      
    // 2
    Name: Catalina:type=NamingResources,host=localhost,context=/MemshellTest_war_exploded
    modelerType: org.apache.catalina.mbeans.NamingResourcesMBean
    container
    
    // 3
    Name: Catalina:type=Valve,host=localhost,context=/MemshellTest_war_exploded,name=StandardContextValve
    modelerType: org.apache.tomcat.util.modeler.BaseModelMBean
    

    首先,如何在Respoitory中找到这些对应的MBean,可以发现在Respository对象的domainTb属性中,有一个名为Catalina的HashMap,value中存放的就是/manager/jmxproxy页面看到的那些name字段的内容。

    Repository中的对象

    想要获取Catalina某个value对应的对象,首先利用反射从Repository中获取属性domainTb。然后获取domainTb中Catalina对应的HashMap,再根据某个具体的value获取对象。具体写法如下

    field = Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb");
    field.setAccessible(true);
    HashMap<String, Map<String,NamedObject>> domainTb = (HashMap<String, Map<String,NamedObject>>)field.get(repository);
    
    NamedObject object=domainTb.get("Catalina").get("J2EEApplication=none,J2EEServer=none,j2eeType=WebModule,name=//localhost/MemshellTest_war_exploded");
    

    此时得到的是一个NamedObject对象,name就是页面看到的字符串,object就是具体的对象。展开这个ContextMBean对象,会发现其resource字段对应的就是StandardContext

    NamedObject

    NamedObject获取object对象,利用反射完成。而object获取resource同理

    field = Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object");
    field.setAccessible(true);
    Object object1=field.get(object); //此时的object1即为图中的object对象,类型为图中的ContextMBean
    
    field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource");
    field.setAccessible(true);
    Object obj1 = field.get(object1);
    

    c. 如何从MBean间接得到StandardContext

    看到上面这个代码可能会有一问,明明对象是ContextMBean,在反射获取resource时,传入的为什么是BaseModelMBean。如果打开源码会发现,ContextMBean不存在任何属性。它的父类BaseCatalinaMBean也不具备任何属性。但是其父类的父类BaseModelMBean包含resource属性。所以在反射时写入的应该是具备属性的父类。

    public class ContextMBean extends BaseCatalinaMBean<Context> {...}
    public abstract class BaseCatalinaMBean<T> extends BaseModelMBean {...}
    public class BaseModelMBean implements DynamicMBean, MBeanRegistration, ModelMBeanNotificationBroadcaster { 
       protected Object resource = null;
    }
    

    在网上看到的一般MBean获取StandardContext的方法都是NonLoginAuthenticator这个object。调试过程中还有如下的一些方法,可能还有更多,就不再展示了,仔细看看会发现基本都用到BaseModelMBean。

    // #1 
    NamedObject object=domainTb.get("Catalina").get("context=/MemshellTest_war_exploded,host=localhost,name=NonLoginAuthenticator,type=Valve");
    
    field = Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object");
    field.setAccessible(true);
    Object object1=field.get(object);
    
    field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource");
    field.setAccessible(true);
    Object obj1 = field.get(object1);
    
    field = Class.forName("org.apache.catalina.authenticator.AuthenticatorBase").getDeclaredField("context");
    field.setAccessible(true);
    Object objectl=field.get(obj1);
    
    // #2
    NamedObject object=domainTb.get("Catalina").get("context=/MemshellTest_war_exploded,host=localhost,type=NamingResources");
    
    field = Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object");
    field.setAccessible(true);
    Object object1=field.get(object);
    
    
    field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource");
    field.setAccessible(true);
    Object obj1 = field.get(object1);
    
    
    field = Class.forName("org.apache.catalina.deploy.NamingResourcesImpl").getDeclaredField("container");
    field.setAccessible(true);
    Object objectl=field.get(obj1);
    
    // #3
    NamedObject object=domainTb.get("Catalina").get("context=/MemshellTest_war_exploded,host=localhost,name=StandardContextValve,type=Valve");
    
    field = Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object");
    field.setAccessible(true);
    Object object1=field.get(object);
    
    field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource");
    field.setAccessible(true);
    Object obj1 = field.get(object1);
    
    field = Class.forName("org.apache.catalina.valves.ValveBase").getDeclaredField("container");
    field.setAccessible(true);
    Object objectl=field.get(obj1);
    

    相关文章

      网友评论

          本文标题:Tomcat获取MBean

          本文链接:https://www.haomeiwen.com/subject/nzvlrrtx.html