🐱 Tomcat 学习笔记

启动流程 · 请求处理 · 设计思想 · 源码解析

一、什么是 Tomcat

Tomcat 是一个由 Java 开发的轻量级 Web 服务器。

下载网址:https://tomcat.apache.org/

二、初始化:load()

这一阶段的主要工作就是:配置解析、初始化组件、绑定端口。

首先进入 main() 方法中:

1. 自身实例化:Bootstrap bootstrap = new Bootstrap()

执行 static 代码块,作用是:确定 CATALINA_HOME 和 CATALINA_BASE 两个路径并写到 JVM 系统属性,以内置的 Map 形式储存,通过 getProperty() 获取。

2. Bootstrap.init() —— 搭建类加载环境 + 创建 Catalina 实例

Bootstrap.init() 的作用是为 Tomcat 的核心引擎(Catalina 类)搭建一个隔离的类加载环境,然后在这个环境中通过反射创建 Catalina 实例。共分三步:

第一步:构建三层类加载器体系(initClassLoaders())

通过 createClassLoader() 方法,根据 catalina.properties 中的配置创建三个类加载器:

这样的层次设计实现了类加载器隔离:Tomcat 内部类(如 catalina.jar)和 Web 应用类(如 /WEB-INF/lib)使用不同的 ClassLoader,互相不可见,避免了类冲突。

第二步:设置线程 ClassLoader 与 SecurityManager 预加载

第三步:反射创建 Catalina 守护进程实例

由于 Bootstrap 由 App ClassLoader 加载(来自 bootstrap.jar),而 Catalina 类在 lib 目录下只能由 catalinaLoader 加载,二者处于不同的 ClassLoader 命名空间。如果直接写 new Catalina(),JVM 会用 Bootstrap 的 ClassLoader(App CL)去查找 Catalina 类,必然抛出 ClassNotFoundException。因此必须先用 catalinaLoader.loadClass("Catalina") 显式指定加载器获取 Class 对象,再通过 getConstructor().newInstance() 反射调用构造方法,这就绕过了编译期类型绑定,实现了跨 ClassLoader 的"桥接"。创建完成后,再通过反射调用 setParentClassLoader(sharedLoader),将 sharedLoader 注入到 Catalina 实例中,后续所有 Web 应用的 ClassLoader 都将以 sharedLoader 为父加载器。

最后将创建好的 Catalina 实例保存到 catalinaDaemon 字段,后续的 load()、start()、stop() 等方法都通过反射操作这个对象。

Bootstrap.init()
  ├── initClassLoaders()              创建 common/catalina/shared 类加载器
  ├── Thread.setContextClassLoader()  线程绑定 catalinaLoader
  ├── SecurityClassLoad.load()        SecurityManager 预加载
  ├── catalinaLoader.loadClass(Catalina)  反射加载 Catalina 类
  ├── getConstructor().newInstance()      反射创建 Catalina 对象
  └── setParentClassLoader(sharedLoader)  注入 sharedLoader

三、Catalina.load() —— 解析配置文件 + 构建组件树 + init 初始化组件

Catalina.load() 是启动过程中最关键的一步,负责将 conf/server.xml 中的 XML 配置解析为一棵完整的 Java 组件对象树,并完成所有组件的初始化(init),最终绑定端口。该方法包含以下几个主要环节:

总体流程

