1. 程式人生 > 其它 >tomcat原始碼解讀三(2) tomcat中JMX的原始碼分析

tomcat原始碼解讀三(2) tomcat中JMX的原始碼分析

     在這裡我是將tomcat中的jmx給拆分出來進行單獨分析,希望通過此種方式能夠儘可能的出現更多的問題,以便對其有更多的瞭解,首先需要宣告的是tomcat的JMX是在jsvase原有的基礎上做了一些複用,這就必須瞭解一些JMX的實現過程

1.1.1 tomcat中JMX的UML圖

1.1.2 啟動程式碼解析      注意:本人是在剝離下來的程式碼上分析的,跟原始碼可能有所出入,但不會太大,主要是將它的思想分析一下在這個分析過程中以LifecycleMBeanBase類的register方法為入口分析

1.1.2.1 register方法       這個方法是總共分為三步邏輯如下:           第一步:構建ObjectName           第二步:獲取Mbean的登錄檔           第三步 : 註冊當前Mbean元件

程式碼如下:

protected final ObjectName register(Object obj, String objectNameKeyProperties) {
    //根據domain構造一個物件名 形式一般 domain:type=className 這個最終構成 jmxStudy:type=mainTest
    //StringBuilder name = new StringBuilder(getDomain());
    StringBuilder name = new StringBuilder("jmxStudy");
    name.append(':');
    name.append(objectNameKeyProperties);
    ObjectName on = null;
    try {
        //將上面構建的物件名字串轉化為對應的物件
        on = new ObjectName(name.toString());
        //獲取MBeans建模登錄檔並註冊元件
        Registry.getRegistry(null, null).registerComponent(obj, on, null);
    } catch (MalformedObjectNameException e) {
        throw new RuntimeException(e.toString());
    } catch (Exception e) {
        throw new RuntimeException(e.toString());
    }
    return on;
}

     就這樣tomcat的JMX是註冊成功的,但是既然分析原始碼,我們肯定要知根問底,下面就看看如何獲取Mbean登錄檔以及註冊元件

1.1.2.2 獲取Mbean登錄檔

     主要呼叫Registry類的靜態方法getRegistry

/**
 * tomcat中的JMX傳入的兩個引數都是null
 * 所以最終返回registry這個靜態控制代碼的值 當然第一次為空是例項化了一個Registry例項
 * */
public static synchronized Registry getRegistry(Object key, Object guard) {
    Registry localRegistry;
    //perLoaderRegistries是一個HashMap集合
    if( perLoaderRegistries!=null ) {
        if( key==null ){
            //獲取當前執行緒載入器
            key=Thread.currentThread().getContextClassLoader();
        }
        //如果key不為空 則從perLoaderRegistries中獲取,如果沒有的話例項化一個並放入perLoaderRegistries控制代碼
        if( key != null ) {
            localRegistry = perLoaderRegistries.get(key);
            if( localRegistry == null ) {
                localRegistry=new Registry();
                localRegistry.guard=guard;
                perLoaderRegistries.put( key, localRegistry );
                return localRegistry;
            }
            if( localRegistry.guard != null &&
                    localRegistry.guard != guard ) {
                return null;
            }
            return localRegistry;
        }
    }
    //例項化一個靜態的Registry
    if (registry == null) {
        registry = new Registry();
    }
    //這裡的邏輯就是guard不為空則必須與傳入的相同
    if( registry.guard != null && registry.guard != guard ) {
        return null;
    }
    return (registry);
}

1.1.2.3 註冊Mbean元件

     註冊Mbean元件即註冊當前例項,在驗證註冊例項不為空之後,根據其全限定型別在mbean管理器中找到相應的ManagedBean例項,如果找不到則建立一個,並在驗證ObjectName(如果有則將原有的註冊的取消掉)情況下將當前Mbean註冊進去

