首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

擴展 Spring JMX 支援:外部 XML 元資料與通知事件實戰

     編輯:Java 相關知識

前言

Spring 1.2+ 提供優秀的 JMX 整合,但預設配置缺乏細粒度控制。本文展示如何擴展 Spring JMX:透過外部 XML 定義 MBean 元資料 (類似 Hibernate mapping),並為屬性變更、方法執行前後自動發送通知事件,實現零侵入式管理監控。

核心擴展架構

Spring JMX Extension Architecture
├── MBeanDescriptorEnabledExporter (擴展導出器)
│   └── 創建 ModelMBeanExtension 替代預設 ModelMBean
├── ModelMBeanExtension (擴展 ModelMBean)
│   ├── 重寫 invoke() → 發送 before/after 通知
│   ├── 重寫 setAttribute() → 發送屬性變更通知
│   └── 內建 NotificationInfoMap 快速查找
├── MBeanDescriptorBasedAssembler (自訂裝配器)
│   └── 從 classpath 載入 {ClassName}.mbean.xml 產生 ModelMBeanInfo
└── XML Metadata Model (JDOM 解析)
    ├── MBeanDescriptor (根模型)
    ├── AttributeDescriptor, OperationDescriptor, NotificationDescriptor
    └── MBeanDescriptorUtil (載入工具)

一、外部 XML 元資料定義

範例:ExampleService.mbean.xml

<?xml version="1.0" encoding="UTF-8"?>
<mbean name="ExampleService" description="Example Service"
       type="com.claudeduguay.jmx.demo.server.ExampleService">
    <attribute name="propertyValue"
               description="Property Value Access"
               type="java.lang.String"
               readable="true" writable="true" />
    <operation name="stopService"
               description="Stop Example Service" />
    <operation name="startService"
               description="Start Example Service" />
    <notification name="PropertyValueSet"
                  types="example.service.set.propertyValue"
                  description="PropertyValue was set" />
    <notification name="BeforeStartService"
                  types="example.service.before.startService"
                  description="Example Service is Starting" />
    <notification name="AfterStartService"
                  types="example.service.after.startService"
                  description="Example Service is Started" />
    <notification name="BeforeStopService"
                  types="example.service.before.stopService"
                  description="Example Service is Stopping" />
    <notification name="AfterStopService"
                  types="example.service.after.stopService"
                  description="Example Service is Stopped" />
</mbean>

命名規範:通知類型字串

  • set.{attributeName}:屬性設定通知
  • before.{methodName}:方法執行前通知
  • after.{methodName}:方法執行後通知
  • 完整格式:{domain}.{type}.{name} (如 example.service.set.propertyValue)

二、核心擴展代碼實作

1. ModelMBeanExtension - 通知發送核心

public class ModelMBeanExtension extends RequiredModelMBean {
    private NotificationInfoMap notificationInfoMap;
    private ModelMBeanInfo modelMBeanInfo;
    private Object managedBean;

    public ModelMBeanExtension() throws MBeanException {}
    
    public ModelMBeanExtension(ModelMBeanInfo modelMBeanInfo) throws MBeanException {
        super(modelMBeanInfo);
        this.modelMBeanInfo = modelMBeanInfo;
        this.notificationInfoMap = new NotificationInfoMap(modelMBeanInfo);
    }

    @Override
    public void setModelMBeanInfo(ModelMBeanInfo modelMBeanInfo) throws MBeanException {
        this.modelMBeanInfo = modelMBeanInfo;
        this.notificationInfoMap = new NotificationInfoMap(modelMBeanInfo);
        super.setModelMBeanInfo(modelMBeanInfo);
    }

    @Override
    public MBeanNotificationInfo[] getNotificationInfo() {
        return modelMBeanInfo.getNotifications(); // 僅回傳 XML 定義的通知
    }

    @Override
    public void setManagedResource(Object managedBean, String type)
            throws MBeanException, RuntimeOperationsException,
                   InstanceNotFoundException, InvalidTargetObjectTypeException {
        super.setManagedResource(managedBean, type);
        this.managedBean = managedBean;
    }

    // 方法調用攔截:before/after 通知
    @Override
    public Object invoke(String name, Object[] args, String[] signature)
            throws MBeanException, ReflectionException {
        maybeSendMethodNotification("before", name);
        Object returnValue = super.invoke(name, args, signature);
        maybeSendMethodNotification("after", name);
        return returnValue;
    }

