首页 > 编程语言 >Java 中文官方教程 2022 版(三十七)

Java 中文官方教程 2022 版(三十七)

时间:2024-04-12 15:11:07浏览次数:25  
标签:XML JMX Java 2022 示例 三十七 API MBean

原文:docs.oracle.com/javase/tutorial/reallybigindex.html

使用 GUI API 的 JDBC

原文:docs.oracle.com/javase/tutorial/jdbc/basics/jdbcswing.html

示例CoffeesFrame.java演示了如何将 JDBC 与 GUI API 集成,特别是 Swing API。它在表中显示了COFFEES数据库表的内容,并包含字段和按钮,使您可以向表中添加行。以下是此示例的屏幕截图:

示例 CoffeeFrames.java 的屏幕截图

该示例包含五个文本字段,对应于COFFEES表中的每一列。它还包含三个按钮:

  • 向表中添加行:根据文本字段中输入的数据向示例表中添加一行。

  • 更新数据库:根据示例表中的数据更新COFFEES表。

  • 放弃更改:检索COFFEES表的内容,替换示例表中的现有数据。

这个示例(需要CoffeesTableModel.java)演示了将 JDBC 与 Swing API 集成的一般步骤:

  1. 实现TableModel接口

  2. 实现RowSetListener接口

  3. 布局 Swing 组件

  4. 为示例中的按钮添加监听器

实现 javax.swing.event.TableModel

TableModel接口使得 Java Swing 应用程序能够管理JTable对象中的数据。示例CoffeesTableModel.java实现了这个接口。它指定了JTable对象应该如何从RowSet对象中检索数据并在表中显示。

注意:尽管此示例在 Swing 应用程序中显示了COFFEES表的内容,但CoffeesTableModel类应该适用于任何 SQL 表,只要它的数据可以用String对象表示。(但是,用于向COFFEES添加行的字段,这些字段在CoffeesFrame类中指定,必须针对其他 SQL 表进行修改。)

在实现TableModel接口的方法之前,CoffeeTableModel类的构造函数初始化了为这些实现方法所需的各种成员变量,如下所示:

public CoffeesTableModel(CachedRowSet rowSetArg)
    throws SQLException {

    this.coffeesRowSet = rowSetArg;
    this.metadata = this.coffeesRowSet.getMetaData();
    numcols = metadata.getColumnCount();

    // Retrieve the number of rows.
    this.coffeesRowSet.beforeFirst();
    this.numrows = 0;
    while (this.coffeesRowSet.next()) {
        this.numrows++;
    }
    this.coffeesRowSet.beforeFirst();
}

以下描述了在这个构造函数中初始化的成员变量:

  • CachedRowSet coffeesRowSet:存储COFFEES表的内容。

    本示例使用RowSet对象,特别是CachedRowSet对象,而不是ResultSet对象,有两个原因。CachedRowSet对象使应用程序的用户能够对其中包含的数据进行更改,而无需连接到数据库。此外,因为CachedRowSet对象是一个 JavaBeans 组件,它可以在发生某些事情时通知其他组件。在本示例中,当向CachedRowSet对象添加新行时,它会通知渲染表中数据的 Swing 组件刷新自身并显示新行。

  • ResultSetMetaData metadata: 检索表COFFEES中的列数以及每个列的名称。

  • int numcols, numrows: 分别存储表COFFEES中的列数和行数。

CoffeesTableModel.java示例实现了TableModel接口中的以下方法:

  • Class<?> getColumnClass(int columnIndex): 返回列中所有单元格值的最具体的超类。

  • int getColumnCount(): 返回模型中的列数。

  • String getColumnName(int columnIndex): 返回由参数columnIndex指定的列的名称。

  • int getRowCount(): 返回模型中的行数。

  • Object getValueAt(int rowIndex, int columnIndex): 返回交叉点处的单元格的值,该单元格位于列columnIndex和行rowIndex的交叉点处。

  • boolean isCellEditable(int rowIndex, int columnIndex): 如果列rowIndex和行columnIndex的交叉点处的单元格可以编辑,则返回 true。

以下方法未实现,因为此示例不允许用户直接编辑表的内容:

  • void addTableModelListener(TableModelListener l): 向列表中添加一个侦听器,每当数据模型发生更改时通知该侦听器。

  • void removeTableModelListener(TableModelListener l): 从列表中移除一个侦听器,每当数据模型发生更改时通知该侦听器。

  • void setValueAt(Object aValue, int rowIndex, int columnIndex): 将交叉点处的单元格中的值设置为对象aValue,该单元格位于列columnIndex和行rowIndex的交叉点处。

实现 getColumnCount 和 getRowCount

getColumnCountgetRowCount方法分别返回成员变量numcolsnumrows的值:

public int getColumnCount() {
    return numcols;
}

public int getRowCount() {
    return numrows;
}

实现 getColumnClass

getColumnClass方法返回指定列的数据类型。为了保持简单,此方法返回String类,从而将表中的所有数据转换为String对象。JTable类使用此方法确定如何在 GUI 应用程序中呈现数据。

public Class getColumnClass(int column) {
    return String.class;
}

实现 getColumnName

getColumnName方法返回指定列的名称。JTable类使用此方法为其每一列加上标签。

public String getColumnName(int column) {
    try {
        return this.metadata.getColumnLabel(column + 1);
    } catch (SQLException e) {
        return e.toString();
    }
}

实现 getColumnAt

getColumnAt方法检索行集coffeesRowSet中指定行和列的值。JTable类使用此方法来填充其表格。请注意,SQL 从 1 开始对其行和列进行编号,但TableModel接口从 0 开始;这就是为什么rowIndexcolumnIndex的值要增加 1 的原因。

public Object getValueAt(int rowIndex, int columnIndex) {

    try {
        this.coffeesRowSet.absolute(rowIndex + 1);
        Object o = this.coffeesRowSet.getObject(columnIndex + 1);
        if (o == null)
            return null;
        else
            return o.toString();
    } catch (SQLException e) {
        return e.toString();
    }
}

实现 isCellEditable

因为此示例不允许用户直接编辑表的内容(行是由另一个窗口控件添加的),所以无论rowIndexcolumnIndex的值如何,此方法都返回false

public boolean isCellEditable(int rowIndex, int columnIndex) {
    return false;
}

实现 javax.sql.RowSetListener

CoffeesFrame仅实现接口RowSetListener中的一个方法rowChanged。当用户向表中添加行时,将调用此方法。

public void rowChanged(RowSetEvent event) {

    CachedRowSet currentRowSet =
        this.myCoffeesTableModel.coffeesRowSet;

    try {
        currentRowSet.moveToCurrentRow();
        myCoffeesTableModel = new CoffeesTableModel(
            myCoffeesTableModel.getCoffeesRowSet());
        table.setModel(myCoffeesTableModel);

    } catch (SQLException ex) {

        JDBCTutorialUtilities.printSQLException(ex);

        // Display the error in a dialog box.

        JOptionPane.showMessageDialog(
            CoffeesFrame.this,
            new String[] {
                // Display a 2-line message
                ex.getClass().getName() + ": ",
                ex.getMessage()
            }
        );
    }
}

此方法更新 GUI 应用程序中的表格。

布置 Swing 组件

CoffeesFrame的构造函数初始化并布置 Swing 组件。以下语句检索COFFEES表的内容,将内容存储在CachedRowSet对象myCachedRowSet中,并初始化JTable Swing 组件:

CachedRowSet myCachedRowSet = getContentsOfCoffeesTable();
myCoffeesTableModel = new CoffeesTableModel(myCachedRowSet);
myCoffeesTableModel.addEventHandlersToRowSet(this);

// Displays the table   
table = new JTable(); 
table.setModel(myCoffeesTableModel);

如前所述,此示例使用RowSet对象(特别是CachedRowSet对象)而不是ResultSet对象来表示COFFEES表的内容。

方法CoffeesFrame.getContentsOfCoffeesTable检索表COFFEES的内容。

方法CoffeesTableModel.addEventHandlersToRowSet将在CoffeesFrame类中定义的事件处理程序添加到行集成员变量CoffeesTableModel.coffeesRowSet中。这使得CoffeesFrame类能够通知行集coffeesRowSet任何事件,特别是当用户点击按钮Add row to tableUpdate databaseDiscard changes时。当行集coffeesRowSet被通知到这些变化之一时,方法CoffeesFrame.rowChanged被调用。

语句table.setModel(myCoffeesTableModel)指定使用CoffeesTableModel对象myCoffeesTableModel来填充JTable Swing 组件table

以下语句指定CoffeesFrame类使用布局GridBagLayout来布置其 Swing 组件:

Container contentPane = getContentPane();
contentPane.setComponentOrientation(
    ComponentOrientation.LEFT_TO_RIGHT);
contentPane.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();

有关使用布局GridBagLayout的更多信息,请参见如何使用 GridBagLayout 中的 JFC/Swing 创建 GUI。

查看CoffeesFrame.java的源代码,了解如何将此示例的 Swing 组件添加到布局GridBagLayout中。

为按钮添加监听器

以下语句向按钮Add row to table添加了一个监听器:

button_ADD_ROW.addActionListener(
    new ActionListener() {

    public void actionPerformed(ActionEvent e) {

        JOptionPane.showMessageDialog(
            CoffeesFrame.this, new String[] {
                "Adding the following row:",
                "Coffee name: [" +
                textField_COF_NAME.getText() +
                "]",
                "Supplier ID: [" +
                textField_SUP_ID.getText() + "]",
                "Price: [" +
                textField_PRICE.getText() + "]",
                "Sales: [" +
                textField_SALES.getText() + "]",
                "Total: [" +
                textField_TOTAL.getText() + "]"
            }
        );

        try {
            myCoffeesTableModel.insertRow(
                textField_COF_NAME.getText(),
                Integer.parseInt(textField_SUP_ID.getText().trim()),
                Float.parseFloat(textField_PRICE.getText().trim()),
                Integer.parseInt(textField_SALES.getText().trim()),
                Integer.parseInt(textField_TOTAL.getText().trim())
            );
        } catch (SQLException sqle) {
            displaySQLExceptionDialog(sqle);
        }
    }
});

当用户点击此按钮时,它执行以下操作:

  • 创建一个消息对话框,显示要添加到表中的行。

  • 调用方法CoffeesTableModel.insertRow,将行添加到成员变量CoffeesTableModel.coffeesRowSet中。

如果抛出SQLException,则方法CoffeesFrame.displaySQLExceptionDialog将创建一个消息对话框,显示SQLException的内容。

以下语句向按钮更新数据库添加了一个监听器:

button_UPDATE_DATABASE.addActionListener(
    new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            try {
                myCoffeesTableModel.coffeesRowSet.acceptChanges();
                msgline.setText("Updated database");
            } catch (SQLException sqle) {
                displaySQLExceptionDialog(sqle);
                // Now revert back changes
                try {
                    createNewTableModel();
                    msgline.setText("Discarded changes");
                } catch (SQLException sqle2) {
                    displaySQLExceptionDialog(sqle2);
                }
            }
        }
    }
);

当用户点击此按钮时,将使用行集myCoffeesTableModel.coffeesRowSet的内容更新表COFFEES

以下语句向按钮放弃更改添加了一个监听器:

button_DISCARD_CHANGES.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        try {
            createNewTableModel();
        } catch (SQLException sqle) {
            displaySQLExceptionDialog(sqle);
        }
    }
});

当用户点击此按钮时,将调用方法CoffeesFrame.createNewTableModel,该方法重新填充JTable组件,其中包含COFFEES表的内容。

路线:Java 管理扩展(JMX)

原文:docs.oracle.com/javase/tutorial/jmx/index.html

Java 管理扩展(JMX) 路线介绍了包含在 Java 平台标准版(Java SE 平台)中的 JMX 技术,通过示例展示了如何使用 JMX 技术的最重要特性。

提供了 JMX 技术的简要描述,包括其目的和主要特点。

介绍了 JMX 技术的基本概念,管理 bean,又称为 MBeans。本课程还介绍了 MXBeans。

介绍了 JMX 技术的通知机制。

展示了如何实现 JMX API 的远程管理能力以及如何创建 JMX 客户端应用程序。

trail icon 接下来怎么做 提供了指向更高级文档的指针,描述了 JMX 技术。

教训:JMX 技术概述

原文:docs.oracle.com/javase/tutorial/jmx/overview/index.html

Java 管理扩展(JMX)技术是 Java 平台标准版(Java SE 平台)的标准部分。JMX 技术是在 Java 2 平台标准版(J2SE)5.0 发布中添加到平台中的。