public void registerComponent(Object bean, ObjectName oname, String type)
        throws Exception
{
    //如果要註冊的bean為空 則直接返回
    if( bean ==null ) {
        return;
    }
    try {
        //如果型別為空則獲取bean的全限定類名
        if( type==null ) {
            type=bean.getClass().getName();
        }
        //mbean的管理器
        ManagedBean managed = findManagedBean(null, bean.getClass(), type);

        //真實的mbean
        DynamicMBean mbean = managed.createMBean(bean);

        //如果當前oname被註冊先解除其註冊
        if( getMBeanServer().isRegistered( oname )) {
            getMBeanServer().unregisterMBean( oname );
        }
        //傳入的mbean==>JMX.MBeanTest  oname==>mainTest1:type=MBeanTest
        getMBeanServer().registerMBean( mbean, oname);
    } catch( Exception ex) {
        ex.printStackTrace();
        throw ex;
    }
}

1.1.2.4 查詢Mbean管理器

     根據型別從descriptors和descriptorsByClass這兩個HashMap結構中去尋找,優先順序descriptors>descriptorsByClass。在沒有找到的情況下會進行一下操作:      1. findDescriptor 方法根據bean找到對應描述檔案,將例項載入到Registry類的registry控制代碼中去,然後再進行查詢(後文描述),一般這種情況是找的到的      2. 在1中沒有找到的情況下,修改ModelerSource再進行查詢 依上面順序找到了就返回,沒找到則返回空

public ManagedBean findManagedBean(Object bean, Class<?> beanClass, String type) throws Exception {
    //如果bean不為空 beanClass為空 獲取beanClass
    if( bean!=null && beanClass==null ) {
        beanClass=bean.getClass();
    }
    //如果type為空 獲取beanClass的name
    if( type==null ) {
        type=beanClass.getName();
    }
    //從descriptors和descriptorsByClass中獲取相應的ManagedBean例項 這裡首次回去的為空
    ManagedBean managed = findManagedBean(type);
    // 尋找相同包下的描述符
    if( managed==null ) {
        // check package and parent packages
        findDescriptor( beanClass, type );
        managed=findManagedBean(type);
    }
    // 還是沒有找到 再根據beanClass來load一遍
    if( managed==null ) {
        // introspection
        load("MbeansDescriptorsIntrospectionSource", beanClass, type);
        managed=findManagedBean(type);
        if( managed==null ) {
            return null;
        }
        managed.setName( type );
        addManagedBean(managed);
    }
    return managed;
}

1.1.2.5 建立最終使用的Mbean      這個過程中最終建立的是BaseModelMBean例項其繼承了DynamicMBean介面,並將mbean管理器注入到其控制代碼

public DynamicMBean createMBean(Object instance) throws InstanceNotFoundException, MBeanException, RuntimeOperationsException {

    BaseModelMBean mbean = null;
    // 如果當前ManagedBean繼承了BASE_MBEAN 則例項化一個BaseModelMBean tomcat的預設實現方式就是這種方式
    if(getClassName().equals(BASE_MBEAN)) {
        mbean = new BaseModelMBean();
    } else {
        //跟還有全限定類名例項化mbean
        Class<?> clazz = null;
        Exception ex = null;
        try {
            clazz = Class.forName(getClassName());
        } catch (Exception e) {
        }

        if( clazz==null ) {
            try {
                ClassLoader cl= Thread.currentThread().getContextClassLoader();
                if ( cl != null){
                    clazz= cl.loadClass(getClassName());
                }
            } catch (Exception e) {
                ex=e;
            }
        }

        if( clazz==null) {
            throw new MBeanException
                    (ex, "Cannot load ModelMBean class " + getClassName());
        }
        try {
            // Stupid - this will set the default minfo first....
            mbean = (BaseModelMBean) clazz.newInstance();
        } catch (RuntimeOperationsException e) {
            throw e;
        } catch (Exception e) {
            throw new MBeanException
                    (e, "Cannot instantiate ModelMBean of class " +
                            getClassName());
        }
    }

    //設定當前物件為例項化mbean的managedBean控制代碼
    mbean.setManagedBean(this);
    try {
        if (instance != null){
            mbean.setManagedResource(instance, "ObjectReference");
        }
    } catch (InstanceNotFoundException e) {
        throw e;
    }

    return mbean;
}

1.1.2.6 registerMBean註冊元件      從管理工廠ManagementFactory獲取MbeanServer,並通過registerMBean方法將屬性和操作註冊到Mbean 棧幀如下:

registerComponent(Object, ObjectName, String):127, Registry (JMX), Registry.java

registerMBean(Object, ObjectName):522, JmxMBeanServer (com.sun.jmx.mbeanserver), JmxMBeanServer.java

registerMBean(Object, ObjectName):319, DefaultMBeanServerInterceptor (com.sun.jmx.interceptor), DefaultMBeanServerInterceptor.java

getNewMBeanClassName(Object):333, DefaultMBeanServerInterceptor (com.sun.jmx.interceptor), DefaultMBeanServerInterceptor.java

getMBeanInfo():88, BaseModelMBean (JMX), BaseModelMBean.java

getMBeanInfo():160, ManagedBean (JMX), ManagedBean.java

     通過getMBeanInfo方法會將屬性、操作和通知註冊到對應例項MBeanAttributeInfo、MBeanOperationInfo以及NotificationInfo然後統一注入到MBeanInfo,最終其會注入到Mbean的管理器從而實現在jconsole等上進行使用

MBeanInfo getMBeanInfo() {
    mBeanInfoLock.readLock().lock();
    try {
        if (info != null) {
            return info;
        }
    } finally {
        mBeanInfoLock.readLock().unlock();
    }
    mBeanInfoLock.writeLock().lock();
    try {
        if (info == null) {
            //建立必要的資訊說明
            AttributeInfo attrs[] = getAttributes();
            MBeanAttributeInfo attributes[] =
                    new MBeanAttributeInfo[attrs.length];
            for (int i = 0; i < attrs.length; i++){
                attributes[i] = attrs[i].createAttributeInfo();
            }

            OperationInfo opers[] = getOperations();
            MBeanOperationInfo operations[] =
                    new MBeanOperationInfo[opers.length];
            for (int i = 0; i < opers.length; i++){
                operations[i] = opers[i].createOperationInfo();
            }

            //獲取所有的通知物件
            NotificationInfo notifs[] = getNotifications();
            //MBeanNotificationInfo類用於描述由MBean發出的不同通知例項的特徵
            MBeanNotificationInfo notifications[] =
                    new MBeanNotificationInfo[notifs.length];
            for (int i = 0; i < notifs.length; i++){
                notifications[i] = notifs[i].createNotificationInfo();
            }


            //建立一個MBeanInfo物件例項 注入相關屬性和操作
            info = new MBeanInfo(getClassName(),
                    getDescription(),
                    attributes,
                    new MBeanConstructorInfo[] {},
                    operations,
                    notifications);
        }

        return info;
    } finally {
        mBeanInfoLock.writeLock().unlock();
    }
}

1.1.2.7 載入資源描述      這是一個比較核心的方法,其獲取相應的類載入器,找到相應包下的mbeans-descriptors.xml,然後獲取模型資源例項,根據字串MbeansDescriptorsIntrospectionSource的到其例項,注入相應registry,然後在其execute方法中根據createManagedBean 建立ManagedBean,也就是在這裡根據物件方法設定屬相的的具體操作(如:是否可讀,可寫),根據initMethods方法將相關屬相操作進行區分,下面展示execute和initMethods方法程式碼 execute程式碼如下:

public ManagedBean createManagedBean(Registry registry, String domain, Class<?> realClass, String type) {
    ManagedBean mbean= new ManagedBean();
    Method methods[]=null;
    Hashtable<String,Method> attMap = new Hashtable<>();
    // key: attribute val: getter method
    Hashtable<String,Method> getAttMap = new Hashtable<>();
    // key: attribute val: setter method
    Hashtable<String,Method> setAttMap = new Hashtable<>();
    // key: operation val: invoke method
    Hashtable<String,Method> invokeAttMap = new Hashtable<>();
    methods = realClass.getMethods();
    //初始化屬性與操作 在這個過程主要將方法載入到對應Hashtable集合 從而分成屬性 操作 以及後面在JMX中設定值呼叫的setAttMap
    initMethods(realClass, methods, attMap, getAttMap, setAttMap, invokeAttMap );

    try {
        //將所有的attMap中的屬性新增到ManagedBean的attributes控制代碼中
        Enumeration<String> en = attMap.keys();
        while( en.hasMoreElements() ) {
            String name = en.nextElement();
            AttributeInfo ai=new AttributeInfo();
            ai.setName( name );
            //根據name從getAttMap獲取相關方法 如果不為空 給屬性設定這個get方法 如果返回型別不為空 設定相應的返回型別
            Method gm = getAttMap.get(name);
            if( gm!=null ) {
                ai.setGetMethod( gm.getName());
                Class<?> t=gm.getReturnType();
                if( t!=null ){
                    ai.setType(t.getName() );
                }
            }
            //根據name從setAttMap獲取相關方法 如果不為空 給屬性設定這個set方法 如果返回型別不為空 設定相應的返回型別
            Method sm = setAttMap.get(name);
            if( sm!=null ) {
                Class<?> t = sm.getParameterTypes()[0];
                if( t!=null ){
                    ai.setType( t.getName());
                    ai.setSetMethod( sm.getName());
                }

            }
            ai.setDescription("自省屬性" + name);

            //如果gm為空 設定當前屬性不可讀
            if( gm==null ){
                ai.setReadable(false);
            }
            //如果sm為空 設定當前屬性不可寫
            if( sm==null ){
                ai.setWriteable(false);
            }
            //主要sm和gm中有一個不為 則像mbean中添加當前屬性
            if( sm!=null || gm!=null ){
                mbean.addAttribute(ai);
            }
        }

        //遍歷所有invokeAttMap中的方法 這些方法排除的有setter getter方法 靜態方法 非public方法 object類中的方法
        for (Map.Entry<String,Method> entry : invokeAttMap.entrySet()) {
            String name = entry.getKey();
            Method m = entry.getValue();
            if(m != null) {
                OperationInfo op=new OperationInfo();
                op.setName(name);
                op.setReturnType(m.getReturnType().getName());
                op.setDescription("自省操作 " + name);
                Class<?> parms[] = m.getParameterTypes();
                for(int i=0; i<parms.length; i++ ) {
                    ParameterInfo pi=new ParameterInfo();
                    pi.setType(parms[i].getName());
                    pi.setName( "引數名" + i);
                    pi.setDescription("引數說明" + i);
                    op.addParameter(pi);
                }
                mbean.addOperation(op);
            } else {
                throw new RuntimeException("Null arg method for [" + name + "]");
            }
        }

        //設定mbean的name
        mbean.setName( type );

        return mbean;
    } catch( Exception ex ) {
        ex.printStackTrace();
        return null;
    }
}

initMethods方法程式碼:

private void initMethods(Class<?> realClass,
                         Method methods[],
                         Hashtable<String,Method> attMap,
                         Hashtable<String,Method> getAttMap,
                         Hashtable<String,Method> setAttMap,
                         Hashtable<String,Method> invokeAttMap)
{
    for (int j = 0; j < methods.length; ++j) {
        String name=methods[j].getName();

        //如果是一個靜態方法則跳過
        if( Modifier.isStatic(methods[j].getModifiers())){
            continue;
        }
        //不是public方法 跳過
        if( ! Modifier.isPublic( methods[j].getModifiers() ) ) {
            continue;
        }
        //獲取該方法所在的類這是因為Object類中的方法都不需要註冊到Mbean
        if( methods[j].getDeclaringClass() == Object.class ){
            continue;
        }
        Class<?> params[] = methods[j].getParameterTypes();
        //如果方法以get開始並且引數個數為0,其返回型別是支援的返回型別 則獲取其新增到attMap和getAttMap
        if( name.startsWith( "get" ) && params.length==0) {
            Class<?> ret = methods[j].getReturnType();
            if(!supportedType(ret) ) {
                continue;
            }
            name=unCapitalize( name.substring(3));
            getAttMap.put( name, methods[j] );
            attMap.put( name, methods[j] );
        } else if( name.startsWith( "is" ) && params.length==0) {
            //如果方法是is開頭 則如果其返回型別為Boolean 則獲取其新增到attMap和getAttMap
            Class<?> ret = methods[j].getReturnType();
            if( Boolean.TYPE != ret  ) {
                continue;
            }
            name=unCapitalize( name.substring(2));
            getAttMap.put( name, methods[j] );
            // just a marker, we don't use the value
            attMap.put( name, methods[j] );

        } else if( name.startsWith( "set" ) && params.length==1) {
            //如果方法是set開頭 則如果其返回型別為Boolean 則獲取其新增到attMap和setAttMap
            if( ! supportedType( params[0] ) ) {
                continue;
            }
            name=unCapitalize( name.substring(3));
            setAttMap.put( name, methods[j] );
            attMap.put( name, methods[j] );
        } else {
            //如果引數長度為0,根據方法名從specialMethods中獲取,如果不為空則直接返回 反之將其新增到invokeAttMap
            //預設去掉preDeregister postDeregister
            if( params.length == 0 ) {
                if( specialMethods.get( methods[j].getName() ) != null ){
                    continue;
                }
                invokeAttMap.put( name, methods[j]);
            } else {
                //如果引數長度不為空 滿足所有引數型別是支援型別將其新增到invokeAttMap中
                boolean supported=true;
                for( int i=0; i<params.length; i++ ) {
                    if( ! supportedType( params[i])) {
                        supported=false;
                        break;
                    }
                }
                if( supported ){
                    invokeAttMap.put( name, methods[j]);
                }
            }
        }
    }
}

1.1.3 呼叫程式碼解析      在這例結合jconsole的Mbean對tomcat程式碼中的設定屬性值、獲取屬性值、呼叫方法、傳送通知四種方法進行分析。為減少篇幅在這裡只是展示入口方法,核心呼叫的方法都標紅

1.1.3.1 設定屬性值      設定屬性值是BaseModelMBean中setAttribute方法作為入口根據方法名獲取相關屬性,根據Mbean例項來獲取相應的方法,並進行呼叫

@Override
public void setAttribute(Attribute attribute) throws AttributeNotFoundException, MBeanException, ReflectionException
{
    //如果是動態Mbean並且不是BaseModelMBean 將屬性直接設定到資源
    if( (resource instanceof DynamicMBean) &&
            ! ( resource instanceof BaseModelMBean )) {
        try {
            ((DynamicMBean)resource).setAttribute(attribute);
        } catch (InvalidAttributeValueException e) {
            throw new MBeanException(e);
        }
        return;
    }
    // 驗證輸入引數
    if (attribute == null){
        throw new RuntimeOperationsException(new IllegalArgumentException("Attribute is null"), "Attribute is null");
    }

    String name = attribute.getName();
    Object value = attribute.getValue();
    if (name == null){
        throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name is null"), "Attribute name is null");
    }

    Object oldValue=null;

    //根據name獲取指定的方法並呼叫相應的方法
    Method m=managedBean.getSetter(name,this,resource);

    try {
        //檢查這個方法所在的類是否與當前例項類相同或是當前例項的超類或介面 如果是呼叫當前例項的方法 反之呼叫資源類的方法
        if( m.getDeclaringClass().isAssignableFrom( this.getClass()) ) {
            m.invoke(this, new Object[] { value });
        } else {
            m.invoke(resource, new Object[] { value });
        }
    } catch (InvocationTargetException e) {
      。。。。
    }
}

1.1.3.2 獲取屬性值      獲取屬性入口 BaseModelMBean—》getAttribute      獲取屬性是點選到管理介面具體屬性的時候進行顯示然後會呼叫到當前方法

public Object getAttribute(String name) throws AttributeNotFoundException, MBeanException, ReflectionException {
    //如果name為空扔出異常
    if (name == null){
        throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name is null"), "Attribute name is null");
    }
    //如果例項是繼承DynamicMBean並且不是BaseModelMBean則呼叫其自己獲取屬性的方式
    //這種情況在tomcat比較常見 如ConnectorMBean它利用自己的setter/getter屬性 resource是註冊的例項
    if( (resource instanceof DynamicMBean) && ! ( resource instanceof BaseModelMBean )) {
        return ((DynamicMBean)resource).getAttribute(name);
    }

    //這個方法的功能是根據name獲取的相關屬性,再根據屬性例項找到方法名,利用反射獲取這個方法
    Method m=managedBean.getGetter(name, this, resource);

    Object result = null;
    try {
        //獲取這個方法所在的類 可能是當前類也有可能是其父類
        Class<?> declaring = m.getDeclaringClass();
        //如果條件為真,declaring是其父類 這直接通過當前例項呼叫 這樣完全java繼承方法的實現思想
        //這種情況出現於Mbean例項繼承BaseModelMBean
        if( declaring.isAssignableFrom(this.getClass()) ) {
            result = m.invoke(this, NO_ARGS_PARAM );
        } else {
            //利用Mbean例項直接呼叫方法 這種情況是常見的
            result = m.invoke(resource, NO_ARGS_PARAM );
        }
    } catch (InvocationTargetException e) {
     。。。。。。

    return (result);
}

1.1.3.3 呼叫方法      呼叫入口: BaseModelMBean—》invoke      點選相應操作則會呼叫

@Override
public Object invoke(String name, Object[] params, String[] signature) throws MBeanException, ReflectionException {
    if( (resource instanceof DynamicMBean) &&
            ! ( resource instanceof BaseModelMBean )) {
        return ((DynamicMBean)resource).invoke(name, params, signature);
    }


    if (name == null){
        throw new RuntimeOperationsException(new IllegalArgumentException("Method name is null"), "Method name is null");
    }

    //根據引數和簽名獲取相應的方法
    Method method= managedBean.getInvoke(name, params, signature, this, resource);

    Object result = null;
    try {
        if( method.getDeclaringClass().isAssignableFrom( this.getClass()) ) {
            result = method.invoke(this, params );
        } else {
            result = method.invoke(resource, params);
        }
    } catch (InvocationTargetException e) {
     。。。。。。
    return (result);

}

1.1.3.4 傳送通知      傳送通知需要從兩方面進行考慮,第一方面是客戶端進行連線要將相應的監聽器加入另一方面是在呼叫相應事件則通過相應方法傳送給注入的監聽器,這樣就實現了相應的訊息通知

     接受接聽器: BaseModelMBean –》addNotificationListener

public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws IllegalArgumentException {
    //如果監聽器為空 則扔出異常不合法引數
    if (listener == null){
        throw new IllegalArgumentException("Listener is null");
    }

    //廣播例項控制代碼為空則例項化一個BaseNotificationBroadcaster例項
    if (generalBroadcaster == null){
        generalBroadcaster = new BaseNotificationBroadcaster();
    }

    generalBroadcaster.addNotificationListener(listener, filter, handback);

    if (attributeBroadcaster == null){
        attributeBroadcaster = new BaseNotificationBroadcaster();
    }

    attributeBroadcaster.addNotificationListener(listener, filter, handback);

}

     傳送訊息: BaseModelMBean –-> sendAttributeChangeNotification

@Override
public void sendAttributeChangeNotification(AttributeChangeNotification notification) throws MBeanException, RuntimeOperationsException {
    //這個通知是在做了修改操作之後構建的一個操作 如果為空 則必然扔出異常
    if (notification == null){
        throw new RuntimeOperationsException(new IllegalArgumentException("Notification is null"), "Notification is null");
    }
    //如果廣播為空則意味著沒有監聽器 其是在連線的時候例項化了一個BaseNotificationBroadcaster
    if (attributeBroadcaster == null){
        //意味著沒有註冊監聽器
        return;
    }
    attributeBroadcaster.sendNotification(notification);
}

     由上可值Mbean得動態操作都是在BaseModelMBean這個類中,JMX的分析到這裡告一段落 要想更清除的理解則需要再次到tomcat這個環境以及從底層rmi實現方面進行了解,後期會補上這些內容