Catalina.load() 方法里一共执行了如下:
├── initDirs()                     创建 temp/work/logs 目录(已废弃,空方法)
├── initNaming()                   注册 Tomcat JNDI 实现
├── parseServerXml(true)          ★ 解析 server.xml,构建完整组件树
│     ├── ConfigFileLoader.setSource()         注册配置源(CatalinaBaseConfigurationSource)
│     │                                          以 catalina.base 为基准目录
│     ├── createStartDigester()                 构建 Digester 规则引擎
│     │     └── 为每个 XML 元素绑定创建规则:
│     │           <Server>  → new StandardServer()
│     │           <Service> → new StandardService()
│     │           <Connector> → new Connector()
│     │           <Engine>  → new StandardEngine()
│     │           <Host>    → new StandardHost()
│     │           <Context> → new StandardContext()
│     │           <Wrapper> → new StandardWrapper()
│     ├── digester.push(this)                   Catalina 自身入栈
│     └── digester.parse(inputSource)           SAX 事件驱动解析 XML → 构建对象树
│           └── 解析结果:Catalina.server = StandardServer(完整组件树)
│                 ├── StandardService
│                 │     ├── Connector (port=8080)
│                 │     └── StandardEngine
│                 │           └── StandardHost (localhost)
│                 │                 └── StandardContext (/app)
│                 │                       └── StandardWrapper (具体Servlet)
│
├── server.setCatalina(this)       双向绑定:Server 反向持有 Catalina 引用
│     作用:组件树深层节点可通过 Server → Catalina 获取全局配置(如 parentClassLoader)
├── server.setCatalinaHome(Bootstrap.getCatalinaHomeFile())   注入安装目录路径
├── server.setCatalinaBase(Bootstrap.getCatalinaBaseFile())   注入实例目录路径
│     作用:将路径存到 Server 对象,组件树通过 getCatalinaBase() 获取,不依赖 JVM 系统属性
│
├── initStreams()                  重定向 System.out/err
│     └── System.setOut(new SystemLogHandler(System.out))
│     └── System.setErr(new SystemLogHandler(System.err))
│     作用:装饰器模式,按线程按需捕获 stdout/stderr 输出,转为 Tomcat 日志
│
└── server.init()                  ★ 初始化整个组件树(级联)
      │
      └── StandardServer.initInternal()
            ├── register(MBeanFactory)              JMX 管理接口注册
            ├── globalNamingResources.init()        全局 JNDI 资源
            └── for 每个 Service: service.init()
                  │
                  └── StandardService.initInternal()
                        ├── engine.init()                引擎 init
                        │     └── ContainerBase.initInternal()
                        │           ├── Realm.init()     认证域
                        │           └── 并行 init 所有 Host
                        │                 └── 并行 init 所有 Context
                        │
                        ├── executor.init()              线程池
                        ├── mapperListener.init()        URL 路由映射表
                        │
                        └── connector.init()            ★ Connector init
                              │
                              └── Connector.initInternal()
                                    ├── adapter = new CoyoteAdapter(this)    创建适配器
                                    ├── protocolHandler.setAdapter(adapter)   注入到协议层
                                    └── protocolHandler.init()              协议处理器 init
                                          └── AbstractProtocol.init()
                                                ├── 注册 JMX MBean
                                                └── endpoint.init()
                                                      └── bind()
                                                           ★★★ 绑定端口 8080!
                                                           serverSocket.bind(addr, backlog)
                                                           init 阶段只 bind,不 accept

(1) initDirs():创建临时目录

创建临时目录(如 temp/、work/、logs/),此方法在 Tomcat 9 中已废弃,为空方法,目录创建由其他组件负责。

(2) initNaming():初始化 JNDI 命名服务

设置 JVM 系统属性,告诉 JNDI 优先搜索 org.apache.naming 包下的实现,并使用 Tomcat 自己的 InitialContext 工厂类。这样 Web 应用中的 JNDI 查找(如数据库连接池)会被路由到 Tomcat 的 JNDI 实现。

什么是 JNDI:JNDI 是 Java 提供的一套 API 标准,用来通过"名字"去查找和访问各种资源,而不是直接写死资源的地址。

(3) parseServerXml(true):★ 解析 server.xml,构建完整的组件树

这是最核心的一步。

StandardServer
  └── StandardService
        ├── Connector (port=8080)
        └── StandardEngine
              └── StandardHost (name=localhost)
                    └── StandardContext (path=/app)
                          └── StandardWrapper (具体 Servlet)

(4) server.setCatalina(this):建立双向绑定

Catalina 持有 Server 引用(server 字段),现在也让 Server 反向持有 Catalina 引用。这样组件树中任意深层节点(如 Context)都可以通过 getParent() 链路最终拿到 Server,再通过 Server.getCatalina() 访问全局配置(如 parentClassLoader),而不需要把这些配置层层传递。

(5) 注入路径

server.setCatalinaHome(Bootstrap.getCatalinaHomeFile()):将安装目录路径注入到 Server 对象中。server.setCatalinaBase(Bootstrap.getCatalinaBaseFile()):将实例目录路径注入到 Server 对象中。这样组件树任何节点都能通过 server.getCatalinaBase() 获取基准目录,解析相对路径(如 webapps/、work/ 等),不依赖 JVM 系统属性。

(6) initStreams():重定向 System.out / System.err