JMX 技术提供了一种简单、标准的管理资源(如应用程序、设备和服务)的方式。由于 JMX 技术是动态的,您可以使用它来监视和管理资源的创建、安装和实施过程。您还可以使用 JMX 技术来监视和管理 Java 虚拟机(Java VM)。

JMX 规范为管理和监视应用程序和网络定义了 Java 编程语言中的架构、设计模式、API 和服务。

使用 JMX 技术,一个给定的资源由一个或多个称为托管 BeanMBeans的 Java 对象仪器化。这些 MBeans 注册在一个核心管理对象服务器中,称为MBean 服务器。MBean 服务器充当管理代理,并可以在大多数已启用 Java 编程语言的设备上运行。

规范定义了您用于管理已正确配置的任何资源的 JMX 代理。JMX 代理由一个 MBean 服务器(其中注册了 MBeans)和一组用于处理 MBeans 的服务组成。通过这种方式,JMX 代理直接控制资源并使其可供远程管理应用程序使用。

资源的仪器化方式与管理基础设施完全独立。因此,资源可以被管理,而不管它们的管理应用是如何实现的。

JMX 技术定义了标准连接器(称为 JMX 连接器),使您可以从远程管理应用程序访问 JMX 代理。使用不同协议的 JMX 连接器提供相同的管理接口。因此,管理应用程序可以透明地管理资源,而不管使用的通信协议是什么。只要这些系统或应用程序支持 JMX 代理,JMX 代理也可以被不符合 JMX 规范的系统或应用程序使用。

为什么使用 JMX 技术?

原文:docs.oracle.com/javase/tutorial/jmx/overview/why.html

JMX 技术为开发人员提供了一种灵活的手段来为基于 Java 技术的应用程序(Java 应用程序)提供仪器化,创建智能代理,实现分布式管理中间件和管理器,并将这些解决方案顺利集成到现有的管理和监控系统中。

  • JMX 技术使得 Java 应用程序可以在不需要大量投资的情况下进行管理

    基于 JMX 技术的代理(JMX 代理)可以在大多数支持 Java 技术的设备上运行。因此,Java 应用程序可以在设计上几乎没有影响地变得可管理。一个 Java 应用程序只需要嵌入一个托管对象服务器,并将部分功能作为一个或多个托管 bean(MBeans)注册到对象服务器中。这就是从管理基础设施中受益所需的全部。

  • JMX 技术提供了一种标准的方式来管理 Java 应用程序、系统和网络

    例如,Java 平台企业版(Java EE)5 应用服务器符合 JMX 架构,因此可以使用 JMX 技术进行管理。

  • JMX 技术可用于对 Java 虚拟机进行开箱即用的管理

    Java 虚拟机(Java VM)使用 JMX 技术进行高度仪器化。您可以启动一个 JMX 代理来访问内置的 Java VM 仪器,从而远程监视和管理 Java VM。

  • JMX 技术提供了一个可扩展的、动态的管理架构

    每个 JMX 代理服务都是一个独立的模块,可以根据需求插入到管理代理中。这种基于组件的方法意味着 JMX 解决方案可以从小型设备扩展到大型电信交换机等更大的设备。JMX 规范提供了一组核心代理服务。可以开发额外的服务,并在管理基础设施中动态加载、卸载或更新这些服务。

  • JMX 技术利用了现有的标准 Java 技术

    在需要时,JMX 规范引用现有的 Java 规范,例如 Java 命名和目录接口(J.N.D.I.)API。

  • 基于 JMX 技术的应用程序(JMX 应用程序)可以通过 NetBeans IDE 模块创建

    您可以从 NetBeans 更新中心获取一个模块(在 NetBeans 界面中选择工具 -> 更新中心),该模块使您可以使用 NetBeans IDE 创建 JMX 应用程序。这降低了 JMX 应用程序开发的成本。

  • JMX 技术与现有的管理解决方案和新兴技术集成

    JMX API 是任何管理系统供应商都可以实现的开放接口。JMX 解决方案可以使用查找和发现服务以及诸如 Jini 网络技术和服务位置协议(SLP)等协议。

JMX 技术的架构

原文:docs.oracle.com/javase/tutorial/jmx/overview/architecture.html

JMX 技术可以分为三个级别,如下:

  • 仪器化

  • JMX 代理

  • 远程管理

仪器化

要使用 JMX 技术管理资源,必须首先使用 Java 编程语言对资源进行仪器化。您使用称为 MBeans 的 Java 对象来实现对资源仪器化的访问。MBeans 必须遵循 JMX 规范中定义的设计模式和接口。这样做可以确保所有 MBeans 以标准化的方式提供受管理资源的仪器化。除了标准 MBeans 外,JMX 规范还定义了一种特殊类型的 MBean,称为 MXBean。MXBean 是仅引用预定义数据类型的 MBean。还有其他类型的 MBean,但本文将集中讨论标准 MBeans 和 MXBeans。

一旦资源被 MBeans 仪器化,就可以通过 JMX 代理进行管理。MBeans 不需要了解它们将操作的 JMX 代理。

MBeans 被设计为灵活、简单且易于实现。应用程序、系统和网络的开发人员可以使其产品以标准方式可管理,而无需了解或投资于复杂的管理系统。现有资源可以通过最小的努力变得可管理。

此外,JMX 规范的仪器化级别提供了通知机制。该机制使 MBeans 能够生成和传播通知事件给其他级别的组件。

JMX 代理

基于 JMX 技术的代理(JMX 代理)是一个标准的管理代理,直接控制资源并使其可供远程管理应用程序使用。JMX 代理通常位于控制资源的同一台机器上,但这并不是必需的。

JMX 代理的核心组件是MBean 服务器,一个注册 MBeans 的受管理对象服务器。JMX 代理还包括一组服务来管理 MBeans,并至少一个通信适配器或连接器,以允许管理应用程序访问。

当您实现一个 JMX 代理时,不需要了解它将管理的资源的语义或功能。事实上,JMX 代理甚至不需要知道它将服务的资源,因为任何按照 JMX 规范进行仪器化的资源都可以使用任何提供资源所需服务的 JMX 代理。同样,JMX 代理也不需要知道将访问它的管理应用程序的功能。

远程管理

JMX 技术仪器化可以通过许多不同的方式访问,可以通过现有的管理协议,如简单网络管理协议(SNMP)或专有协议来访问。 MBean 服务器依赖于协议适配器和连接器,使得 JMX 代理可以从代理的 Java 虚拟机(Java VM)外部的管理应用程序访问。

每个适配器通过特定协议提供了在 MBean 服务器中注册的所有 MBeans 的视图。例如,HTML 适配器可以在浏览器中显示一个 MBean。

连接器提供了一个管理端接口,处理管理者和 JMX 代理之间的通信。每个连接器通过不同的协议提供相同的远程管理接口。当远程管理应用程序使用此接口时,它可以通过网络透明地连接到 JMX 代理,而不受协议的限制。 JMX 技术提供了一种标准解决方案,用于基于 Java 远程方法调用(Java RMI)将 JMX 技术仪器化导出到远程应用程序。

监控和管理 Java 虚拟机

原文:docs.oracle.com/javase/tutorial/jmx/overview/javavm.html

JMX 技术也可以用于监控和管理 Java 虚拟机(Java VM)。

Java VM 具有内置的仪器,使您可以使用 JMX 技术监控和管理它。这些内置的管理实用程序通常被称为 Java VM 的开箱即用管理工具。为了监控和管理 Java VM 的不同方面,Java VM 包括一个平台 MBean 服务器和专门的 MXBeans,供符合 JMX 规范的管理应用程序使用。

平台 MXBeans 和平台 MBean 服务器

平台 MXBeans是 Java SE 平台提供的一组 MXBeans,用于监控和管理 Java VM 和 Java 运行时环境(JRE)的其他组件。每个平台 MXBean 封装了 Java VM 功能的一部分,例如类加载系统、即时(JIT)编译系统、垃圾收集器等。这些 MXBeans 可以通过符合 JMX 规范的监控和管理工具显示和交互,以便您监控和管理这些不同的 VM 功能。其中一种监控和管理工具是 Java SE 平台的 JConsole 图形用户界面(GUI)。

Java SE 平台提供了一个标准的平台 MBean 服务器,其中注册了这些平台 MXBeans。平台 MBean 服务器还可以注册您希望创建的任何其他 MBeans。

JConsole

Java SE 平台包括符合 JMX 规范的 JConsole 监控和管理工具。JConsole 使用 Java VM 的广泛仪器(平台 MXBeans)提供关于在 Java 平台上运行的应用程序的性能和资源消耗的信息。

开箱即用的管理实用程序在操作中

因为标准的实现 JMX 技术的监控和管理实用程序内置于 Java SE 平台中,您可以在不编写一行 JMX API 代码的情况下看到开箱即用的 JMX 技术在操作中。您可以通过启动 Java 应用程序,然后使用 JConsole 监控它来实现。

使用 JConsole 监控应用程序

本过程展示了如何监控记事本 Java 应用程序。在 Java SE 平台版本 6 之前的版本中,您需要使用以下选项启动要使用 JConsole 监控的应用程序。

-Dcom.sun.management.jmxremote

然而,Java SE 6 平台提供的 JConsole 版本可以连接到支持 Attach API 的任何本地应用程序。换句话说,任何在 Java SE 6 HotSpot VM 中启动的应用程序都会被 JConsole 自动检测到,不需要使用上述命令行选项启动。

  1. 启动记事本 Java 应用程序,使用以下命令在终端窗口中:

    java -jar 
        *jdk_home*/demo/jfc/Notepad/Notepad.jar
    
    

    其中jdk_home是安装 Java 开发工具包(JDK)的目录。如果您没有运行 Java SE 平台的 6 版本,您将需要使用以下命令:

    java -Dcom.sun.management.jmxremote -jar 
          *jdk_home*/demo/jfc/Notepad/Notepad.jar
    
    
  2. 一旦 Notepad 打开,在另一个终端窗口中,使用以下命令启动 JConsole:

    jconsole
    
    

    会显示一个新的连接对话框。

  3. 在新连接对话框中,从本地进程列表中选择Notepad.jar,然后点击连接按钮。

    JConsole 会打开并连接到Notepad.jar进程。当 JConsole 打开时,您将看到与 Notepad 相关的监控和管理信息概览。例如,您可以查看应用程序消耗的堆内存量,应用程序当前运行的线程数,以及应用程序消耗的中央处理单元(CPU)容量。

  4. 点击不同的 JConsole 选项卡。

    每个选项卡提供了关于 Notepad 运行的 Java 虚拟机不同功能区域的更详细信息。所有呈现的信息都是从本教程中提到的各种 JMX 技术 MXBeans 获取的。所有平台 MXBeans 都可以在 MBeans 选项卡中显示。MBeans 选项卡将在本教程的下一部分中进行讨论。

  5. 要关闭 JConsole,选择连接 -> 退出。

教程:介绍 MBeans

原文:docs.oracle.com/javase/tutorial/jmx/mbeans/index.html

本课程介绍了 JMX API 的基本概念,即受管理的 bean,或MBeans

一个 MBean 是一个受管理的 Java 对象,类似于 JavaBeans 组件,遵循 JMX 规范中设定的设计模式。一个 MBean 可以代表一个设备、一个应用程序或任何需要被管理的资源。MBeans 公开一个由以下内容组成的管理接口:

  • 一组可读或可写的属性,或两者兼有。

  • 一组可调用的操作。

  • 自我描述。

管理接口在 MBean 实例的整个生命周期中不会改变。当发生某些预定义事件时,MBeans 也可以发出通知。

JMX 规范定义了五种类型的 MBean:

  • 标准 MBeans

  • 动态 MBeans

  • 开放 MBeans

  • 模型 MBeans

  • MXBeans

本教程中的示例仅演示了最简单的 MBean 类型,即标准 MBeans 和 MXBeans。

标准 MBeans

原文:docs.oracle.com/javase/tutorial/jmx/mbeans/standard.html

本节介绍了一个简单的标准 MBean 示例。

通过编写一个名为 SomethingMBean 的 Java 接口和一个实现该接口的名为 Something 的 Java 类来定义标准 MBean。接口中的每个方法默认定义一个操作。属性和操作是遵循特定设计模式的方法。标准 MBean 由一个 MBean 接口和一个类组成。MBean 接口列出了所有公开属性和操作的方法。类实现此接口并提供受监视资源的功能。