    // 屬性設定攔截:屬性變更通知
    @Override
    public void setAttribute(Attribute attribute)
            throws MBeanException, AttributeNotFoundException,
                   InvalidAttributeValueException, ReflectionException {
        try {
            Method setter = ModelMBeanUtil.findSetMethod(modelMBeanInfo, managedBean, attribute.getName());
            setter.invoke(managedBean, attribute.getValue());
            maybeSendAttributeNotification(attribute);
        } catch (InvocationTargetException | IllegalAccessException e) {
            throw new MBeanException(e);
        }
    }

    @Override
    public Object getAttribute(String name) throws MBeanException,
            AttributeNotFoundException, ReflectionException {
        try {
            Method getter = ModelMBeanUtil.findGetMethod(modelMBeanInfo, managedBean, name);
            return getter.invoke(managedBean, new Object[0]);
        } catch (InvocationTargetException | IllegalAccessException e) {
            throw new MBeanException(e);
        }
    }

    private void maybeSendMethodNotification(String type, String name) throws MBeanException {
        MBeanNotificationInfo info = notificationInfoMap.findNotificationInfo(type, name);
        if (info != null) {
            long timeStamp = System.currentTimeMillis();
            String notificationType = ModelMBeanUtil.matchType(info, "." + type + "." + name);
            sendNotification(new Notification(notificationType, this, timeStamp, info.getDescription()));
        }
    }

    private void maybeSendAttributeNotification(Attribute attribute) throws MBeanException {
        String name = attribute.getName();
        MBeanNotificationInfo info = notificationInfoMap.findNotificationInfo("set", name);
        if (info != null) {
            Object oldValue = getAttribute(name);
            Object newValue = attribute.getValue();
            long timeStamp = System.currentTimeMillis();
            String notificationType = ModelMBeanUtil.matchType(info, ".set." + name);
            sendNotification(new AttributeChangeNotification(
                this, timeStamp, timeStamp, info.getDescription(),
                info.getName(), notificationType, oldValue, newValue));
        }
    }
}

2. NotificationInfoMap - 快速查找索引

public class NotificationInfoMap {
    private Map map = new HashMap<>();

    public NotificationInfoMap(ModelMBeanInfo info) {
        for (MBeanNotificationInfo n : info.getNotifications()) {
            String[] types = n.getNotifTypes();
            for (String type : types) {
                map.put(type, n); // key: "example.service.set.propertyValue"
            }
        }
    }

    public MBeanNotificationInfo findNotificationInfo(String prefix, String name) {
        return map.get(prefix + "." + name); // 如 "set.propertyValue"
    }
}

3. MBeanDescriptorBasedAssembler - XML 載入裝配器

public class MBeanDescriptorBasedAssembler implements MBeanInfoAssembler {
    @Override
    public ModelMBeanInfo getMBeanInfo(Object managedBean, String beanKey) {
        String className = managedBean.getClass().getName();
        String resourcePath = className.replace('.', '/') + ".mbean.xml";
        
        ClassPathResource resource = new ClassPathResource(resourcePath);
        try (InputStream input = resource.getInputStream()) {
            MBeanDescriptor descriptor = MBeanDescriptorUtil.read(input);
            return descriptor.createMBeanInfo();
        } catch (IOException e) {
            throw new IllegalStateException("無法載入 MBean 描述檔: " + resourcePath, e);
        }
    }
}

4. MBeanDescriptorEnabledExporter - 擴展導出器

public class MBeanDescriptorEnabledExporter extends MBeanExporter {
    private MBeanInfoAssembler mBeanInfoAssembler;

    public MBeanDescriptorEnabledExporter() {
        setAssembler(new MBeanDescriptorBasedAssembler());
    }

    @Override
    public ModelMBean createModelMBean() throws MBeanException {
        // 僅當使用自訂裝配器時才建立擴展 ModelMBean
        if (mBeanInfoAssembler instanceof MBeanDescriptorBasedAssembler) {
            return new ModelMBeanExtension();
        }
        return super.createModelMBean();
    }

    @Override
    public void setAssembler(MBeanInfoAssembler mBeanInfoAssembler) {
        this.mBeanInfoAssembler = mBeanInfoAssembler;
        super.setAssembler(mBeanInfoAssembler);
    }
}

三、Spring 配置整合

