`
maimode
  • 浏览: 416221 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

为 Web 应用建立基于 JMX 的管理系统

阅读更多

 

以下转自:http://www.ibm.com/developerworks/cn/java/l-jmx2/index.html

Web 应用系统总算开发了,接下来该如何让客户(Web 应用系统的管理员)轻松管理我的一堆配置文件,或者如何实现动态修改系统运行属性,同时又让客户不需要过多的了解配置文件的内容就能够实现这些管理呢?这是许多刚刚结束 Web 应用系统前期开发的系统分析人员需要面临的问题。又或者说我想对早已完成 Web 应用系统进行有效的资源管理,希望再添加管理功能的同时,对原有的代码不需要做过多的修改,换句话说就是管理系统与被管理的应用系统做到很好的隔离。JMX 的管理框架(图 1)为你很好的解决了这些问题。


 

JMX(Java Management Extensions) 是来管理网络,设备,应用程序等资源,它描述了一个可扩展的管理体系结构,并且提供了 JMX API 和一些预定义的 java 管理服务。在撰写本文时,JMX 规范最新版本为 v1.2(http://jcp.org/aboutJava/communityprocess/final/jsr003/index3.html) ,JMX 参考实现的最新版本为 v1.2.1(http://java.sun.com/products/JavaManagement/)。JMX 推出后,一些大型的项目就立即采用了基于 JMX 的实现框架,例如 Jakarta tomcat 和 JBoss,这充分说明 JMX 的可行性和良好的特性。

对于 Web 应用的管理往往是比较麻烦的,例如客户手动的修改配置文件,开启数据库监控程序等等,如果要动态修改数据库访问方案或者监控用户数,动态修改日志级别会更加麻烦,并且可能把系统的结构弄得凌乱,造成结构不良的恶果,更别说可扩展性了。JMX 的分层结构以及高度的组件化,通过将各种资源封装成 MBean 的方式,让我们可以很低成本的实现对现有 Web 应用的扩展性很强的管理方案。

本文以 Tomcat 作为 Web 服务器为例,详细的介绍如何使用 JMX 建立对 Web 应用的管理。对于 JMX 的概念性的东西、体系结构以及使用规范已经有不少的相关文档,为了能够更好的理解本文,在阅读本文时请先参阅这些文档,本文的笔墨将着重在应用和实现上。下图(图 2)为 JMX 的基本框架图(见 JMX 规范),目的是给大家理解本文提供方便。


 

创建 Web 应用的管理系统

对 Web 应用构建一个基于 JMX 的管理系统,我们需要做的事情有哪些呢?

1. 针对每一个需要管理的资源创建一个 MBean 的实例,这是 JMX 框架所要求的 , 有两种类型的实例可供选择,一种是直接管理资源的 MBean,一种通过调用资源实例进行管理的 MBean。

2. 编写一个 MBean 描述文件,并描述每一个 MBean,选择基于 XML 的 MBean 描述文件是一个不错的决定。

3. 通过读 MBean 描述文件,生成 MBeanInfo,从而生成一个个 MBean。

4. 将需要进行管理的 MBean 注册到 MBean Server 当中。

5. 编写客户端代码,选择 Web 的方式进行客户端的编码比较 Web 应用的风格,也比较容易实现。

那么一个基于 JMX 的 Web 应用的管理框架已经成形,图 3 是它的基本结构图,虚线部分为基于 JMX 的管理系统。接下来我们按照步骤实现整个管理系统。


 

获得 MBeanServer 的实例

有两种方案获得 MBeanServer 的实例,

1. 通过获得 Web 服务器的 MBeanServer 的实例,这样做的好处是通过该 MBeanServer 对本身,甚至可以实现对 Web 服务器的自身的一些管理。Tomcat 的管理框架也是建立 JMX 的基础上,它使用的 JMX 的实现是 MX4J,这是一个非常优秀的 JMX 开源项目,在 tomcat4.1.27 中,MBeanServer 的实例存放与属性名为"org.apache.catalina.MBeanServer"的 application 变量 (Web 应用中变量的几种范围:page,request,session,application) 中,因此 servlet 中获得 MBeanServer 实例的办法:

server = (MBeanServer)getServletContext().getAttribute("org.apache.catalina.MBeanServer");

如果通过这种方式,你获得的 server 为 null,这说明你必须还要完成下面的工作,使你能够有权限获得系统的 MBeanServer,tomcat 才会将 MBeanServer 的实例存放在 web 应用程序下属性名"org.apache.catalina.MBeanServer"的系统变量中。

找到 tomcat 下 conf 目录,修改 server.xml 文件。修改 Web 应用的 context 元素,添加上 privileged="true"这一项属性即可,例如:

        	       	 <Context path="/myapp" docBase="c:/web/" 
        	 debug="9" privileged="true" reloadable="true" crossContext="true"/> 
        	


2. 通过 JMX API 中 MBeanServerFactory 类的 createMbeanServer() 的方法创建 MBeanServer 的实例,这样做得好处的使 JMX 的实现与 Web 服务器无关,使代码的移植性更强。在创建完 MBeanServer 以后,为了让能够在管理系统中很方便的获得该 MBeanServer 的引用,可将其置入 application 变量中 ( 推荐 ),或者使用 singleton 设计模式的方法创建和获得。

使用 JMX API 创建 MBean Server 的代码如下:

                MBeanServer server = MBeanServerFactory.createMBeanServer(); 
       


究竟采取何种方案获得 MBeanServer 并不十分重要,可以考虑实现的方便进行选择。

创建 MBean

为了能够管理 Web 应用的资源,首先要使资源能够被管理,按照 JMX 规范的要求,我们将资源封装成 MBean,实际上也就是为 Web 应用添加可管理性。

获得 MBeanServer 的实例以后,就可以编写自己的 MBean 了,根据 JMX 的规范,MBean 有标准的和动态的两种主要类型,这里就不赘述了,具体可以参看 JMX 的规范(http://)。有两种用于特殊用途的动态 MBeans:模型 MBean 和开放 MBean。模型 MBean (Modle MBean) 提供了"现成的"MBean 实现,您可以使用它来快速地利用任何 JMX 可管理资源。它是预制的、通用的和动态的 MBean 类,并且提供了参考实现,已经包含了所有必要缺省行为的实现 - 允许您在运行时添加或覆盖需要定制的那些实现。这使得基于 Java 的、非工具化的资源能够在运行时提供保证兼容的 MBean 虚包,使它们能够通过 JMX 体系结构进行管理。 在 Web 应用中,资源是多元化,有运行实例,静态文件,甚至是设备或者是硬件状态,那么我们把这些资源可以分为两类,一些资源需要进行动态的管理监控 ( 如登陆用户数,数据库连接监控 , 即时日志等 ),一些则是静态资源,只需要进行静态的管理 ( 系统配置文件,日志级别等 ),要管理这些不同类型的资源,这就要求 MBean 一方面能够直接调用运行实例提供的接口进,另一方面又希望 MBean 自身能够提供静态资源的管理,模型 MBean 能够很好的满足这样的要求,并且对于新增需要管理的资源,提供了很好的灵活性和可扩展性,因此推荐模型 MBean 作为 Web 应用首选。当然具体采用何种类型的 MBean,需要结合不同的应用,或者多种类型的 MBean 相结合使用。顺便提一点,采用不同类型的 MBean,对于管理客户端是透明的,也就是说客户端使用一致的访问方法来访问 MBean 的属性和方法,这也是 JMX 的一个优点。

为了更好的观察如何实现对静态资源和动态资源的管理,本文编写了两个 MBean,分别实现对数据库连接属性实现静态和动态的管理。静态管理的 MBean 的目的是实现对数据库配置文件修改,这样当 Web 应用重启后,会使用新的数据库配置,我们采用 MBean 的提供对资源的直接操作进行实现。动态管理的 MBean 目的是对已经实例化的数据库连接对象的属性进行修改,这样在不重新启动 Web 应用的情况下,改变数据库连接。

1. 实现管理配置文件的 MBean。

JDBCConfig 是一个 Model MBean,它的作用是对 config.properties 文件封装成 MBean,该 MBean 包括四个属性及其相关的 getter 和 setter 和一个 save 方法,其中 save 方法的作用是属性存入配置文件,从而实现了通过 MBean 直接对配置文件进行操作。

这个例子中的 MBean 继承了 BaseModelMBean 类,该类实现了 javax.management.ModelMBean 接口,我们可以在 commons-modeler.jar 中找到 BaseModelMBean 类,commons-modeler.jar 是 tomcat4.x 中使用的一个开放源码的包。

 import javax.management.MBeanException; 
 import javax.management.RuntimeOperationsException; 
 import org.apache.commons.modeler.BaseModelMBean; 
 import java.io.*; 
 import java.util.Properties; 
 public class JDBCConfigMBean extends BaseModelMBean { 
    private String driver; 
    private String username; 
    private String password; 
    private String dburl; 
    private static final String CONFIG_FILEPATH = "c:\\myweb\\conf\\config.properties"; 
    /////////////////////////////////////////////////////////////////// 
    //constructor 
    /////////////////////////////////////////////////////////////////// 
    public JDBCConfig() 
            throws MBeanException, RuntimeOperationsException { 
        super(); 
        init(); 
    } 
    private void init() { 
        InputStream in = null; 
        try { 
            Properties prop = new Properties(); 
            in = new BufferedInputStream(new FileInputStream(CONFIG_FILEPATH)); 
            prop.load(in); 
            driver = prop.getProperty("driver"); 
            dburl = prop.getProperty("dburl"); 
            username = prop.getProperty("username"); 
            password = prop.getProperty("password"); 
            in.close(); 
        } catch (Exception e) { 
            try { 
                if (in != null) 
                    in.close(); 
            } catch (IOException e1) { 
                e1.printStackTrace(); 
            } 
            e.printStackTrace(); 
        } 
 } 
    /////////////////////////////////////////////////////////////////// 
    //getter and setter 
 /////////////////////////////////////////////////////////////////// 
 ...... 
    /////////////////////////////////////////////////////////////////// 
    //public method 
    /////////////////////////////////////////////////////////////////// 
    public String save() { 
        OutputStream out = null; 
        try { 
            out = new BufferedOutputStream(new FileOutputStream(CONFIG_FILEPATH)); 
            Properties prop = new Properties(); 
            prop.setProperty("driver", driver); 
            prop.setProperty("dburl", dburl); 
            prop.setProperty("username", username); 
            prop.setProperty("password", password); 
            prop.store(out, "---jdbc config---"); 
            out.close(); 
            return "save success!"; 
        } catch (Exception e) { 
            try { 
                if (out != null) 
                    out.close(); 
            } catch (IOException e1) { 
                e1.printStackTrace(); 
            } 
            e.printStackTrace(); 
            return "save error!"; 
        } 
    } 
 } 


2. 实现修改运行实例的属性。

上面的例子是对静态资源的修改,如果需要对正在运行的类实例进行动态修改,Model MBean 同样提供了很好的方法,在 javax.management.ModelMBean 的 API 中有个 setManagedResourced 方法,这个方法的作用是将正在运行的类实例置入 Model MBean,实际上所要做的也就这么多。当需要对改变实例属性的时候,只需要调用 MBean 的 setAttribute 的方法即可,MBean 将会通过反射调用该实例对应的属性设置方法,就是这么简单。但是许多人喜欢直接从代码了解过程,因此我将如何实现动态修改实例的属性代码也写出来。同样以 JDBC 为例,这里假设系统已经有了一个使用 JDBC 的数据库连接,现在需要改变 JDBC 的属性,并建立新的连接,这一切都在不重启 Web 应用系统的情况下完成。

第一段代码是 Web 应用使用的数据库连接类,它提供一个获得数据库连接,和测试连接属性的两个方法,以及相关的属性。当 Web 应用启动时,该类将会实例化,我们暂且把这个实例叫资源实例。

 package com.myApp.db; 
 import java.sql.Connection; 
 import java.sql.SQLException; 
 public class DBAccess { 
    private String driver; 
    private String username; 
    private String password; 
    private String dburl; 
    public Connection getConnection() 
    { 
	 ......       
    } 
    public static boolean testConnection(String driver,String username,
		String password,String dburl) 
    { 
    } 
    ///////////////////////////////////////////////////////////////////
    //getter and setter 
    ///////////////////////////////////////////////////////////////////
    ...... 
 }


第二段代码是一个 Model MBean 类,和前面提到的第一个 Model MBean 一样,继承了 BaseModelMBean,不同的是它将拥有 Web 应用运行时期资源实例的引用,因为所有的属性和方法都在所引用的资源实例中,父类 BaseModelMBean 完成了通过反射调用该资源实例对应的属性设置方法,所以这个类的编写相当简单,我们可以在里面完成其他一些相关的方法。

 package com.myApp.db; 
 import org.apache.commons.modeler.BaseModelMBean; 
 import javax.management.MBeanException; 
 import javax.management.RuntimeOperationsException; 
 public class ResInstanceMBean extends BaseModelMBean { 
    public ResInstanceMBean () 
            throws MBeanException, RuntimeOperationsException { 
        super(); 
    } 
    /** other operations **/ 
    ..... 
 } 


第三段代码是如何实现管理数据库连接类的运行实例

 ...... 
 /**Web 应用运行初期创建的数据库连接 **/ 
 DBAccess dbAccess = new DBAccess(); 
 dbAccess.setDriver(); 
 dbAccess.setPassword(); 
 dbAccess.setUsername(); 
 dbAccess.setDburl(); 
 ...... 
 /** 创建并注册管理数据库的 MBean 
 // 创建 mbean 
 ResInstanceMBean mbean= new ResInstanceMBean(); 
 // 设置 MBeanInfo,这是必须的
 mbean.setModelMBeanInfo(createMBeanInfo()); 
 // 设置 MBean 所管理的资源实例,instance 为数据库连接的实例,
 //"objectReference"是必须的,否则将无法将资源实例设置到 MBean 中,记住就行了
 DBAccess instance = getDBAccess(); 
 mbean.setManagedResource(instance, "objectReference"); 
 // 注册 mbean 到 MBean Server 中
 MBeanServer serv = getMBeanServer(); 
 ObjectName oname = createObjectName(mbean); 
 serv.registerMBean(mbean, oname); 
 ....    


创建 MBean 描述文件

在上面第三段代码中,我们可以看到,要将 MBean 注册到 MBean Server 中必须先创建 MBeanInfo,MBean 的 setModelMBeanInfo() 用来将 MBeanInfo 设置到 MBean 中。为了能够灵活的获得 MBean 的信息,从而将 MBean 注册到 MBeanServer,在 O'Reilly 出版的"java enterprise 的最佳实践"里提到,采用 XML 文件对 MBean 描述是一种非常不错的选择方案,并且提供了一个 XML 描述范例,因此本文也推荐在管理 Web 应用也采用使用 MBean 描述文件的方法。实际上无论 tomcat4.X, 还是 JBOSS,都采用使用 MBean 描述文件的方式创建 MBean,下面提供了一个 Tomcat4.x 里面的 MBean 描述文件方案,并用该方案描述了上述提到的两个数据库连接管理的 MBean。Tomcat 提供了读取该描述文件的办法,具体可以参看 Tomcat 提供的帮助文档 -- 如何使用 MBean descriptor ( " http://jakarta.apache.org/tomcat/tomcat-4.1-doc/mbeans-descriptor-howto.html")。

 <mbean-list> 
 <mbean         name="JDBCConfigMBean"
            className="com.myApp.jmx.JDBCConfigMBean"
          description="the object to access database"
               domain="myapp"> 
    <attribute   name="driver"
          description="Jdbc driver name"
                 type="java.lang.String"
                 writeable="false"/> 
    <attribute   name="dburl"
          description="database url"
                 type="java.lang.String"/> 
    <attribute   name="username"
          description="Database user name"
                 type="java.lang.String"/> 
    <attribute   name="password"
          description="vthe user name's password"
                 type="java.lang.String"/> 
    <operation   name="save"
          description="save the configuration"
               impact="ACTION"
           returnType="java.lang.String"> 
    </operation> 
  </mbean> 
 <mbean         name="DBAccess"
            className="com.myApp.jmx.ResInstanceMBean"
          description="the object to access database"
               domain="myapp"
                 type="com.myApp.db.DBAccess"> 
    <attribute   name="driver"
          description="Jdbc driver name"
                 type="java.lang.String"
                 writeable="false"/> 
    <attribute   name="dburl"
          description="database url"
                 type="java.lang.String"/> 
    <attribute   name="username"
          description="Database user name"
                 type="java.lang.String"/> 
    <attribute   name="password"
          description="vthe user name's password"
                 type="java.lang.String"/> 
    <operation   name="testConnection"
          description="test configure attribute"
               impact="ACTION"
           returnType="java.lang.String"> 
        <parameter name="driver"
          description="Jdbc driver name for test"
                 type="java.lang.String"/> 
        <parameter name="username"
          description="Database user name for test"
                 type="java.lang.String"/> 
        <parameter name="password"
          description="the user name's password for test"
                 type="java.lang.String"/> 
        <parameter name="dburl"
          description="database url for test"
                 type="java.lang.String"/>     
    </operation> 
  </mbean> 
 </mbean-list> 


注册 MBean

在对 MBean 注册前,必须得到 MBean 的描述信息,并且保存在 MBeanInfo 的实例中,否则是无法将 MBean 注册到 MBean Server 当中的,通过 MBean 描述文件,获得各种类型 MBean 的描述信息是一件非常简单的事情,而这些正是创建 MBean 所需要的,这样做的优点在于不需要通过编写代码,只需要修改描述文件,就可以添加新的 MBean,注册的代码实际上我们之前的代码已经列出。在 MBean 注册时必须指定对应的 ObjectName,ObjectName 相当于 MBean 在 MBean Server 中的唯一名字,它的格式为:"domain:key1=value1,key2=value2...",可根据系统的要求定义一套命名的规则。

 // 注册 mbean 到 MBean Server 中
 MBeanServer serv = getMBeanServer(); 
 ObjectName oname = createObjectName(mbean); 
 serv.registerMBean(mbean, oname); 


编写管理框架的客户端

我们已经完成了服务器端 MBean 的注册工作,接下来是如何让用户能够使用这些 MBean 管理资源。虽然 JMX 的参考实现中提供了 HTMLAdapter,使用户能够通过浏览器使用 MBean。但是提供的界面并不是那么友好可亲,一向苛刻的客户对这绝对不会满意的。因此,编写一些简洁的访问 MBean 页面还是有必要的。如何通过 java 访问 MBean,可以参阅 JMX 的资料,这些资料非常多。

根据上面的介绍,如果要增加对 Web 应用的管理功能或管理系统,基于 JMX 的管理框架绝对是一个非常明智的选择。

在下才疏学浅,难免错漏之处,还望有识之士,不吝赐教在下,在下感激于心。


关于作者

盛戈歆,广州拓微科技有限公司上海分公司高级程序员,对Java的各种技术非常痴迷,参与多个大型的Java项目,并设计实现其的管理系统。Email: shenggexin@topwaver.com。联系电话:021-64366810-170

分享到:
评论

相关推荐

    基于SOA应用JMX和JMS技术的RFID中间件设计

    JMX是一种框架,用于在各种应用程序、设备和系统中嵌入管理功能。它定义了管理组件,即遵循特定设计模式并实现规定接口的Java对象,以标准方式表示和管理资源。管理接口包括属性、操作和通知,使得管理资源的信息可...

    基于Web的网络管理.docx

    为了推动基于Web的网络管理的标准化,业界提出了两种主要的标准:基于Web的企业管理标准(WBEM)和Java管理应用程序编程接口(JMX)。WBEM由微软发起,旨在建立一套跨公司的互操作性框架,提供了一种统一的对象模型...

    基于Java的学生课程管理系统.zip

    总的来说,《基于Java的学生课程管理系统》是一个综合运用了Java编程、数据库管理、Web开发、权限控制等多种技术的项目,它通过模块化的结构和严谨的设计,实现了高效、安全的学生课程管理。这样的系统对于提升学校...

    windchill 系统管理员

    - **Windchill 客户端和 Web 应用程序**:客户端通常包括桌面客户端和基于 Web 的访问方式,允许用户根据自己的需求选择最合适的访问途径。 - **Windchill 服务器管理器和方法服务器**:服务器管理器用于集中管理 ...

    Tomcat6.0Tomcat6.0Tomcat6.0Tomcat6.0Tomcat6.0

    《深入理解Tomcat6.0:核心特性与实践应用》 Tomcat6.0作为Apache软件基金会的一个开源项目,是Java Servlet和JavaServer ...通过深入理解和实践,我们可以更好地掌握Web应用的部署与运维,为业务的稳定运行保驾护航。

    基于Spring的Java平台程序架构研究.pdf

    - **系统监控**:这部分通常使用JMX技术来实现,同样与应用系统解耦。 #### 三、设计思想与关键技术 ##### 3.1 领域驱动设计(DDD) 领域驱动设计是一种强调业务逻辑和技术实现紧密结合的设计方法。其核心在于...

    Tomcat源码研究.pdf

    4. JMX在tomcat中的应用:Java Management Extensions (JMX) 是一种为应用程序、设备、系统等植入管理功能的体系结构。Tomcat使用JMX来暴露其内部状态和管理信息,允许管理员通过各种JMX客户端来监控和配置运行中的...

    32.6、tomcat jdk各个版本区别1

    1. **基于JMX的管理**:增强了管理功能。 2. **管理Web应用**:支持JSP和Struts。 3. **Coyote连接器**:支持HTTP/1.1、AJP 1.3和JNI。 4. **Jasper重写**:改进了JSP页面编译器。 5. **性能和内存效率提升**。 6. *...

    zabbix配置管理

    在实际监控中,我们可以将监控对象识别为多种类型,包括服务器硬件(使用IPMI)、服务器性能(使用Agent)、Java应用(使用JMX)、MySQL和Web状态等。通过使用模板,比如TemplateSNMPDevice,我们可以方便地为交换机...

    apache-tomcat-6.0.53-windows-x64.rar

    总结,"apache-tomcat-6.0.53-windows-x64.rar"是一个针对Windows 64位系统的Tomcat版本,它提供了一个平台来运行和管理Java Web应用。通过了解其基本概念、配置方法和优化技巧,开发者可以高效地利用Tomcat来构建和...

    struts+spring+hibernate

    综上所述,"struts+spring+hibernate"的整合实现了Web应用的模型、视图、控制器分离,提供了面向对象的数据访问,以及灵活的事务和依赖注入管理。这种整合模式在大型企业级应用中非常常见,能够提升开发效率,降低...

    apache-tomcat-7.0.47 linux版

    这个版本是专门为Linux操作系统优化的,因此在Linux环境中部署和运行Java Web应用时,Tomcat 7.0.47是一个理想的选择。 1. **Tomcat的结构与组件** - `bin`目录:包含启动、停止和管理Tomcat的脚本。 - `conf`...

    基于solr的网站索引架构(一)

    它支持多种数据源,如文件、数据库等,并提供了RESTful API,便于集成到各种Web应用程序中。 2. **索引架构基础**: 索引是Solr的核心,它是对原始数据进行预处理后的结构化表示,用于高效搜索。索引由文档集合...

    rails magazine issue 3

    通过 JMX,可以远程管理并监控运行在 JVM 上的 Ruby 应用,这对于性能调优和系统管理至关重要。 ##### 6. Ruby Web 框架:深入 Waves **知识点:** Carlo Pecchia 带领我们深入了解 Waves 这个 Ruby Web 框架。...

    JAVA项目技术标(25个)

    2. **Spring框架**:Spring是Java企业级应用的核心框架,提供依赖注入(DI)、AOP(面向切面编程)、数据访问、事务管理等功能,是构建Java Web应用程序的重要组成部分。 3. **Spring Boot**:简化Spring应用程序的初始...

    apache-tomcat-6.0.36-src

    总的来说,"apache-tomcat-6.0.36-src"源代码提供了深入了解Tomcat内部工作方式的机会,对于开发者来说,这不仅有助于解决特定问题,还能提升对Java Web应用生命周期管理的理解。通过研究源代码,开发者可以更好地...

    一步一步安装配置Jboss4

    Jboss4是一款基于Java的开源应用服务器,它为开发和部署企业级应用程序提供了强大的支持。在本文中,我们将逐步介绍如何安装和配置Jboss4,以便您可以顺利地开始使用这个强大的平台。 首先,我们需要下载Jboss4的...

    JBPM4.4+spring+ext整合

    JBPM(Java Business Process Management)是一个开源的工作流管理系统,主要用于处理业务流程自动化。版本4.4是JBPM的一个重要里程碑,它提供了强大的工作流设计、执行和监控能力。Spring框架则是Java开发中的核心...

    tomcat6,7源码

    在源码中,可以看到Catalina如何通过`org.apache.catalina.core.StandardEngine`、`org.apache.catalina.core.StandardHost`和`org.apache.catalina.core.StandardContext`来管理Web应用程序的生命周期。理解这些类...

Global site tag (gtag.js) - Google Analytics