以下部分将介绍一个标准 MBean 和一个简单的支持 JMX 技术的代理(JMX 代理)管理该 MBean。

MBean 接口

一个基本 MBean 接口的示例,HelloMBean,如下所示:


package com.example; 

public interface HelloMBean { 

    public void sayHello(); 
    public int add(int x, int y); 

    public String getName(); 

    public int getCacheSize(); 
    public void setCacheSize(int size); 
} 

按照惯例,一个 MBean 接口采用实现它的 Java 类的名称,后缀 MBean 添加在后面。在这种情况下,接口被称为 HelloMBean。实现此接口的 Hello 类将在下一节中描述。

根据 JMX 规范,一个 MBean 接口由具有名称和类型的属性组成,这些属性可读取并可能可写,另外还包括由 MBean 管理的应用程序可以调用的具有名称和类型的操作。HelloMBean 接口声明了两个操作:Java 方法 add()sayHello()

HelloMBean 声明了两个属性:Name 是一个只读字符串,CacheSize 是一个既可读又可写的整数。声明了 getter 和 setter 方法,允许受管应用程序访问并可能更改属性值。根据 JMX 规范的定义,getter 是任何不返回 void 并且名称以 get 开头的公共方法。getter 使管理者能够读取属性的值,其类型与返回对象的类型相同。setter 是任何接受单个参数并且名称以 set 开头的公共方法。setter 使管理者能够在属性中写入新值,其类型与参数的类型相同。

这些操作和属性的实现在下一节中展示。

MBean 实现

下面的 Hello Java 类实现了 HelloMBean MBean 接口:

package com.example; 

public class Hello ... 
    implements HelloMBean { 
    public void sayHello() { 
        System.out.println("hello, world"); 
    } 

    public int add(int x, int y) { 
        return x + y; 
    } 

    public String getName() { 
        return this.name; 
    }  

    public int getCacheSize() { 
        return this.cacheSize; 
    } 

    public synchronized void setCacheSize(int size) {
        ...

        this.cacheSize = size; 
        System.out.println("Cache size now " + this.cacheSize); 
    } 
    ...

    private final String name = "Reginald"; 
    private int cacheSize = DEFAULT_CACHE_SIZE; 
    private static final int 
        DEFAULT_CACHE_SIZE = 200; 
}

直接的 Hello 类提供了由 HelloMBean 声明的操作和属性的定义。sayHello()add() 操作非常简单,但实际操作可以根据需要简单或复杂。

还定义了获取Name属性和获取/设置CacheSize属性的方法。在此示例中,Name属性值永远不会改变。但是,在实际情况下,此属性可能随着受管资源的运行而发生变化。例如,该属性可能代表诸如正常运行时间或内存使用情况之类的统计信息。在这里,该属性仅仅是名称Reginald

调用setCacheSize方法可以修改CacheSize属性,将其从声明的默认值 200 改变。在实际情况下,更改CacheSize属性可能需要执行其他操作,例如丢弃条目或分配新条目。此示例仅仅打印一条消息以确认缓存大小已更改。但是,可以定义更复杂的操作,而不是简单调用println()

有了Hello MBean 及其接口的定义,它们现在可以用来管理它们所代表的资源,如下一节所示。

创建一个用于管理资源的 JMX 代理

一旦资源被 MBeans 进行了仪器化,该资源的管理就由 JMX 代理执行。

JMX 代理的核心组件是 MBean 服务器。MBean 服务器是一个托管对象服务器,其中注册了 MBeans。JMX 代理还包括一组服务来管理 MBeans。有关 MBean 服务器实现的详细信息,请参阅MBeanServer接口的 API 文档。

接下来的Main类代表了一个基本的 JMX 代理:

package com.example; 

import java.lang.management.*; 
import javax.management.*; 

public class Main { 

    public static void main(String[] args) 
        throws Exception { 

        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 
        ObjectName name = new ObjectName("com.example:type=Hello"); 
        Hello mbean = new Hello(); 
        mbs.registerMBean(mbean, name); 

        ...

        System.out.println("Waiting forever..."); 
        Thread.sleep(Long.MAX_VALUE); 
    } 
} 

JMX 代理Main首先通过调用java.lang.management.ManagementFactory类的getPlatformMBeanServer()方法来获取由平台创建和初始化的 MBean 服务器。如果平台尚未创建 MBean 服务器,则getPlatformMBeanServer()会通过调用 JMX 方法MBeanServerFactory.createMBeanServer()自动创建 MBean 服务器。Main获取的MBeanServer实例命名为mbs

接下来,Main为将要创建的 MBean 实例定义了一个对象名称。每个 JMX MBean 都必须有一个对象名称。对象名称是 JMX 类ObjectName的一个实例,并且必须符合 JMX 规范定义的语法。换句话说,对象名称必须包含一个域和一组键-属性。在Main定义的对象名称中,域是com.example(示例 MBean 所在的包)。此外,键-属性声明此对象是Hello类型。

创建了一个名为mbeanHello对象实例。然后,通过将对象和对象名称传递给 JMX 方法MBeanServer.registerMBean(),将名为mbeanHello对象注册为 MBean 在 MBean 服务器mbs中。

在 MBean 服务器中注册了Hello MBean 后,Main只是等待对Hello执行管理操作。在这个示例中,这些管理操作包括调用sayHello()add(),以及获取和设置属性值。

运行标准 MBean 示例

在检查了示例类之后,现在可以运行示例了。在这个示例中,使用 JConsole 与 MBean 交互。