SpringJmxServer.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="exporter" 
          class="com.claudeduguay.mbeans.spring.MBeanDescriptorEnabledExporter">
        <property name="beans">
            <map>
                <entry key="Services:name=ExampleService" value-ref="ExampleService" />
                <entry key="MX4J:name=HttpAdaptor" value-ref="HttpAdaptor" />
                <entry key="MX4J:name=XSLTProcessor" value-ref="XSLTProcessor" />
            </map>
        </property>
    </bean>

    <bean id="XSLTProcessor" class="mx4j.tools.adaptor.http.XSLTProcessor" />
    <bean id="HttpAdaptor" class="mx4j.tools.adaptor.http.HttpAdaptor">
        <property name="processor" ref="XSLTProcessor"/>
        <property name="port" value="8080"/>
    </bean>

    <bean id="ExampleService" 
          class="com.claudeduguay.jmx.demo.server.ExampleService" />
</beans>

啟動類 (需啟動 JMX Remote)

public class SpringJmxServer {
    public static void main(String[] args) throws Exception {
        // JVM 參數: -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
        ApplicationContext context = new ClassPathXmlApplicationContext(
            "com/claudeduguay/jmx/demo/server/SpringJmxServer.xml");
        HttpAdaptor httpAdaptor = (HttpAdaptor) context.getBean("HttpAdaptor");
        httpAdaptor.start();
    }
}

四、被管理服務範例

public class ExampleService {
    private String propertyValue = "default value";

    public String getPropertyValue() {
        System.out.println("ExampleService: Get Property Value");
        return propertyValue;
    }

    public void setPropertyValue(String propertyValue) {
        System.out.println("ExampleService: Set Property Value");
        this.propertyValue = propertyValue;
    }

    public void startService() {
        System.out.println("ExampleService: Start Service Called");
    }

    public void stopService() {
        System.out.println("ExampleService: Stop Service Called");
    }
}

五、客戶端驗證通知接收

public class SpringJmxClient implements NotificationListener {
    @Override
    public void handleNotification(Notification notification, Object handback) {
        System.out.println("Notification: " + notification.getMessage());
    }

    public static void main(String[] args) throws Exception {
        SpringJmxClient listener = new SpringJmxClient();
        String url = "service:jmx:rmi:///jndi/rmi://localhost:8999/jmxrmi";
        JMXServiceURL serviceURL = new JMXServiceURL(url);
        JMXConnector connector = JMXConnectorFactory.connect(serviceURL, null);
        MBeanServerConnection mbsc = connector.getMBeanServerConnection();

        ObjectName serviceName = ObjectName.getInstance("Services:name=ExampleService");
        mbsc.addNotificationListener(serviceName, listener, null, null);

        // 觸發各種操作
        mbsc.invoke(serviceName, "startService", null, null);           // before/after 通知
        mbsc.setAttribute(serviceName, new Attribute("propertyValue", "new value")); // set 通知
        System.out.println("Property: " + mbsc.getAttribute(serviceName, "propertyValue"));
        mbsc.invoke(serviceName, "stopService", null, null);            // before/after 通知
    }
}

六、預期輸出

Notification: Example Service is Starting      (before.startService)
Notification: Example Service is Started       (after.startService)
Notification: PropertyValue was set            (set.propertyValue, old=default value, new=new value)
Notification: Example Service is Stopping      (before.stopService)
Notification: Example Service is Stopped       (after.stopService)

七、HTTP 管理介面驗證

瀏覽器開啟 https://localhost:8080 (MX4J HttpAdaptor),可視覺化操作屬性與方法,客戶端同樣接收通知。

擴展優勢總結

特性預設 Spring JMX本擴展方案
元資料定義Java 註解 / 介面 / 內省外部 XML 檔案 (類路徑)
通知事件需手動實作 NotificationBroadcasterXML 宣告自動發送 (set/before/after)
訊息客製化困難XML 中自訂 description
程式碼侵入性低 (需註解或介面)零 (POJO 完全不知情)
版本管理隨代碼編譯XML 可獨立部署更新

適用場景

  • 第三方庫類別無法修改原始碼需加入管理
  • 運維團隊需獨立調整監控指標不重新編譯
  • 多環境部署 (開發/測試/生產) 不同監控粒度
  • 審計追蹤需記錄所有關鍵屬性變更與方法調用
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved