在了解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> 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> 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
访问http://localhost:8080/manager/jmxproxy
,即可看到许多MBean,快速搜StandardContext
,会发现某些MBean中的managedResource或container等字段,存在StandardContext
大致搜索到相关的如下列表(省略了部分),另外如果仔细观察列表的话,发现很多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字段的内容。
想要获取Catalina某个value对应的对象,首先利用反射从Repository中获取属性domainTb。然后获取domainTb中Catalina对应的HashMap,再根据某个具体的value获取对象。具体写法如下
field = Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb");
field.setAccessible(true);
HashMap> domainTb = (HashMap>)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获取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 {...}
public abstract class BaseCatalinaMBean 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);