要运行示例,请按照以下步骤操作:

  1. 将 JMX API 示例类的包jmx_examples.zip保存到你的工作目录work_dir

  2. 使用以下命令在终端窗口中解压示例类的包。

    unzip jmx_examples.zip
    
    
  3. work_dir目录中编译示例 Java 类。

    javac com/example/*.java
    
    
  4. 如果你正在运行 Java 开发工具包(JDK)版本 6,请使用以下命令启动Main应用程序。

    java com.example.Main
    
    

    如果你使用的 JDK 版本低于 6,你需要使用以下选项启动Main应用程序,以便监控和管理应用程序。

    java -Dcom.sun.management.jmxremote example.Main
    
    

    显示Main正在等待某些事件发生的确认。

  5. 在同一台机器的不同终端窗口中启动 JConsole。

    jconsole
    
    

    显示新连接对话框,列出了可以连接的正在运行的 JMX 代理的列表。

  6. 在新连接对话框中,从列表中选择com.example.Main,然后点击连接。

    显示您平台当前活动的摘要。

  7. 点击 MBeans 选项卡。

    此面板显示当前在 MBean 服务器中注册的所有 MBean。

  8. 在左侧框架中,展开 MBean 树中的com.example节点。

    你会看到由Main创建和注册的示例 MBean Hello。如果你点击Hello,你会在 MBean 树中看到其关联的属性和操作节点。

  9. 展开 MBean 树中Hello MBean 的属性节点。

    显示由Hello类定义的 MBean 属性。

  10. CacheSize属性的值更改为 150。

    在你启动Main的终端窗口中,会生成对属性更改的确认。

  11. 展开 MBean 树中Hello MBean 的操作节点。

    Hello MBean 声明的两个操作,sayHello()add(),是可见的。

  12. 通过点击sayHello按钮调用sayHello()操作。

    一个 JConsole 对话框通知您方法已成功调用。消息“hello, world”会在运行Main的终端窗口中生成。

  13. add()操作提供两个整数相加,然后点击add按钮。

    答案显示在一个 JConsole 对话框中。

  14. 要关闭 JConsole,请选择 Connection -> Exit。

MXBeans

原文:docs.oracle.com/javase/tutorial/jmx/mbeans/mxbeans.html

本节介绍了一种特殊类型的 MBean,称为 MXBeans

MXBean 是一种只引用预定义数据类型的 MBean 类型。通过这种方式,您可以确保您的 MBean 可以被任何客户端使用,包括远程客户端,而无需客户端访问表示您的 MBeans 类型的特定模型类。MXBeans 提供了一种方便的方式将相关值捆绑在一起,而无需客户端特别配置以处理这些捆绑。

与标准 MBeans 一样,MXBean 是通过编写一个名为 SomethingMXBean 的 Java 接口和实现该接口的 Java 类来定义的。但是,与标准 MBeans 不同,MXBeans 不要求 Java 类的名称为 Something。接口中的每个方法都定义了 MXBean 中的属性或操作。注解 @MXBean 也可以用于注解 Java 接口,而不需要接口的名称后跟 MXBean 后缀。

MXBeans 存在于 Java 2 Platform, Standard Edition (J2SE) 5.0 软件中,位于 java.lang.management 包中。然而,用户现在可以定义自己的 MXBeans,除了在 java.lang.management 中定义的标准集之外。

MXBeans 的主要思想是,例如在 MXBean 接口中引用的 java.lang.management.MemoryUsage 这样的类型,在本例中是 java.lang.management.MemoryMXBean,被映射到一组标准类型,即所谓的 Open Types,这些类型在 javax.management.openmbean 包中定义。确切的映射规则出现在 MXBean 规范中。然而,一般原则是简单类型如 int 或 String 保持不变,而复杂类型如 MemoryUsage 被映射为标准类型 CompositeDataSupport

MXBean 示例包括以下文件,这些文件位于 jmx_examples.zip 中:

  • QueueSamplerMXBean 接口

  • 实现 MXBean 接口的 QueueSampler

  • QueueSample 是由 MXBean 接口中的 getQueueSample() 方法返回的 Java 类型

  • Main,设置并运行示例的程序

MXBean 示例使用这些类执行以下操作:

  • 定义了一个管理 Queue<String> 类型资源的简单 MXBean

  • 在 MXBean 中声明一个 getter,getQueueSample,当调用时获取队列的快照并返回一个捆绑以下值的 Java 类 QueueSample

    • 获取快照的时间

    • 队列大小

    • 给定时间的队列头

  • 在 MBean 服务器中注册 MXBean

MXBean 接口

以下代码显示了示例 QueueSamplerMXBean MXBean 接口:

package com.example; 

public interface QueueSamplerMXBean { 
    public QueueSample getQueueSample(); 
    public void clearQueue(); 
} 

请注意,声明 MXBean 接口的方式与声明标准 MBean 接口的方式完全相同。QueueSamplerMXBean 接口声明了一个 getter,getQueueSample 和一个操作,clearQueue

定义 MXBean 操作

MXBean 操作在 QueueSampler 示例类中声明如下:

package com.example; 

import java.util.Date; 
import java.util.Queue; 

public class QueueSampler 
                implements QueueSamplerMXBean { 

    private Queue<String> queue; 

    public QueueSampler (Queue<String> queue) { 
        this.queue = queue; 
    } 

    public QueueSample getQueueSample() { 
        synchronized (queue) { 
            return new QueueSample(new Date(), 
                           queue.size(), queue.peek()); 
        } 
    } 

    public void clearQueue() { 
        synchronized (queue) { 
            queue.clear(); 
        } 
    } 
} 

QueueSampler 定义了由 MXBean 接口声明的 getQueueSample() getter 和 clearQueue() 操作。getQueueSample() 操作返回一个 QueueSample Java 类型的实例,该实例是使用 java.util.Queue 方法 peek()size() 返回的值以及 java.util.Date 的实例创建的。

定义 MXBean 接口返回的 Java 类型

QueueSampler 返回的 QueueSample 实例在 QueueSample 类中定义如下:

package com.example; 

import java.beans.ConstructorProperties; 
import java.util.Date; 

public class QueueSample { 

    private final Date date; 
    private final int size; 
    private final String head; 

    @ConstructorProperties({"date", "size", "head"}) 
    public QueueSample(Date date, int size, 
                        String head) { 
        this.date = date; 
        this.size = size; 
        this.head = head; 
    } 

    public Date getDate() { 
        return date; 
    } 

    public int getSize() { 
        return size; 
    } 

    public String getHead() { 
        return head; 
    } 
}   

QueueSample 类中,MXBean 框架调用 QueueSample 中的所有 getter 将给定实例转换为一个 CompositeData 实例,并使用 @ConstructorProperties 注解从 CompositeData 实例重建一个 QueueSample 实例。

创建并在 MBean 服务器中注册 MXBean

到目前为止,已经定义了以下内容:一个 MXBean 接口和实现它的类,以及返回的 Java 类型。接下来,MXBean 必须在 MBean 服务器中创建并注册。这些操作由相同的 Main 示例 JMX 代理执行,该代理在标准 MBean 示例中使用,但相关代码未在 标准 MBean 课程中显示。


package com.example; 

import java.lang.management.ManagementFactory; 
import java.util.Queue; 
import java.util.concurrent.ArrayBlockingQueue; 
import javax.management.MBeanServer; 
import javax.management.ObjectName; 

public class Main { 

    public static void main(String[] args) throws Exception { 
        MBeanServer mbs = 
            ManagementFactory.getPlatformMBeanServer(); 

        ...  
        ObjectName mxbeanName = new ObjectName("com.example:type=QueueSampler");

        Queue<String> queue = new ArrayBlockingQueue<String>(10);
        queue.add("Request-1");
        queue.add("Request-2");
        queue.add("Request-3");
        QueueSampler mxbean = new QueueSampler(queue);

        mbs.registerMBean(mxbean, mxbeanName);

        System.out.println("Waiting..."); 
        Thread.sleep(Long.MAX_VALUE); 
    } 
} 

Main 类执行以下操作:

  • 获取平台 MBean 服务器。

  • 为 MXBean QueueSampler 创建对象名称。

  • QueueSampler MXBean 创建一个 Queue 实例以进行处理。

  • Queue 实例提供给新创建的 QueueSampler MXBean。

  • 以与标准 MBean 完全相同的方式在 MBean 服务器中注册 MXBean。

运行 MXBean 示例

MXBean 示例使用了您在 标准 MBeans 部分中使用的 jmx_examples.zip 包中的类。此示例需要 Java SE 平台的第 6 版本。要运行 MXBeans 示例,请按照以下步骤操作:

  1. 如果尚未这样做,请将jmx_examples.zip保存到work_dir目录中。

  2. 在终端窗口中使用以下命令解压示例类的捆绑包。

    unzip jmx_examples.zip
    
    
  3. work_dir目录中编译示例 Java 类。

    javac com/example/*.java
    
    
  4. 启动Main应用程序。生成一个确认信息,表示Main正在等待某些事件发生。

    java com.example.Main
    
    
  5. 在同一台机器的不同终端窗口中启动 JConsole。显示新连接对话框,展示可以连接的运行中的 JMX 代理列表。

    jconsole
    
    
  6. 在新连接对话框中,从列表中选择com.example.Main并点击连接。

    显示平台当前活动的摘要。

  7. 点击 MBeans 选项卡。

    此面板显示当前在 MBean 服务器中注册的所有 MBeans。

  8. 在左侧框架中,展开 MBean 树中的com.example节点。

    你会看到示例 MBean QueueSampler是由Main创建和注册的。如果你点击QueueSampler,你会在 MBean 树中看到其关联的属性和操作节点。

  9. 展开属性节点。

    你会看到QueueSample属性出现在右侧窗格中,其值为javax.management.openmbean.CompositeDataSupport

  10. 双击CompositeDataSupport值。

    你会看到QueueSampledateheadsize,因为 MXBean 框架已将QueueSample实例转换为CompositeData。如果你将QueueSampler定义为标准 MBean 而不是 MXBean,JConsole 将找不到QueueSample类,因为它不在其类路径中。如果QueueSampler是标准 MBean,当检索QueueSample属性值时,你会收到ClassNotFoundException消息。JConsole 找到QueueSampler这一事实展示了在通过通用 JMX 客户端(如 JConsole)连接到 JMX 代理时使用 MXBeans 的实用性。

  11. 展开操作节点。

    显示一个按钮来调用clearQueue操作。

  12. 点击clearQueue按钮。

    显示成功调用方法的确认信息。

  13. 再次展开属性节点,并双击CompositeDataSupport值。

    headsize值已重置。

  14. 要关闭 JConsole,选择连接 -> 退出。

教训:通知

原文:docs.oracle.com/javase/tutorial/jmx/notifs/index.html

JMX API 定义了一种机制,使 MBeans 能够生成通知,例如,用于表示状态变化、检测到的事件或问题。

要生成通知,MBean 必须实现接口NotificationEmitter或扩展NotificationBroadcasterSupport。要发送通知,您需要构造类javax.management.Notification或其子类(例如AttributeChangedNotification)的实例,并将实例传递给NotificationBroadcasterSupport.sendNotification

每个通知都有一个来源。来源是生成通知的 MBean 的对象名称。

每个通知都有一个序列号。当顺序很重要且存在通知被错误处理的风险时,可以使用此编号来对来自同一来源的通知进行排序。序列号可以为零,但最好对于来自给定 MBean 的每个通知递增。

Hello MBean 在标准 MBeans 中实际上实现了通知机制。但是,出于简单起见,该课程中省略了此代码。Hello的完整代码如下:

package com.example;

import javax.management.*;

public class Hello
        extends NotificationBroadcasterSupport
        implements HelloMBean {

    public void sayHello() {
        System.out.println("hello, world");
    }

    public int add(int x, int y) {
        return x + y;
    }

    public String getName() {
        return this.name;
    }

    public int getCacheSize() {
        return this.cacheSize;
    }

    public synchronized void setCacheSize(int size) {
        int oldSize = this.cacheSize;
        this.cacheSize = size;

        System.out.println("Cache size now " + this.cacheSize);

        Notification n = new AttributeChangeNotification(this,
                                sequenceNumber++, System.currentTimeMillis(),
                                "CacheSize changed", "CacheSize", "int",
                                oldSize, this.cacheSize);

        sendNotification(n);
    }

    @Override
    public MBeanNotificationInfo[] getNotificationInfo() {
        String[] types = new String[]{
            AttributeChangeNotification.ATTRIBUTE_CHANGE
        };

        String name = AttributeChangeNotification.class.getName();
        String description = "An attribute of this MBean has changed";
        MBeanNotificationInfo info = 
                new MBeanNotificationInfo(types, name, description);
        return new MBeanNotificationInfo[]{info};
    }

    private final String name = "Reginald";
    private int cacheSize = DEFAULT_CACHE_SIZE;
    private static final int DEFAULT_CACHE_SIZE = 200;
    private long sequenceNumber = 1;
}

Hello MBean 实现扩展了NotificationBroadcasterSupport类。NotificationBroadcasterSupport实现了NotificationEmitter接口。

操作和属性的设置方式与标准 MBean 示例中的方式相同,唯一的例外是CacheSize属性的 setter 方法现在定义了一个值oldSize。此值记录了在设置操作之前CacheSize属性的值。

通知是从 JMX 类AttributeChangeNotification的实例n构造的,该类扩展了javax.management.Notification。通知是在setCacheSize()方法的定义中从以下信息构造的。这些信息作为参数传递给AttributeChangeNotification

  • 通知来源的对象名称,即Hello MBean,由this表示

  • 一个序列号,即sequenceNumber,设置为 1,并逐渐增加

  • 时间戳

  • 通知消息的内容

  • 已更改的属性名称,本例中为CacheSize

  • 已更改的属性类型

  • 旧属性值,在本例中为oldSize

  • 新属性值,在本例中为this.cacheSize

然后,通知n被传递给NotificationBroadcasterSupport.sendNotification()方法。

最后,MBeanNotificationInfo实例被定义,以描述 MBean 为给定类型的通知生成的不同通知实例的特征。在本例中,发送的通知类型是AttributeChangeNotification通知。

运行 MBean 通知示例

再次使用 JConsole 与Hello MBean 交互,这次是为了发送和接收通知。此示例需要 Java SE 平台的第 6 版。

  1. 如果尚未这样做,请将jmx_examples.zip保存到您的work_dir目录中。

  2. 在终端窗口中使用以下命令解压示例类的捆绑包。

    unzip jmx_examples.zip
    
    
  3. work_dir目录中编译示例 Java 类。

    javac com/example/*.java
    
    
  4. 启动Main应用程序。

    java com.example.Main
    
    

    生成一个确认,表示Main正在等待某些事件发生。

  5. 在同一台机器的不同终端窗口中启动 JConsole。

    jconsole
    
    

    显示新连接对话框,呈现可以连接的正在运行的 JMX 代理列表。

  6. 在新连接对话框中,从列表中选择com.example.Main并点击连接。

    显示您平台当前活动的摘要。

  7. 点击 MBeans 选项卡。

    此面板显示当前在 MBean 服务器中注册的所有 MBeans。

  8. 在左侧框架中,展开 MBean 树中的com.example节点。

    您会看到由Hello创建和注册的示例 MBeanHello。如果点击Hello,您会看到其在 MBean 树中的通知节点。

  9. 展开 MBean 树中Hello MBean 的通知节点。

    请注意,面板为空白。

  10. 点击订阅按钮。

    当前接收的通知数量(0)显示在通知节点标签中。

  11. 展开 MBean 树中Hello MBean 的属性节点,并将CacheSize属性的值更改为 150。

    在启动Main的终端窗口中,会显示对此属性更改的确认。请注意,显示在通知节点中的接收通知数量已更改为 1。

  12. 再次展开 MBean 树中Hello MBean 的通知节点。

    通知的详细信息将被显示。

  13. 要关闭 JConsole,请选择连接 -> 退出。

课程:远程管理

原文:docs.oracle.com/javase/tutorial/jmx/remote/index.html

JMX API 使您能够通过使用基于 JMX 技术的连接器(JMX 连接器)对资源进行远程管理。 JMX 连接器使 MBean 服务器对远程基于 Java 技术的客户端可访问。 连接器的客户端端口基本上导出与 MBean 服务器相同的接口。

JMX 连接器由连接器客户端和连接器服务器组成。 连接器服务器 附加到 MBean 服务器并监听来自客户端的连接请求。 连接器客户端 负责与连接器服务器建立连接。 连接器客户端通常位于与连接器服务器不同的 Java 虚拟机(Java VM)中,并且通常在不同的计算机上运行。 JMX API 定义了基于远程方法调用(RMI)的标准连接协议。 此协议使您能够从远程位置将 JMX 客户端连接到 MBean 服务器中的 MBean,并执行对 MBean 的操作,就像这些操作是在本地执行一样。

Java SE 平台提供了一种开箱即用的方式,通过使用 JMX API 的标准 RMI 连接器远程监视应用程序。 开箱即用的 RMI 连接器会自动将应用程序暴露给远程管理,而无需您自己创建专用的远程连接器服务器。 开箱即用的远程管理代理通过使用正确的属性启动 Java 应用程序来激活。 与 JMX 技术兼容的监视和管理应用程序然后可以连接到这些应用程序并远程监视它们。

通过 JConsole 将资源暴露给远程管理

原文:docs.oracle.com/javase/tutorial/jmx/remote/jconsole.html

使用 JMX API 将您的 Java 应用程序暴露给远程管理可以非常简单,如果您使用开箱即用的远程管理代理和现有的监控和管理工具,如 JConsole。

要将您的应用程序暴露给远程管理,您需要使用正确的属性启动它。此示例展示了如何将Main JMX 代理暴露给远程管理。


安全注意事项:

为简单起见,此示例中禁用了身份验证和加密安全机制。但是,在实际环境中实现远程管理时,您应该实现这些安全机制。下一步是什么? 提供了指向其他 JMX 技术文档的指针,显示如何激活安全性。


此示例需要 Java SE 平台的 6 版本。要远程监视Main JMX 代理,请按照以下步骤操作:

  1. 如果尚未这样做,请将jmx_examples.zip保存到您的work_dir目录中。

  2. 在终端窗口中使用以下命令解压示例类的捆绑包。

    unzip jmx_examples.zip
    
    
  3. work_dir目录中编译示例 Java 类。

    javac com/example/*.java
    
    
  4. 启动Main应用程序,指定暴露Main进行远程管理的属性。(对于 Windows,请使用插入符(^)而不是反斜杠(\)来将长命令分成多行):

    java -Dcom.sun.management.jmxremote.port=9999 \
         -Dcom.sun.management.jmxremote.authenticate=false \
         -Dcom.sun.management.jmxremote.ssl=false \
         com.example.Main
    
    

    生成一个等待某些事件发生的Main的确认。

  5. 不同的机器上的不同终端窗口中启动 JConsole:

    jconsole
    
    

    显示新连接对话框,显示可以在本地连接的正在运行的 JMX 代理列表。

  6. 选择远程进程,并在远程进程字段中输入以下内容:

    *hostname*:9999
    
    

    在此地址中,hostname是运行Main应用程序的远程机器的名称,9999 是将连接到开箱即用的 JMX 连接器的端口号。

  7. 点击连接。

    显示运行Main的 Java 虚拟机(Java VM)的当前活动摘要。

  8. 点击 MBeans 选项卡。

    此面板显示当前在远程 MBean 服务器中注册的所有 MBean。

  9. 在左侧框架中,在 MBean 树中展开com.example节点。

    您会看到由Main创建和注册的示例 MBean Hello。如果您点击Hello,即使它在不同的机器上运行,您也会看到其关联的属性和操作节点在 MBean 树中。

  10. 要关闭 JConsole,请选择连接 -> 退出。

创建自定义 JMX 客户端

原文:docs.oracle.com/javase/tutorial/jmx/remote/custom.html

本教程中的前几课已经向您展示了如何创建 JMX 技术的 MBeans 和 MXBeans,并将它们注册到 JMX 代理中。然而,所有之前的示例都使用了现有的 JMX 客户端 JConsole。本课将演示如何创建您自己的自定义 JMX 客户端。

一个自定义 JMX 客户端的示例,Client,包含在 jmx_examples.zip 中。这个 JMX 客户端与之前课程中看到的相同的 MBean、MXBean 和 JMX 代理进行交互。由于 Client 类的大小,将在以下部分中逐块进行检查。

导入 JMX 远程 API 类

要能够从 JMX 客户端远程连接到运行的 JMX 代理,您需要使用 javax.management.remote 中的类。

package com.example;
...

import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

public class Client {
...

Client 类将创建 JMXConnector 实例,为此它将需要一个 JMXConnectorFactory 和一个 JMXServiceURL

创建一个通知监听器

JMX 客户端需要一个通知处理程序,以便监听并处理可能由注册在 JMX 代理的 MBean 服务器中的 MBeans 发送的任何通知。JMX 客户端的通知处理程序是 NotificationListener 接口的一个实例,如下所示。

... 

public static class ClientListener implements NotificationListener {

    public void handleNotification(Notification notification,
            Object handback) {
        echo("\nReceived notification:");
        echo("\tClassName: " + notification.getClass().getName());
        echo("\tSource: " + notification.getSource());
        echo("\tType: " + notification.getType());
        echo("\tMessage: " + notification.getMessage());
        if (notification instanceof AttributeChangeNotification) {
            AttributeChangeNotification acn =
                (AttributeChangeNotification) notification;
            echo("\tAttributeName: " + acn.getAttributeName());
            echo("\tAttributeType: " + acn.getAttributeType());
            echo("\tNewValue: " + acn.getNewValue());
            echo("\tOldValue: " + acn.getOldValue());
        }
    }
}    
...       

此通知监听器确定接收到的任何通知的来源,并检索通知中存储的信息。然后根据接收到的通知类型执行不同的操作。在这种情况下,当监听器接收到 AttributeChangeNotification 类型的通知时,它将通过调用 AttributeChangeNotification 方法 getAttributeNamegetAttributeTypegetNewValuegetOldValue 获取已更改的 MBean 属性的名称和类型,以及其旧值和新值。

代码稍后将创建一个新的 ClientListener 实例。


ClientListener listener = new ClientListener();

创建 RMI 连接器客户端

Client 类创建了一个 RMI 连接器客户端,配置为连接到在启动 JMX 代理 Main 时将要启动的 RMI 连接器服务器。这将允许 JMX 客户端与 JMX 代理进行交互,就好像它们在同一台机器上运行一样。

...

public static void main(String[] args) throws Exception {

echo("\nCreate an RMI connector client and " +
    "connect it to the RMI connector server");
JMXServiceURL url = 
    new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:9999/jmxrmi");
JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
...        

正如你所看到的,Client定义了一个名为urlJMXServiceURL,表示连接器客户端期望找到连接器服务器的位置。此 URL 允许连接器客户端从运行在本地主机端口 9999 上的 RMI 注册表中检索 RMI 连接器服务器存根jmxrmi,并连接到 RMI 连接器服务器。

识别了 RMI 注册表后,可以创建连接器客户端。连接器客户端jmxcJMXConnector接口的一个实例,通过JMXConnectorFactoryconnect()方法创建。在调用connect()方法时,传递了参数url和一个空的环境映射。

连接到远程 MBean 服务器

有了 RMI 连接,JMX 客户端必须连接到远程 MBean 服务器,以便可以通过远程 JMX 代理与其中注册的各种 MBeans 进行交互。

...

MBeanServerConnection mbsc = 
    jmxc.getMBeanServerConnection();

...     

通过调用JMXConnector实例jmxcgetMBeanServerConnection()方法,创建了一个名为MBeanServerConnection的实例,命名为 mbsc。

现在,连接器客户端已连接到由 JMX 代理创建的 MBean 服务器,并且可以注册 MBeans 并对其执行操作,连接对双方完全透明。

要开始,客户端定义了一些简单的操作,以发现代理的 MBean 服务器中找到的信息。

...

echo("\nDomains:");
String domains[] = mbsc.getDomains();
Arrays.sort(domains);
for (String domain : domains) {
    echo("\tDomain = " + domain);
}

...

echo("\nMBeanServer default domain = " + mbsc.getDefaultDomain());

echo("\nMBean count = " +  mbsc.getMBeanCount());
echo("\nQuery MBeanServer MBeans:");
Set<ObjectName> names = 
    new TreeSet<ObjectName>(mbsc.queryNames(null, null));
for (ObjectName name : names) {
    echo("\tObjectName = " + name);
}

...

客户端调用MBeanServerConnection的各种方法,以获取不同 MBeans 所在的域,MBean 服务器中注册的 MBeans 数量,以及它发现的每个 MBean 的对象名称。

通过代理执行远程 MBeans 上的操作

客户端通过创建一个 MBean 代理,通过 MBean 服务器连接访问 MBean 服务器中的Hello MBean。这个 MBean 代理是客户端本地的,并模拟了远程 MBean。


...

ObjectName mbeanName = new ObjectName("com.example:type=Hello");
HelloMBean mbeanProxy = JMX.newMBeanProxy(mbsc, mbeanName, 
                                          HelloMBean.class, true);

echo("\nAdd notification listener...");
mbsc.addNotificationListener(mbeanName, listener, null, null);

echo("\nCacheSize = " + mbeanProxy.getCacheSize());

mbeanProxy.setCacheSize(150);

echo("\nWaiting for notification...");
sleep(2000);
echo("\nCacheSize = " + mbeanProxy.getCacheSize());
echo("\nInvoke sayHello() in Hello MBean...");
mbeanProxy.sayHello();

echo("\nInvoke add(2, 3) in Hello MBean...");
echo("\nadd(2, 3) = " + mbeanProxy.add(2, 3));

waitForEnterPressed();

...

MBean 代理允许您通过 Java 接口访问 MBean,使您可以在代理上调用而不必编写冗长的代码来访问远程 MBean。在此处通过在javax.management.JMX类中调用newMBeanProxy()方法创建Hello的 MBean 代理,传递 MBean 的MBeanServerConnection、对象名称、MBean 接口的类名和true,表示代理必须表现为NotificationBroadcaster。JMX 客户端现在可以执行Hello定义的操作,就好像它们是本地注册的 MBean 的操作一样。JMX 客户端还添加了一个通知监听器,并更改了 MBean 的CacheSize属性,使其发送通知。

通过代理对远程 MXBeans 执行操作

您可以像创建 MBean 代理一样创建 MXBean 的代理。

...

ObjectName mxbeanName = new ObjectName ("com.example:type=QueueSampler");
QueueSamplerMXBean mxbeanProxy = JMX.newMXBeanProxy(mbsc, 
    mxbeanName,  QueueSamplerMXBean.class);
QueueSample queue1 = mxbeanProxy.getQueueSample();
echo("\nQueueSample.Date = " + queue1.getDate());
echo("QueueSample.Head = " + queue1.getHead());
echo("QueueSample.Size = " + queue1.getSize());
echo("\nInvoke clearQueue() in QueueSampler MXBean...");
mxbeanProxy.clearQueue();

QueueSample queue2 = mxbeanProxy.getQueueSample();
echo("\nQueueSample.Date = " +  queue2.getDate());
echo("QueueSample.Head = " + queue2.getHead());
echo("QueueSample.Size = " + queue2.getSize());

...

如上所示,要为 MXBean 创建代理,您只需调用JMX.newMXBeanProxy而不是newMBeanProxy。MXBean 代理mxbeanProxy允许客户端调用QueueSample MXBean 的操作,就好像它们是本地注册的 MXBean 的操作一样。

关闭连接

一旦 JMX 客户端获取了所需的所有信息,并在远程 JMX 代理的 MBean 服务器上执行了所有必要的操作,连接必须关闭。


jmxc.close();

通过调用JMXConnector.close方法关闭连接。

运行自定义 JMX 客户端示例

此示例需要 Java SE 平台的 6 版本。要使用自定义 JMX 客户端Client远程监视Main JMX 代理,请按照以下步骤操作:

  1. 如果尚未这样做,请将jmx_examples.zip保存到您的work_dir目录中。

  2. 在终端窗口中使用以下命令解压示例类的捆绑包。

    unzip jmx_examples.zip
    
    
  3. work_dir目录中编译示例 Java 类。

    javac com/example/*.java
    
    
  4. 启动Main应用程序,指定暴露Main以进行远程管理的属性:

    java -Dcom.sun.management.jmxremote.port=9999 \
         -Dcom.sun.management.jmxremote.authenticate=false \
         -Dcom.sun.management.jmxremote.ssl=false \ 
         com.example.Main
    
    

    生成一个确认,表明Main正在等待某些事件发生。

  5. 在另一个终端窗口中启动Client应用程序:

    java com.example.Client
    
    

    显示已获取MBeanServerConnection的确认。

  6. 按 Enter 键。

    显示了由Main启动的 MBean 服务器中注册的所有 MBeans 所在的域。

  7. 再次按 Enter 键。

    显示在 MBean 服务器中注册的 MBean 数量,以及所有这些 MBean 的对象名称。显示的 MBeans 包括在 Java VM 中运行的所有标准平台 MXBeans,以及由Main在 MBean 服务器中注册的Hello MBean 和QueueSampler MXBean。

  8. 再次按 Enter 键。

    Hello MBean 的操作由Client调用,结果如下:

    • Client添加通知监听器,以侦听来自Main的通知。

    • CacheSize属性的值从 200 更改为 150。

    • 在启动Main的终端窗口中,会显示CacheSize属性更改的确认信息。

    • 在启动Client的终端窗口中,显示来自Main的通知,通知ClientCacheSize属性更改。

    • 调用Hello MBean 的sayHello操作。

    • 在启动Main的终端窗口中,显示消息“Hello world”。

    • 调用Hello MBean 的add操作,参数为 2 和 3。结果由Client显示。

  9. 再次按 Enter 键。

    QueueSampler MXBean 的操作由Client调用,结果如下:

    • 显示QueueSampledateheadsize

    • 调用clearQueue操作。

  10. 再次按 Enter 键。

    Client关闭与 MBean 服务器的连接,并显示确认信息。

教程:Java API for XML Processing (JAXP)

原文:docs.oracle.com/javase/tutorial/jaxp/index.html

Java API for XML Processing (JAXP) 教程介绍了 Java API for XML Processing (JAXP) 1.4 技术,通过 JAXP 应用示例。

在阅读本教程之前

要充分利用 Java API for XML Processing (JAXP) 教程中的信息,您应该具备以下技术知识:

  • Java 编程语言及其开发环境。

  • 可扩展标记语言(XML)

  • 文档对象模型(DOM),由万维网联盟(W3C)DOM 工作组定义。

  • 简单 XML API(SAX),由 XML-DEV 邮件列表成员合作开发。

假定具有一些 DOM 和 SAX 的先验知识。本教程详细讨论了仅特定于 JAXP API 的代码。

简要描述了 JAXP 技术,包括其目的和主要特点。

介绍了 JAXP 技术中使用的概念,即简单 XML API(SAX):何时使用 SAX,如何解析 XML 文件,如何实现 SAX 验证,如何运行 SAX 解析器以及如何处理词法事件。提供了进一步信息的链接。

介绍了文档对象模型(DOM)使用的树结构,并展示了如何使用 DOM 函数创建节点、删除节点、更改节点内容以及遍历节点层次结构。

包括如何将文档对象模型写成 XML 文件的信息,以及如何从任意数据文件生成 DOM 以将其转换为 XML。

着重介绍了基于流的 Java 技术、事件驱动、拉取解析的 XML 读写 API。StAX 可以创建快速、相对易于编程且内存占用较小的双向 XML 解析器。

介绍了在 7u40 和 JDK8 中添加的属性。

讨论了 JAXP 实现限制,包括在 7u45 中添加的三个限制。

教程:JAXP 简介

原文:docs.oracle.com/javase/tutorial/jaxp/intro/index.html

Java API for XML Processing (JAXP) 是用于使用 Java 编程语言编写的应用程序处理 XML 数据的工具。JAXP 利用了解析器标准 Simple API for XML Parsing (SAX) 和 Document Object Model (DOM),因此您可以选择将数据解析为事件流或构建对象表示。JAXP 还支持可扩展样式表语言转换 (XSLT) 标准,让您控制数据的呈现方式,并使您能够将数据转换为其他 XML 文档或其他格式,如 HTML。JAXP 还提供命名空间支持,允许您处理可能存在命名冲突的 DTD。最后,从版本 1.4 开始,JAXP 实现了流式 XML (StAX) 标准。

为了灵活性,JAXP 允许您在应用程序中使用任何符合 XML 标准的解析器。它通过所谓的可插拔层实现这一点,让您可以插入 SAX 或 DOM API 的实现。可插拔层还允许您插入 XSL 处理器,让您控制 XML 数据的显示方式。

包概述

原文:docs.oracle.com/javase/tutorial/jaxp/intro/package.html

SAX 和 DOM API 分别由 XML-DEV 组和 W3C 定义。定义这些 API 的库如下:

  • javax.xml.parsers:JAXP API 提供了不同供应商的 SAX 和 DOM 解析器的通用接口。

  • org.w3c.dom:定义了Document类(DOM)以及 DOM 的所有组件的类。

  • org.xml.sax:定义了基本的 SAX API。

  • javax.xml.transform:定义了 XSLT API,让您可以将 XML 转换为其他形式。

  • javax.xml.stream:提供了特定于 StAX 的转换 API。

简单 XML API(SAX)是一种事件驱动、串行访问机制,逐个元素进行处理。该级别的 API 读取和写入 XML 到数据存储库或网络。对于服务器端和高性能应用程序,您需要充分了解这个级别。但对于许多应用程序,了解最基本的内容就足够了。

DOM API 通常是一个更容易使用的 API。它提供了熟悉的对象树结构。您可以使用 DOM API 来操作封装的应用程序对象的层次结构。DOM API 非常适合交互式应用程序,因为整个对象模型都存在于内存中,用户可以访问和操作它。

另一方面,构建 DOM 需要读取整个 XML 结构并将对象树保存在内存中,因此它需要更多的 CPU 和内存资源。因此,SAX API 往往更受服务器端应用程序和不需要数据的内存表示的数据过滤器的青睐。

javax.xml.transform中定义的 XSLT API 允许您将 XML 数据写入文件或将其转换为其他形式。正如本教程的 XSLT 部分所示,您甚至可以与 SAX API 一起使用它来将传统数据转换为 XML。

最后,在javax.xml.stream中定义的 StAX API 提供了基于 Java 技术的流式、事件驱动、拉取解析的 API,用于读取和写入 XML 文档。StAX 提供了比 SAX 更简单的编程模型,比 DOM 更高效的内存管理。

简单的 XML API

原文:docs.oracle.com/javase/tutorial/jaxp/intro/simple.html

SAX 解析 API 的基本概述如图 1-1 所示。要启动该过程,需要使用SAXParserFactory类的一个实例来生成解析器的一个实例。

图 1-1 SAX API

SAX API

解析器包装了一个SAXReader对象。当调用解析器的parse()方法时,阅读器会调用应用程序中实现的几个回调方法之一。这些方法由ContentHandlerErrorHandlerDTDHandlerEntityResolver接口定义。

下面是关键的 SAX API 摘要:

SAXParserFactory

SAXParserFactory对象根据系统属性javax.xml.parsers.SAXParserFactory创建解析器的实例。

SAXParser

SAXParser接口定义了几种parse()方法。通常情况下,你会将 XML 数据源和一个DefaultHandler对象传递给解析器,解析器会处理 XML 并调用处理程序对象中适当的方法。

SAXReader

SAXParser包装了一个SAXReader。通常情况下,你不需要关心这一点,但偶尔你需要使用SAXParsergetXMLReader()来获取它,以便进行配置。SAXReader与你定义的 SAX 事件处理程序进行交互。

DefaultHandler

在图中未显示的是,DefaultHandler实现了ContentHandlerErrorHandlerDTDHandlerEntityResolver接口(带有空方法),因此你只需要覆盖你感兴趣的方法。

ContentHandler

当识别到 XML 标签时,诸如startDocumentendDocumentstartElementendElement等方法会被调用。该接口还定义了characters()processingInstruction()方法,当解析器遇到 XML 元素中的文本或内联处理指令时会被调用。

ErrorHandler

方法error()fatalError()warning()会在响应各种解析错误时被调用。默认的错误处理程序对于致命错误会抛出异常,并忽略其他错误(包括验证错误)。这就是为什么即使使用 DOM,你也需要了解一些关于 SAX 解析器的知识的原因。有时,应用程序可能能够从验证错误中恢复。其他时候,可能需要生成异常。为确保正确处理,你需要向解析器提供自己的错误处理程序。

DTDHandler

定义了通常不会被调用的方法。在处理 DTD 时用于识别和处理未解析实体的声明。

EntityResolver

当解析器必须识别由 URI 标识的数据时,将调用resolveEntity方法。在大多数情况下,URI 只是一个 URL,指定了文档的位置,但在某些情况下,文档可能由 URN 标识-在网络空间中是唯一的公共标识符或名称。公共标识符可以在 URL 之外指定。EntityResolver然后可以使用公共标识符而不是 URL 来查找文档-例如,如果存在本地副本,则可以访问文档。

一个典型的应用程序至少实现了大部分ContentHandler方法。因为接口的默认实现除了致命错误外忽略所有输入,一个健壮的实现可能还想要实现ErrorHandler方法。

SAX 包

SAX 解析器在下表中列出的包中定义。

表 SAX 包

描述
org.xml.sax 定义了 SAX 接口。org.xml是由定义 SAX API 的组确定的包前缀。
org.xml.sax.ext 定义了用于执行更复杂的 SAX 处理的 SAX 扩展-例如,处理文档类型定义(DTD)或查看文件的详细语法。
org.xml.sax.helpers 包含一些辅助类,使得使用 SAX 更加容易-例如,通过定义一个默认处理程序,其中所有接口的方法都是空方法,这样你只需要重写你真正想要实现的方法。
javax.xml.parsers 定义了SAXParserFactory类,该类返回SAXParser。还定义了用于报告错误的异常类。

文档对象模型 API

原文:docs.oracle.com/javase/tutorial/jaxp/intro/dom.html

下图  展示了 DOM API 的运行情况。

图  DOM API

DOM API

您可以使用 javax.xml.parsers.DocumentBuilderFactory 类获取一个 DocumentBuilder 实例,并使用该实例生成符合 DOM 规范的 Document 对象。实际上,您获得的构建器是由系统属性 javax.xml.parsers.DocumentBuilderFactory 决定的,该属性选择用于生成构建器的工厂实现。(可以从命令行覆盖平台的默认值。)

您还可以使用 DocumentBuildernewDocument() 方法创建一个实现 org.w3c.dom.Document 接口的空 Document。或者,您可以使用构建器的解析方法之一从现有 XML 数据创建一个 Document。结果是一个类似上述 图  中显示的 DOM 树。


  • 注意 - 尽管它们被称为对象,但 DOM 树中的条目实际上是相当低级的数据结构。例如,考虑这个结构:<color>blue</color>。颜色标签有一个元素节点,在其下有一个包含数据 blue 的文本节点!这个问题将在本教程的 DOM 课程中详细探讨,但是那些期望得到对象的开发人员通常会惊讶地发现,在元素节点上调用 getNodeValue() 不会返回任何内容。要获得真正面向对象的树,请参阅 www.jdom.org 上的 JDOM API。

DOM 包

文档对象模型实现在以下 表  中列出的包中定义。

表  DOM 包

描述
org.w3c.dom 定义了 W3C 规范中 XML(以及可选的 HTML)文档的 DOM 编程接口。
javax.xml.parsers 定义了 DocumentBuilderFactory 类和 DocumentBuilder 类,后者返回一个实现 W3C Document 接口的对象。用于创建构建器的工厂由 javax.xml.parsers 系统属性确定,可以从命令行设置或在调用 new Instance 方法时覆盖。此包还定义了用于报告错误的 ParserConfigurationException 类。

可扩展样式表语言转换 APIs

原文:docs.oracle.com/javase/tutorial/jaxp/intro/extensible.html

下面的 图 展示了 XSLT APIs 的运行情况。

图 XSLT APIs

XSLT APIs

创建一个 TransformerFactory 对象,并用它来创建一个 Transformer。源对象是转换过程的输入。源对象可以从 SAX 读取器、DOM 或输入流创建。

类似地,结果对象是转换过程的结果。该对象可以是 SAX 事件处理程序、DOM 或输出流。

创建转换器时,可以根据一组转换指令创建它,这样指定的转换就会执行。如果没有任何特定的指令创建它,那么转换器对象只是将源复制到结果。

XSLT 包

XSLT APIs 定义在 表 中所示的包中。

表 XSLT 包

描述
javax.xml.transform 定义了 TransformerFactoryTransformer 类,用于获取能够执行转换的对象。创建转换器对象后,调用其 transform() 方法,提供输入(源)和输出(结果)。
javax.xml.transform.dom 用于从 DOM 创建输入(源)和输出(结果)对象的类。
javax.xml.transform.sax 用于从 SAX 解析器创建输入(源)对象和从 SAX 事件处理程序创建输出(结果)对象的类。
javax.xml.transform.stream 用于从 I/O 流创建输入(源)对象和输出(结果)对象的类。

XML 的流 API

原文:docs.oracle.com/javase/tutorial/jaxp/intro/streaming.html

StAX 是 JAXP 家族中最新的 API,为那些希望进行高性能流过滤、处理和修改的开发人员提供了一种替代方案,特别是对于内存较低和扩展性要求有限的情况。

总结一下,StAX 提供了标准的、双向的拉取解析器接口,用于流式 XML 处理,提供了比 SAX 更简单的编程模型,比 DOM 更高效的内存管理。StAX 使开发人员能够将 XML 流解析和修改为事件,并扩展 XML 信息模型以允许特定于应用程序的添加。有关 StAX 与几种替代 API 的更详细比较,请参见 XML 流 API,以及 将 StAX 与其他 JAXP API 进行比较。

StAX 包

StAX API 定义在 表 1-4 中所示的包中。

表 1-4 StAX 包

描述
javax.xml.stream 定义了 XMLStreamReader 接口,用于迭代 XML 文档的元素。XMLStreamWriter 接口指定了 XML 的写入方式。
javax.xml.transform.stax 提供了专门针对 StAX 的转换 API。

查找 JAXP 示例程序

原文:docs.oracle.com/javase/tutorial/jaxp/intro/sample.html

一组 JAXP 示例程序包含在可从Apache Xerces™ Project获取的Xerces2二进制下载包中。安装 Xerces2 后,示例程序位于目录*INSTALL_DIR*/xerces-*version*/samples/jaxp中。

这些示例程序旨在在Java 平台标准版(Java SE)6 或更高版本上运行。

您接下来该怎么办?

原文:docs.oracle.com/javase/tutorial/jaxp/intro/next.html

到目前为止,您已经有足够的信息可以开始浏览 JAXP 库。您的下一步取决于您想要实现什么。您可能想查看以下任何一课程:

  • 如果数据结构已经确定,并且您正在编写需要快速处理的服务器应用程序或 XML 过滤器,请参阅简单 XML API。

  • 如果您需要从 XML 数据构建对象树,以便在应用程序中对其进行操作,或者将内存中的对象树转换为 XML,请参阅文档对象模型。

  • 如果您需要将 XML 标记转换为其他形式,如果您想要生成 XML 输出,或者(与 SAX API 结合使用)如果您想要将传统数据结构转换为 XML,请参阅可扩展样式表语言转换。

  • 如果您想要基于流的 Java 技术、事件驱动、拉取解析 API 来读取和写入 XML 文档,或者想要创建快速、相对易于编程且具有轻量级内存占用的双向 XML 解析器,那么请参阅 XML 流 API。

课程:Simple API for XML

原文:docs.oracle.com/javase/tutorial/jaxp/sax/index.html

本课程重点介绍了用于访问 XML 文档的事件驱动、串行访问机制——Simple API for XML(SAX)。这种协议经常被需要传输和接收 XML 文档的 servlet 和面向网络的程序使用,因为它是目前处理 XML 文档的最快速、最占用内存最少的机制,除了 XML 流 API(StAX)之外。