用 SystemLogHandler(装饰器模式,继承 PrintStream)替换 JVM 的标准输出/错误流。SystemLogHandler 内部通过 ThreadLocal 栈实现"按线程按需捕获":当 StandardWrapperValve 调用 Servlet 时,先调用 startCapture() 开始捕获,Servlet 中 System.out.println() 的输出会被拦截到缓冲区,Servlet 执行完毕后 stopCapture() 取出内容,通过 Tomcat 日志框架(JULI)输出。没有捕获时,输出原样透传到原始 PrintStream(控制台)。

(7) server.init():★ 级联初始化整个组件树

所有组件都走 LifecycleBase.init() 模板方法,它控制状态转换骨架(NEW → INITIALIZING → INITIALIZED),子类通过重写 initInternal() 实现各自的初始化逻辑。初始化从 Server 开始,逐层向下:

整个 load() 完成后,Tomcat 处于 INITIALIZED 状态:组件树构建完毕,所有对象创建完毕,端口已绑定,但一切还是静止的——没有线程在运行,没有请求被处理。

四、Catalina.start() —— 启动所有组件 + 开始接收请求

总体流程

Catalina.start() 方法里执行了:
├── (如果未 load,先执行 load())
├── server.start()                     ★ 启动组件树
│     │
│     └── LifecycleBase.start()        模板方法(final,子类不可重写)
│           ├── 状态检查: NEW → 自动 init()
│           ├── setState(STARTING_PREP)
│           ├── startInternal()         ★ 多态调用子类实现
│           └── setState(STARTED)
│           │
│           └── StandardServer.startInternal()
│                 ├── globalNamingResources.start()
│                 └── for 每个 Service: service.start()
│                       │
│                       └── StandardService.startInternal()
│                             │
│                             ├── engine.start()         ★ 容器先启动
│                             │     └── ContainerBase.startInternal()
│                             │           ├── cluster.start()          集群
│                             │           ├── realm.start()            认证域
│                             │           ├── 并行启动 Host(多线程)
│                             │           │     └── Host.startInternal()
│                             │           │           ├── 安装 ErrorReportValve
│                             │           │           └── 并行启动 Context(多线程)
│                             │           │                 ├── WebappLoader.start()
│                             │           │                 ├── Manager.start()       Session管理器
│                             │           │                 ├── filterStart()         初始化Filter
│                             │           │                 ├── loadOnStartup()       ★ load-on-startup
│                             │           │                 │     └── 收集 loadOnStartup>=0 的Wrapper
│                             │           │                 │           └── wrapper.load()
│                             │           │                 │                 ├── loadServlet()
│                             │           │                 │                 │     └── newInstance()  反射创建Servlet
│                             │           │                 │                 └── initServlet()
│                             │           │                 │                       └── servlet.init(facade) ★
│                             │           │                 └── pipeline.start()     Valve链启动
│                             │           └── pipeline.start()
│                             │
│                             ├── executor.start()        线程池
│                             ├── mapperListener.start()  构建URL路由映射表
│                             │     └── 遍历所有Host/Context/Wrapper
│                             │           → 建立 URL → 组件 的映射关系
│                             │
│                             └── connector.start()       ★ 连接器最后启动
│                                   │
│                                   └── Connector.startInternal()
│                                         └── protocolHandler.start()
│                                               │
│                                               └── AbstractProtocol.start()
│                                                     ├── endpoint.start()
│                                                     │     └── NioEndpoint.startInternal()
│                                                     │           ├── 创建对象缓存池(避免GC)
│                                                     │           │     ├── processorCache  Http11Processor复用
│                                                     │           │     ├── eventCache      PollerEvent复用
│                                                     │           │     └── nioChannels     NioChannel复用
│                                                     │           ├── createExecutor()      Worker线程池(200个)
│                                                     │           ├── Poller线程启动        NIO Selector轮询
│                                                     │           └── Acceptor线程启动      ★★★
│                                                     │                 └── serverSock.accept()
│                                                     │                      开始阻塞等待客户端连接!
│                                                     └── 异步超时扫描定时器(每60秒)
│
├── Runtime.addShutdownHook()         注册 JVM 关闭钩子(Ctrl+C 优雅关闭)
└── await()                           主线程阻塞等待 shutdown 命令

详细说明

Catalina.start() 是整个启动流程的最后一步,负责将所有已经初始化好的组件"激活"——让它们从 INITIALIZED 状态进入 STARTED 状态,最终开始接收和处理客户端请求。

首先,Catalina.start() 会检查 Server 是否已经 load 过,如果还没有 load(比如嵌入式场景直接调了 start),就先执行 load() 完成组件树的构建和初始化。

然后调用 server.start(),这里进入 LifecycleBase.start() 这个模板方法。start() 被声明为 final,所有子类都不能重写它,保证了状态机骨架的统一。模板方法内部先做状态检查:如果是 NEW 状态则自动调用 init();然后设置状态为 STARTING_PREP,接着调用 startInternal()——这里利用 Java 的多态机制,JVM 根据 this 的实际类型(StandardServer)来调用对应的 startInternal() 实现。执行完毕后将状态设置为 STARTED。

StandardServer.startInternal() 首先触发 START 事件,然后启动全局 JNDI 资源,接着遍历所有 Service 并依次调用它们的 start()。

每个 Service 的启动在 StandardService.startInternal() 中完成,这里是启动顺序的关键设计点:容器先启动,连接器后启动。这样确保当外部请求进来时,Engine → Host → Context 整个处理链路已经完全就绪。

具体来说,Service 启动时先启动 engine(容器层)。Engine 的 startInternal() 打印启动日志后调用父类 ContainerBase.startInternal()。ContainerBase 的启动逻辑是通用的,适用于 Engine、Host、Context 等所有容器:首先启动 Cluster(集群)和 Realm(认证域),然后并行启动所有子容器。子容器用线程池并行启动,多个 Host 或 Context 可以同时初始化,大大缩短启动时间。

对于 StandardHost,启动前会先安装 ErrorReportValve(错误页面阀门),然后调用父类逻辑并行启动它下面的所有 Context。

对于 StandardContext,启动过程是 Web 应用初始化的核心:

Engine 启动完成后,Service 接着启动 executor(线程池)和 mapperListener(URL 路由映射表)。MapperListener 会遍历所有 Host、Context、Wrapper,建立 URL 到目标组件的映射关系,后续请求到达时 CoyoteAdapter 可以通过这个映射表快速路由。

最后,Service 启动所有 Connector——这是整个启动流程的最后一步,也是最关键的一步。Connector.startInternal() 调用 protocolHandler.start(),最终进入 NioEndpoint.startInternal():

Connector 全部启动完毕后,Catalina.start() 注册 JVM ShutdownHook(当用户按 Ctrl+C 或 kill 进程时,钩子线程会调用 Catalina.stop() 优雅关闭所有组件),然后调用 await() 让主线程阻塞等待 shutdown 命令。至此,Tomcat 完全启动,开始正常处理请求。

五、请求到达 —— 完整的请求处理链路

客户端发起 HTTP 请求
       │
       ▼
┌──────────────────────────────────────────────────────┐
│  Acceptor 线程 (http-nio-8080-Acceptor)    [1个]      │
│                                                      │
│  while (!stopCalled) {                               │
│      socket = serverSock.accept()   ← 阻塞等待TCP连接  │
│      socket.configureBlocking(false) ← 设为非阻塞模式  │
│      poller.register(socketWrapper)  ← 注册到Poller   │
│  }                                                   │
│                                                      │
│  职责:只做accept(),拿到连接立即交给Poller,            │
│        然后马上回头继续accept下一个连接                  │
└────────────────────┬─────────────────────────────────┘
                     │ 通过事件队列传递(线程安全)
                     ▼
┌──────────────────────────────────────────────────────┐
│  Poller 线程 (http-nio-8080-Poller)    轮询器   [1-2个]│
│                                                      │
│  持有: Selector selector;    // Java NIO 多路复用器    │
│        SynchronizedQueue<PollerEvent> events; 事件队列 │
│                                                      │
│  while (true) {                                      │
│      events()             ← 处理待注册/修改事件队列     │
│      selector.select()    ← NIO 多路复用,阻塞等IO事件  │
│      processKey()         ← 遍历就绪的SelectionKey     │
│          └─ executor.execute(socketProcessor)         │
│              ★ 提交到 Worker 线程池                    │
│  }                                                   │
│                                                      │
│  职责:一个线程通过Selector同时监控成千上万个连接,       │
│        只有"有数据到达"的连接才占用Worker线程            │
└────────────────────┬─────────────────────────────────┘
                     │ 线程池分派
                     ▼
┌──────────────────────────────────────────────────────┐
│  Worker 线程 (http-nio-8080-exec-N)      [200个]      │
│                                                      │
│  Http11Processor.process()                           │
│    ├─ 解析 HTTP 请求行 (GET /app/hello HTTP/1.1)      │
│    ├─ 解析请求头 (Host, Cookie, Content-Type...)      │
│    ├─ 解析请求体 (POST body)                          │
│    └─ adapter.service(coyoteReq, coyoteRes)  ──────→ │
│                                                      │
│  职责:解析HTTP协议,然后通过Adapter交给Catalina层       │
└──────────────────────────────────────────────────────┘
                     │
                     ▼  CoyoteAdapter (适配器/翻译官)
┌──────────────────────────────────────────────────────┐
│  CoyoteAdapter.service()                             │
│                                                      │
│   ① coyote.Request → catalina.Request  (对象包装转换)  │
│   ② postParseRequest()  (URI解析 + 路由映射)          │
│        └─ mapper.map(host, uri)                      │
│             → 找到目标: Engine→Host→Context→Wrapper    │
│   ③ pipeline.invoke(request, response)  ───────────→ │
│                                                      │
│  职责:把Coyote层的原始请求翻译成Catalina层的HttpServlet│
│        Request,并完成URI路由                          │
└──────────────────────────────────────────────────────┘
                     │
                     ▼  Pipeline 责任链
┌──────────────────────────────────────────────────────┐
│  StandardEngineValve.invoke()                         │
│    └─ StandardHostValve.invoke()                      │
│          └─ StandardContextValve.invoke()              │
│                └─ StandardWrapperValve.invoke()        │
│                      │                                │
│                      ├─ wrapper.allocate()            │
│                      │     └─ 首次访问此Servlet        │
│                      │           └─ loadServlet()     │
│                      │                 └─ newInstance()│
│                      │                 └─ servlet.init(facade) ★
│                      │                                │
│                      ├─ filterChain.doFilter()        │
│                      │     └─ Filter1 → Filter2 → ... │
│                      │                                │
│                      └─ servlet.service(req, res) ★★★ │
│                           最终到达你的业务代码!         │
└──────────────────────────────────────────────────────┘

六、三种线程的职责划分(Reactor 模式)

线程类型线程名数量职责
Acceptor http-nio-8080-Acceptor 1 个 只做 accept(),接收 TCP 连接,交给 Poller
Poller http-nio-8080-Poller 1-2 个 NIO Selector 多路复用,监听所有连接的 IO 事件,分派给 Worker 线程池
Worker (线程池) http-nio-8080-exec-N 200 个(可配) 解析 HTTP 协议、调用 Servlet 处理业务逻辑、返回响应

设计目的

避免慢请求阻塞新连接的接收。

七、设计模式总结

设计模式应用场景
模板方法模式LifecycleBase.init()/start() 控制骨架,子类 initInternal()/startInternal() 填空
组合模式Server→Service→Engine→Host→Context→Wrapper 树形组件结构
适配器模式CoyoteAdapter 连接 coyote 层和 catalina 层
装饰器模式SystemLogHandler 包装 System.out,实现按线程捕获输出
责任链模式Pipeline + Valve 链式处理请求
Reactor 模式Acceptor(接收)→Poller(IO复用)→Worker(处理)
生产者-消费者模式Acceptor 生产连接 → Poller 消费连接
状态机模式Lifecycle: NEW→INITIALIZED→STARTING→STARTED,严格状态转换
单例模式Bootstrap daemon 全局唯一实例
反射/桥接Bootstrap 用反射跨越 ClassLoader 隔离墙,操作 Catalina 实例

八、核心概念速查

CATALINA_HOME vs CATALINA_BASE

Selector

Java NIO 多路复用器,底层是 Linux epoll / macOS kqueue。一个线程通过一个 Selector 同时监控成千上万个 Channel 的 IO 事件,复杂度 O(1),只返回就绪的 Channel。

Adapter (CoyoteAdapter)

Coyote 层和 Catalina 层之间的翻译官:coyote.Request → catalina.Request,完成 URI 路由映射。

Digester

SAX 事件驱动的 XML→对象映射引擎。通过 addObjectCreate/addSetProperties 等规则,把 server.xml 解析为 Java 组件树。

类加载器三层

commonLoader (lib/*.jar)
  ├── catalinaLoader (Tomcat内部,默认=commonLoader)
  └── sharedLoader (Web应用共享,默认=commonLoader)

init vs start

阶段作用状态
init资源准备(创建对象、绑定端口)INITIALIZED
start启动线程(开始 accept、开始处理)STARTED

分离原因:失败回滚简单、支持热部署、JMX 管理、嵌入式场景。

九、主要用到的思想

1. 分层 / 组件化思想

Server(服务器,最外层)
 └── Service(服务,管理多个连接器)
      ├── Connector(连接器,处理网络 IO)
      │    ├── ProtocolHandler(协议处理)
      │    └── Adapter(适配器,把请求转成 Servlet 请求)
      └── Engine(引擎,顶层容器)
           └── Host(虚拟主机)
                └── Context(Web 应用)
                     └── Wrapper(单个 Servlet)

2. 生命周期管理思想

主要体现在 Lifecycle 接口 + LifecycleBase 抽象类,LifecycleBase 用模板方法模式统一管理状态,StandardServer、StandardService、StandardContext 等都继承它。

3. 责任链思想

一个请求进来,要经过一串处理器,每个处理完再交给下一个。核心是 ContainerBase 里的 Pipeline 和 Valve,StandardPipeline 里维护一串 Valve,用 addValve 添加。

4. 类加载器隔离思想

这是 Tomcat 最独特的思想之一。它没有直接沿用 JDK 标准的双亲委派,而是自定义了一套:

Bootstrap(系统类加载器)
  └── Common(公共类加载器)
       ├── Catalina(服务器内部类)
       └── Shared(共享类)
            └── Webapp(每个应用独立)

核心目的:让不同 Web 应用之间的类互相隔离(A 应用的 jar 不影响 B 应用),同时又能共享公共库。体现在 Bootstrap + WebappClassLoader,应用级隔离在 org.apache.catalina.loader.WebappClassLoader,它的 loadClass 方法先自己加载,加载不到再委托父加载器。

5. 连接器与容器解耦思想

Tomcat 把网络处理和业务处理彻底分开:

两者通过 Adapter(CoyoteAdapter)衔接。这样切换协议(HTTP/1.1、AJP、HTTP/2)不影响容器逻辑。体现在 Connector + CoyoteAdapter。

6. NIO 非阻塞 IO 思想

用少量线程处理海量连接,核心是"事件驱动 + 复用"。核心是:

7. 事件驱动思想

程序不是一下就产生结果,而是在边启动边启动其他的事件。

这个也是 SAX 的核心思想:

在 Tomcat 中的使用:事件监听器、SAX 的解析、Servlet / Filter 的 doGet / doFilter、Valve(阀门)管道机制。

十、我学到了什么?

1. 如何将信息设置到系统属性

用 System.setProperty() 和 getProperty()。

2. 分层构建

分为不同层级来实现不同的 Web 应用的实现。

3. JNDI

JNDI 是 Java 提供的一套 API 标准,用来通过"名字"去查找和访问各种资源,而不是直接写死资源的地址。

4. 反射创建

Class<?> className = Class.forName("位置");
Object instance = className.getConstructor().newInstance();//这个是无参构造
Method method = instance.getClass().getMethod("method", String);//获取方法method,String表示参数类型
method.invoke(instance,);

反射 API 速查表:

你想获取公开的(含继承)所有(含私有,不含继承)
构造方法getConstructor(...)getDeclaredConstructor(...)
构造方法(全部)getConstructors()getDeclaredConstructors()
字段getField(name)getDeclaredField(name)
方法getMethod(name, paramTypes)getDeclaredMethod(name, paramTypes)

5. 解析 XML 文件,用 SAX 方法

什么是 SAX:SAX 是一种基于事件、流式的 XML 解析方式。

特点:

写法

重写里面的方法:

方法参数说明
startElement() ① String uri — 命名空间 URI,例如 XML 里写了 <student xmlns="http://xxx">,这里就会是 "http://xxx"。
② String localName — 例如 <ns1:student>,localName 就是 "student"。没有命名空间时,它和 qName 相同。
③ String qName — 限定名(含前缀),例如 <ns1:student>,qName 就是 "ns1:student"。ns1 是非必须的。
④ Attributes attributes — 属性集合,例如 <student id="1">,attributes.getValue("id") 就返回 "1"。
characters() ① char[] ch — 字符缓冲区,解析器把文本内容存到这个数组里
② int start — 有效文本在 ch 数组中的起始下标
③ int length — 有效文本的长度(字符个数)
endElement() 参数有 String uri, String localName, String qName,和 startElement 的使用相同。

使用流程

// 在使用时,先创建一个工厂实例
SAXParserFactory factory = SAXParserFactory.newInstance();

// 在从这个工厂中获取解析器
SAXParser parser = factory.newSAXParser();

// 创建重写DefaultHandler类实例
ParseHandler handler = new ParseHandler();

// 打开文件并使用这个handler规则解析
parser.parse(is, handler);