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實現方面進行了解,後期會補上這些內容

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章