注意 - 简而言之,SAX 面向状态独立处理,其中处理元素不依赖于之前的元素。另一方面,StAX 面向状态相关处理。有关更详细的比较,请参见何时使用 SAX。


设置程序以使用 SAX 需要比设置为使用文档对象模型(DOM)需要更多的工作。SAX 是一种事件驱动模型(您提供回调方法,解析器在读取 XML 数据时调用它们),这使得它更难以可视化。最后,您不能像回退串行数据流或重新排列已从该流中读取的字符一样“回退”到文档的早期部分或重新排列它。

出于这些原因,编写一个显示 XML 文档并可能修改它的面向用户的应用程序的开发人员将希望使用文档对象模型中描述的 DOM 机制。

然而,即使您计划专门构建 DOM 应用程序,熟悉 SAX 模型也有几个重要原因:

  • 相同的错误处理:SAX 和 DOM API 生成相同类型的异常,因此错误处理代码几乎相同。

  • 处理验证错误:默认情况下,规范要求忽略验证错误。如果您希望在发生验证错误时抛出异常(您可能会这样做),那么您需要了解 SAX 错误处理的工作原理。

  • 转换现有数据:正如您将在文档对象模型中看到的,有一种机制可以用来将现有数据集转换为 XML。然而,利用该机制需要理解 SAX 模型。

何时使用 SAX

原文:docs.oracle.com/javase/tutorial/jaxp/sax/when.html

当你想将现有数据转换为 XML 时,了解 SAX 事件模型是很有帮助的。转换过程的关键是修改现有应用程序,在读取数据时生成 SAX 事件。

SAX 是快速高效的,但其事件模型使其最适用于状态无关的过滤。例如,SAX 解析器在遇到元素标签时调用应用程序中的一个方法,并在找到文本时调用不同的方法。如果你正在进行的处理是状态无关的(意味着它不依赖于之前出现的元素),那么 SAX 就可以很好地工作。

另一方面,对于状态相关处理,当程序需要在元素 A 下对数据执行一项操作,但在元素 B 下需要执行不同的操作时,那么像 XML 流 API(StAX)这样的拉取解析器可能是更好的选择。使用拉取解析器,你可以在代码中的任何位置请求下一个节点,无论它是什么。因此,你可以轻松地改变处理文本的方式(例如),因为你可以在程序中的多个位置处理它(更多细节,请参见更多信息)。

SAX 需要比 DOM 更少的内存,因为 SAX 不构造 XML 数据的内部表示(树结构),而 DOM 则会。相反,SAX 只是在读取时将数据发送给应用程序;然后你的应用程序可以对看到的数据进行任何操作。

拉取解析器和 SAX API 都像串行 I/O 流一样工作。你可以看到数据随着流入,但不能回到较早的位置或跳到不同的位置。一般来说,这样的解析器在你只想读取数据并让应用程序对其进行操作时效果很好。

但是,当你需要修改 XML 结构 - 特别是当你需要交互式地修改它时 - 使用内存结构更有意义。DOM 就是这样的模型之一。然而,尽管 DOM 为大型文档(如书籍和文章)提供了许多强大的功能,但它也需要大量复杂的编码。该过程的详细信息在下一课程的何时使用 DOM 中进行了强调。

对于更简单的应用程序,这种复杂性可能是不必要的。对于更快速的开发和更简单的应用程序,其中一个面向对象的 XML 编程标准,如 JDOM(www.jdom.org)和 DOM4J(www.dom4j.org/),可能更合适。

使用 SAX 解析 XML 文件

原文:docs.oracle.com/javase/tutorial/jaxp/sax/parsing.html

在实际应用中,您将希望使用 SAX 解析器处理 XML 数据并对其进行有用的操作。本节将介绍一个名为SAXLocalNameCount的 JAXP 程序示例,该程序仅使用元素的localName组件计算元素的数量,而忽略了命名空间名称以简化操作。此示例还展示了如何使用 SAX ErrorHandler

创建骨架

SAXLocalNameCount程序保存在名为SAXLocalNameCount.java的文件中。

public class SAXLocalNameCount {
    static public void main(String[] args) {
        // ...
    }
}

因为您将独立运行它,所以需要一个main()方法。并且您需要命令行参数,以便告诉应用程序要处理哪个文件。在SAXLocalNameCount.java文件中找到示例的完整代码。

导入类

应用程序将使用的类的导入语句如下。

package sax;
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;

import java.util.*;
import java.io.*;

public class SAXLocalNameCount {
    // ...
}

javax.xml.parsers包含SAXParserFactory类,用于创建所使用的解析器实例。如果无法生成与指定选项配置匹配的解析器,则会抛出ParserConfigurationException异常。(稍后,您将看到更多有关配置选项的内容)。javax.xml.parsers包还包含SAXParser类,这是工厂用于解析的返回值。org.xml.sax包定义了 SAX 解析器使用的所有接口。org.xml.sax.helpers包含DefaultHandler,它定义了将处理解析器生成的 SAX 事件的类。java.utiljava.io中的类用于提供哈希表和输出。

设置 I/O

首要任务是处理命令行参数,目前这些参数仅用于获取要处理的文件的名称。main方法中的以下代码告诉应用程序您希望SAXLocalNameCount处理哪个文件。

static public void main(String[] args) throws Exception {
    String filename = null;

    for (int i = 0; i < args.length; i++) {
        filename = args[i];
        if (i != args.length - 1) {
            usage();
        }
    }

    if (filename == null) {
        usage();
    } 
}

这段代码将main方法设置为在遇到问题时抛出Exception,并定义了命令行选项,这些选项是告诉应用程序要处理的 XML 文件的名称。在本课程的后面部分,当我们开始查看验证时,代码中的其他命令行参数将被检查。

当您运行应用程序时提供的filename字符串将通过内部方法convertToFileURL()转换为java.io.File URL。这是在SAXLocalNameCount中的以下代码完成的。

public class SAXLocalNameCount {
    private static String convertToFileURL(String filename) {
        String path = new File(filename).getAbsolutePath();
        if (File.separatorChar != '/') {
            path = path.replace(File.separatorChar, '/');
        }

        if (!path.startsWith("/")) {
            path = "/" + path;
        }
        return "file:" + path;
    }

    // ...
}

如果在运行程序时指定了不正确的命令行参数,则会调用SAXLocalNameCount应用程序的usage()方法,以在屏幕上打印出正确的选项。

private static void usage() {
    System.err.println("Usage: SAXLocalNameCount <file.xml>");
    System.err.println("       -usage or -help = this message");
    System.exit(1);
}

更多usage()选项将在本课程的后面部分进行讨论,当处理验证时。

实现ContentHandler接口

SAXLocalNameCount中最重要的接口是ContentHandler。该接口需要一些方法,SAX 解析器会在不同的解析事件发生时调用这些方法。主要的事件处理方法包括:startDocumentendDocumentstartElementendElement

实现此接口的最简单方法是扩展org.xml.sax.helpers包中定义的DefaultHandler类。该类为所有ContentHandler事件提供了空操作方法。示例程序扩展了该类。

public class SAXLocalNameCount extends DefaultHandler {
    // ...
} 


注意 - DefaultHandler还为DTDHandlerEntityResolverErrorHandler接口中定义的其他主要事件定义了空操作方法。您将在本课程的后续部分了解更多关于这些方法的信息。


接口要求这些方法中的每一个都抛出SAXException。在此抛出的异常会被发送回解析器,解析器会将其发送到调用解析器的代码。

处理内容事件

本节展示了处理ContentHandler事件的代码。

当遇到开始标签或结束标签时,标签的名称作为字符串传递给startElementendElement方法。当遇到开始标签时,它定义的任何属性也会作为Attributes列表传递。元素中找到的字符会作为字符数组传递,同时还会传递字符的数量(长度)和指向第一个字符的数组偏移量。

文档事件

以下代码处理了开始文档和结束文档事件:

public class SAXLocalNameCount extends DefaultHandler {

    private Hashtable tags;

    public void startDocument() throws SAXException {
        tags = new Hashtable();
    }

    public void endDocument() throws SAXException {
        Enumeration e = tags.keys();
        while (e.hasMoreElements()) {
            String tag = (String)e.nextElement();
            int count = ((Integer)tags.get(tag)).intValue();
            System.out.println("Local Name \"" + tag + "\" occurs " 
                               + count + " times");
        }    
    }

    private static String convertToFileURL(String filename) {
        // ...
    }

    // ...
}

此代码定义了解析器遇到正在解析的文档的开始和结束点时应用程序执行的操作。ContentHandler接口的startDocument()方法创建了一个java.util.Hashtable实例,在元素事件中将填充解析器在文档中找到的 XML 元素。当解析器到达文档末尾时,将调用endDocument()方法,以获取哈希表中包含的元素的名称和计数,并在屏幕上打印一条消息,告诉用户找到每个元素的次数。

这两个ContentHandler方法都会抛出SAXException。您将在设置错误处理中了解更多关于 SAX 异常的信息。

元素事件

如文档事件中所述,startDocument方法创建的哈希表需要填充解析器在文档中找到的各种元素。以下代码处理了开始元素事件:

public void startDocument() throws SAXException {
    tags = new Hashtable();
}

public void startElement(String namespaceURI,
                         String localName,
                         String qName, 
                         Attributes atts)
    throws SAXException {

    String key = localName;
    Object value = tags.get(key);

    if (value == null) {
        tags.put(key, new Integer(1));
    } 
    else {
        int count = ((Integer)value).intValue();
        count++;
        tags.put(key, new Integer(count));
    }
}

public void endDocument() throws SAXException {
    // ...
}

此代码处理元素标签,包括在开始标签中定义的任何属性,以获取该元素的命名空间统一资源标识符(URI)、本地名称和限定名称。然后,startElement()方法使用startDocument()创建的哈希映射填充每种类型元素的本地名称及其计数。请注意,当调用startElement()方法时,如果未启用命名空间处理,则元素和属性的本地名称可能为空字符串。代码通过在简单名称为空字符串时使用限定名称来处理该情况。

字符事件

JAXP SAX API 还允许您处理解析器传递给应用程序的字符,使用ContentHandler.characters()方法。


注意 - 字符事件在SAXLocalNameCount示例中没有展示,但为了完整起见,本节中包含了一个简要描述。


解析器不需要一次返回任何特定数量的字符。解析器可以一次返回一个字符,直到几千个字符,仍然是符合标准的实现。因此,如果您的应用程序需要处理它看到的字符,最好让characters()方法在java.lang.StringBuffer中累积字符,并且只在确定找到所有字符时对其进行操作。

当元素结束时,您完成了解析文本,因此通常在那时执行字符处理。但您可能还想在元素开始时处理文本。这对于包含与文本混合的 XML 元素的文档式数据是必要的。例如,考虑这个文档片段:

**<para>**这段文字包含**<bold>**重要**</bold>**的想法。**</para>**

初始文本This paragraph contains<bold>元素的开始处终止。文本important在结束标签</bold>处终止,最终文本ideas.在结束标签</para>处终止。

严格来说,字符处理程序应该扫描&<字符,并用适当的字符串&amp;&lt;替换它们。这将在下一节中解释。

处理特殊字符

在 XML 中,实体是具有名称的 XML 结构(或纯文本)。通过名称引用实体会导致它在文档中插入实体引用的位置。要创建实体引用,您用和分号括起实体名称:

&entityName;

当您处理包含许多特殊字符的大块 XML 或 HTML 时,可以使用 CDATA 部分。CDATA 部分类似于 HTML 中的<code>...</code>,只是更加强大:CDATA 部分中的所有空格都是有意义的,并且其中的字符不会被解释为 XML。CDATA 郅始于<![[CDATA[,结束于]]>

下面显示了一个 CDATA 部分的示例。

<p><termdef id="dt-cdsection" term="CDATA Section">CDATA 部分</term> 可以出现在任何字符数据可能出现的地方;它们用于转义包含否则会被识别为标记的文本块。CDATA 郅始于字符串 "<code>&lt;![CDATA[</code>",结束于字符串 "<code>]]&gt;</code>"

解析后,此文本将显示如下:

CDATA 部分可以出现在任何字符数据可能出现的地方;它们用于转义包含否则会被识别为标记的文本块。CDATA 郅始于字符串 "<![CDATA[",结束于字符串 "]]>"。

CDATA 的存在使得正确回显 XML 有点棘手。如果要输出的文本不在 CDATA 部分中,那么文本中的任何尖括号、和其他特殊字符应该被替换为适当的实体引用。(替换左尖括号和和符号是最重要的,其他字符将被正确解释而不会误导解析器。)但如果输出文本在 CDATA 部分中,那么不应进行替换,导致文本如前面的示例中所示。在像我们的 SAXLocalNameCount 应用程序这样的简单程序中,这并不特别严重。但许多 XML 过滤应用程序将希望跟踪文本是否出现在 CDATA 部分中,以便能够正确处理特殊字符。

设置解析器

以下代码设置解析器并启动它:

static public void main(String[] args) throws Exception {

    // Code to parse command-line arguments 
    //(shown above)
    // ...

    SAXParserFactory spf = SAXParserFactory.newInstance();
    spf.setNamespaceAware(true);
    SAXParser saxParser = spf.newSAXParser();
}

这些代码行创建了一个 SAXParserFactory 实例,由 javax.xml.parsers.SAXParserFactory 系统属性的设置确定。通过将 setNamespaceAware 设置为 true 来设置要创建的工厂以支持 XML 命名空间,然后通过调用其 newSAXParser() 方法从工厂获取一个 SAXParser 实例。


注意 - javax.xml.parsers.SAXParser 类是一个包装器,定义了一些便利方法。它包装了(有点不太友好的)org.xml.sax.Parser 对象。如果需要,可以使用 SAXParser 类的 getParser() 方法获取该解析器。


现在您需要实现所有解析器必须实现的 XMLReaderXMLReader 由应用程序用于告诉 SAX 解析器对所讨论的文档执行什么处理。XMLReadermain 方法中通过以下代码实现。

// ...
SAXParser saxParser = spf.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader();
xmlReader.setContentHandler(new SAXLocalNameCount());
xmlReader.parse(convertToFileURL(filename));

在这里,通过调用SAXParser实例的getXMLReader()方法获取用于解析器的XMLReader实例。然后,XMLReaderSAXLocalNameCount类注册为其内容处理程序,以便解析器执行的操作是处理内容事件中显示的startDocument()startElement()endDocument()方法。最后,XMLReader通过将 XML 文件的位置传递给解析器告诉解析器要解析哪个文档,传递的形式是由设置 I/O 中定义的convertToFileURL()方法生成的File URL。

设置错误处理

现在你可以开始使用你的解析器了,但最好实现一些错误处理。解析器可以生成三种错误:致命错误、错误和警告。当发生致命错误时,解析器无法继续。因此,如果应用程序没有生成异常,那么默认的错误事件处理程序会生成一个异常。但对于非致命错误和警告,默认错误处理程序永远不会生成异常,也不会显示任何消息。

如文档事件中所示,应用程序的事件处理方法会抛出SAXException。例如,ContentHandler接口中startDocument()方法的签名被定义为返回一个SAXException

public void startDocument() throws SAXException { /* ... */ }

可以使用消息、另一个异常或两者构造SAXException

因为默认解析器仅为致命错误生成异常,并且默认解析器提供的有关错误的信息有些有限,SAXLocalNameCount程序通过MyErrorHandler类定义了自己的错误处理。

xmlReader.setErrorHandler(new MyErrorHandler(System.err));

// ...

private static class MyErrorHandler implements ErrorHandler {
    private PrintStream out;

    MyErrorHandler(PrintStream out) {
        this.out = out;
    }

    private String getParseExceptionInfo(SAXParseException spe) {
        String systemId = spe.getSystemId();

        if (systemId == null) {
            systemId = "null";
        }

        String info = "URI=" + systemId + " Line=" 
            + spe.getLineNumber() + ": " + spe.getMessage();

        return info;
    }

    public void warning(SAXParseException spe) throws SAXException {
        out.println("Warning: " + getParseExceptionInfo(spe));
    }

    public void error(SAXParseException spe) throws SAXException {
        String message = "Error: " + getParseExceptionInfo(spe);
        throw new SAXException(message);
    }

    public void fatalError(SAXParseException spe) throws SAXException {
        String message = "Fatal Error: " + getParseExceptionInfo(spe);
        throw new SAXException(message);
    }
}

与设置解析器中展示XMLReader指向正确内容处理程序的方式相同,在这里通过调用其setErrorHandler()方法将XMLReader指向新的错误处理程序。

MyErrorHandler类实现了标准的org.xml.sax.ErrorHandler接口,并定义了一个方法来获取解析器生成的任何SAXParseException实例提供的异常信息。这个方法,getParseExceptionInfo(),简单地通过调用标准的SAXParseException方法getLineNumber()getSystemId()获取错误发生在 XML 文档中的行号和运行它的系统的标识符。然后,这个异常信息被馈送到基本 SAX 错误处理方法error()warning()fatalError()的实现中,这些方法被更新以发送关于文档中错误的性质和位置的适当消息。

处理非致命错误

当 XML 文档未通过有效性约束时会发生非致命错误。如果解析器发现文档无效,则会生成一个错误事件。这样的错误是由验证解析器生成的,给定文档类型定义(DTD)或模式,当文档具有无效标记时,当找到不允许的标记时,或者(在模式的情况下)当元素包含无效数据时。

关于非致命错误最重要的原则是,默认情况下它们会被忽略。但如果文档中发生验证错误,你可能不希望继续处理它。你可能希望将这类错误视为致命错误。

要接管错误处理,你需要重写处理致命错误、非致命错误和警告的DefaultHandler方法,作为ErrorHandler接口的一部分。正如在前一节的代码片段中所示,SAX 解析器将SAXParseException传递给这些方法中的每一个,因此在发生错误时生成异常就像简单地将其抛回一样。


注意 - 检查org.xml.sax.helpers.DefaultHandler中定义的错误处理方法可能会很有启发性。你会发现error()warning()方法什么都不做,而fatalError()会抛出异常。当然,你总是可以重写fatalError()方法以抛出不同的异常。但如果你的代码在发生致命错误时不抛出异常,那么 SAX 解析器会抛出异常。XML 规范要求如此。


处理警告

警告也会被默认忽略。警告是信息性的,只有在存在 DTD 或模式的情况下才能生成。例如,如果在 DTD 中两次定义了一个元素,则会生成警告。这并不是非法的,也不会引起问题,但你可能想知道,因为这可能不是有意的。将 XML 文档与 DTD 进行验证将在下一节中展示。

在没有验证的情况下运行 SAX 解析器示例

以下步骤解释了如何在没有验证的情况下运行 SAX 解析器示例。

在没有验证的情况下运行SAXLocalNameCount示例

  1. SAXLocalNameCount.java文件保存在名为sax的目录中。

  2. 编译文件如下:

    javac sax/SAXLocalNameCount.java
    
    
  3. 将示例 XML 文件rich_iii.xmltwo_gent.xml保存在data目录中。

  4. 在 XML 文件上运行SAXLocalNameCount程序。

    选择data目录中的一个 XML 文件,并在其上运行SAXLocalNameCount程序。在这里,我们选择在文件rich_iii.xml上运行该程序。

    java sax/SAXLocalNameCount data/rich_iii.xml
    
    

    XML 文件rich_iii.xml包含了威廉·莎士比亚的剧作理查三世的 XML 版本。当你在其上运行SAXLocalNameCount时,你应该会看到以下输出。

    Local Name "STAGEDIR" occurs 230 times
    Local Name "PERSONA" occurs 39 times
    Local Name "SPEECH" occurs 1089 times
    Local Name "SCENE" occurs 25 times
    Local Name "ACT" occurs 5 times
    Local Name "PGROUP" occurs 4 times
    Local Name "PLAY" occurs 1 times
    Local Name "PLAYSUBT" occurs 1 times
    Local Name "FM" occurs 1 times
    Local Name "SPEAKER" occurs 1091 times
    Local Name "TITLE" occurs 32 times
    Local Name "GRPDESCR" occurs 4 times
    Local Name "P" occurs 4 times
    Local Name "SCNDESCR" occurs 1 times
    Local Name "PERSONAE" occurs 1 times
    Local Name "LINE" occurs 3696 times
    
    

    SAXLocalNameCount程序解析 XML 文件,并提供每种类型的 XML 标记实例数量的计数。

  5. 在文本编辑器中打开文件data/rich_iii.xml

    为了检查错误处理是否有效,请从 XML 文件中删除一个条目的闭合标签,例如第 21 行显示的闭合标签</PERSONA>

    21 <PERSONA>爱德华,威尔士亲王,后来的国王爱德华五世。</PERSONA>

  6. 运行SAXLocalNameCount

    这次,你应该看到以下致命错误消息。

    Exception in thread "main" org.xml.sax.SAXException: Fatal Error: URI=file:data/rich_iii.xml Line=21: The element type "PERSONA" must be terminated by the matching end-tag "</PERSONA>".
    
    

    正如你所看到的,当遇到错误时,解析器生成了一个SAXParseException,这是SAXException的一个子类,用于标识错误发生的文件和位置。

标签:XML,JMX,Java,2022,示例,三十七,API,MBean
From: https://www.cnblogs.com/apachecn/p/18131295

相关文章

  • Java 中文官方教程 2022 版(二十)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html解决常见组件问题原文:docs.oracle.com/javase/tutorial/uiswing/components/problems.html本节讨论您在使用组件时可能遇到的问题。如果在本节中找不到您的问题,请参考以下章节:解决使用其他Swing功能的常见问......
  • Java命名
    命名类的命名(名词组合)把关注点放在最前面ProvinceInquiryController优点:这种命名方式将"Province"放在前面,突出了控制器的主题是有关省份的查询。例子:ProvinceInquiryController更直接地指出了控制器的作用是进行省份的查询,比如处理"/province/inquiry"这样的请......
  • Java 中文官方教程 2022 版(二十一)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html如何使用焦点子系统原文:docs.oracle.com/javase/tutorial/uiswing/misc/focus.html许多组件-即使是主要通过鼠标操作的组件,如按钮-也可以通过键盘操作。要使按键影响组件,组件必须具有键盘焦点。从用户的角度......
  • Java 中文官方教程 2022 版(二十二)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html使用布局管理器原文:docs.oracle.com/javase/tutorial/uiswing/layout/using.html布局管理器是实现LayoutManager接口*的对象,确定容器内组件的大小和位置。尽管组件可以提供大小和对齐提示,但容器的布局管理器最终决......
  • Java 中文官方教程 2022 版(二十三)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html创建自定义布局管理器原文:docs.oracle.com/javase/tutorial/uiswing/layout/custom.html在开始创建自定义布局管理器之前,请确保没有现有的布局管理器符合您的要求。特别是像GridBagLayout、SpringLayout和BoxLayou......
  • Java 中文官方教程 2022 版(十六)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html如何使用编辑窗格和文本窗格原文:docs.oracle.com/javase/tutorial/uiswing/components/editorpane.html两个Swing类支持样式文本:JEditorPane及其子类JTextPane。JEditorPane类是Swing样式文本组件的基础,并提供......
  • Java 中文官方教程 2022 版(十七)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html如何使用菜单原文:docs.oracle.com/javase/tutorial/uiswing/components/menu.html菜单提供了一种节省空间的方式,让用户从几个选项中选择一个。用户可以使用其他组件进行一对多选择,包括组合框、列表、单选按钮、微......
  • Java 中文官方教程 2022 版(十八)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html如何使用旋转器原文:docs.oracle.com/javase/tutorial/uiswing/components/spinner.html旋转器类似于组合框和列表,允许用户从一系列值中选择。与可编辑的组合框类似,旋转器允许用户输入值。与组合框不同,旋转器没有可......
  • Java 中文官方教程 2022 版(十三)
    原文:docs.oracle.com/javase/tutorial/reallybigindex.html部署JavaWebStart应用程序原文:docs.oracle.com/javase/tutorial/deployment/deploymentInDepth/createWebStartLaunchButtonFunction.html您可以使用部署工具包脚本的createWebStartLaunchButton函数部署Java......
  • 2023蓝桥杯 java A组 小蓝的旅行计划
    最小堆(优先队列)和区间树(线段树,红黑树)的结合java中有自己实现这两种数据结构的对象(1)最小堆(优先队列)PriorityQueue<int[]>minHeap=newPriorityQueue<>(newComparator<int[]>(){//int[]三个元素第一个cost第二个lim第三个tag @Override publicintcompare(int......