Loading

[Java]Sevlet

0 前言

对于Java程序员而言,Web服务器(如Tomcat)是后端开发绕不过去的坎。简单来看,浏览器发送HTTP请求给服务器,服务器处理后发送HTTP响应给浏览器。

Web服务器负责对请求进行处理。HTTP请求和响应本质上可以看成是有一定格式的字符串,而在网络中传输的数据都需要转换成二进制格式。Web服务器一方面需要将传入的二进制数据解析成程序能理解的HTTP请求对象,另一方面需要将处理后的响应包装成相应的响应对象,并转换成二进制数据传出。

对于Java程序员而言,以上这两个过程都由Web服务器帮我们自动完成,我们仅仅需要关注具体的业务。同时,Java也为该处理流程提供了规范,即Servlet接口。Maven依赖如下:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
</dependency>

因此,我们的主要工作就是根据不同的请求,实现对应的servlet。然后,服务器就可以通过下图的执行流程,完成对浏览器的响应。

Tomcat处理流程

为了实现具体的servlet,我们需要继承HttpServlet抽象类,并实现其中的某些方法,其类层次结构如下:

HttpServlet

1 Servlet接口

Servlet接口位于javax.servlet包中,是Servlet开发的顶层接口。

public interface Servlet{}

1.1 接口介绍

接口文档注释如下:

1、Defines methods that all servlets must implement.

2、A servlet is a small Java program that runs within a Web server. Servlets receive and respond to requests from Web clients, usually across HTTP, the 
HyperText Transfer Protocol.

3、To implement this interface, you can write a generic servlet that extends
	- javax.servlet.GenericServlet 
	- or an HTTP servlet that extends javax.servlet.http.HttpServlet.
		
4、This interface defines methods to initialize a servlet, to service requests, and to remove a servlet from the server. These are known as life-cycle 
methods and are called in the following sequence:
	① The servlet is constructed, then initialized with the init method.
	② Any calls from clients to the service method are handled.
	③ The servlet is taken out of service, then destroyed with the destroy method, then garbage collected and finalized.
	
5、In addition to the life-cycle methods, this interface provides the getServletConfig method, which the servlet can use to get any startup information, 
and the getServletInfo method, which allows the servlet to return basic information about itself, such as author, version, and copyright.

1、接口功能

定义了所有servlets必须实现的方法。

2、什么是servlet

在Web服务器中,用于处理客户端HTTP请求的Java对象,可以类比于Spring MVC中的Controller、Netty中的Handler

3、使用方法

为了定义我们自己的servlet,有两种方法(通常使用第2种方法):

  1. 继承javax.servlet.GenericServlet
  2. 对于处理Http请求的servlet,继承javax.servlet.http.HttpServlet

4、生命周期

接口中定义了初始化servlet、处理请求和从服务器中移除servlet的方法。这些方法被称为生命周期方法,其执行流程如下:

  1. servlet构造完成后,执行init()方法进行初始化;
  2. 执行service()方法处理客户端发送来的请求;
  3. 当需要移除servlet时,执行destroy()方法,进行相应的垃圾收集工作。

5、其他方法

除了生命周期方法,接口还定义了其他很实用的方法:

  • getServletConfig():获取所有启动信息;
  • getServletInfo():获取servlet的作者、版本和版权等信息。

1.2 方法详解

Servlet中仅定义了3个生命周期方法和2个获取信息的方法:

  • init()
  • service()
  • destroy()
  • getServletConfig()
  • getServeltInfo()

1、 init()

init()方法的完整签名为:void init(ServletConfig) throws ServletException

文档:

1、Called by the servlet container to indicate to a servlet that the servlet is being placed into service.

2、The servlet container calls the init method exactly once after instantiating the servlet. The init method must complete successfully before the servlet 
can receive any requests.

3、The servlet container cannot place the servlet into service if the init method:
	① Throws a ServletException
	② Does not return within a time period defined by the Web server

4、Params:
	config – a ServletConfig object containing the servlet's configuration and initialization parameters
	
5、Throws:
	ServletException – if an exception has occurred that interferes with the servlet's normal operation

如前所述,当servlet构造完成后,可以通过init()方法对其进行初始化。我们仅需要在该方法中定义相应的初始化逻辑,实际调用由servlet container控制。

需要注意的是:在实例化servlet后,servlet container仅会调用1次init()方法。并且只有当init()方法完全执行完毕之后,才会进行后续的接收客户端请求过程。

由于只有init()成功执行完毕,才能进行后续的服务,所以init()的成功执行至关重要。当出现下列2种情况时,init()方法会执行出错,影响后续的服务流程:

  1. 抛出ServletException异常;
  2. 没有在Web服务器规定的时间内返回。如执行耗时的I/O操作、线程休眠时间过长等(我们尽可能要避免这些操作)。

init()方法会接收一个ServletConfig对象,该对象中包含servlet的配置和初始化参数等信息,用于后续具体的初始化逻辑。

init()方法会抛出一个ServletException异常,该异常表示servlet执行过程中会遇到的一般异常。

2、service()

service()方法的完整签名:void service(ServletRequest, ServletResponse) throws ServletException, IOException

文档:

1、Called by the servlet container to allow the servlet to respond to a request.

2、This method is only called after the servlet's init() method has completed successfully.

3、The status code of the response always should be set for a servlet that throws or sends an error.

4、Servlets typically run inside multithreaded servlet containers that can handle multiple requests concurrently. Developers must be aware to synchronize 
access to any shared resources such as files, network connections, and as well as the servlet's class and instance variables. 

4、Params:
	req – the ServletRequest object that contains the client's request
	res – the ServletResponse object that contains the servlet's response

5、Throws:
	ServletException – if an exception occurs that interferes with the servlet's normal operation
	IOException – if an input or output exception occurs

service()方法的功能是处理客户端发送的请求。该方法会在特定的时刻由service container调用,我们仅仅需要定义好相应的处理逻辑即可。

service()只有在init()方法完成后才会被调用。

无论该servlet是否会抛出或发送异常,我们都要记得设置response的状态码。

由于servlet通常是运行在多线程的servlet container中,会同时接收到多个请求,所以开发人员必须在service()方法中注意对共享资源的访问。例如文件、网络连接以及servlet的类变量和成员变量等。

service()会接收ServletRequestServletResponse两个参数。前者代表客户端的请求对象,包含HTTP请求的各种信息,开发人员根据该对象包含的信息进行相应的业务处理;后者代表服务端的响应对象,包含HTTP响应的各种信息,开发人员需要通过该对象进行响应。

service()会抛出ServletExceptionIOException两种异常。前者代表servlet执行过程中的一般性异常,后者代表业务处理过程中IO操作可能会抛出的异常。

3、destroy()

destroy()方法的完整签名:void destroy()

文档:

1、Called by the servlet container to indicate to a servlet that the servlet is being taken out of service. This method is only called once all threads 
within the servlet's service method have exited or after a timeout period has passed. After the servlet container calls this method, it will not call the 
service method again on this servlet.
2、This method gives the servlet an opportunity to clean up any resources that are being held (for example, memory, file handles, threads) and make sure 
that any persistent state is synchronized with the servlet's current state in memory.

destroy()方法的签名比较简单,既没有接收参数,也没有返回参数和抛出异常。

servlet被移除前servlet container会调用destroy()方法,主要功能是在servlet销毁前释放相关资源(例如锁、内存、文件和线程等),以保证系统中的状态同步,使系统正常运行。

需要注意的是:destroy()方法会等到此servletservice()方法中的所有线程退出或超时后才执行,并且仅执行1次。当destroy()被调用后,servlet中的所有方法都不可能再被调用了,亦即该servlet没用了。

4、getServletConfig()

getServletConfig()方法的完整签名为:ServletConfig getServletConfig()

文档:

1、Returns a ServletConfig object, which contains initialization and startup parameters for this servlet. The ServletConfig object returned is the one 
passed to the init method.

2、Implementations of this interface are responsible for storing the ServletConfig object so that this method can return it. The GenericServlet class, 
which implements this interface, already does this.

3、Returns:
	the ServletConfig object that initializes this servlet

getServletConfig()的语义相当清晰,获取初始化servlet的配置信息。

getServletConfig()方法会返回一个ServletConfig对象,该对象包含了servlet的初始化和启动参数等信息。之前我们介绍过,init()方法需要接收一个ServletConfig对象,其实这两个ServletConfig对象是同一个对象。

实现Servlet接口需要保存这个ServletConfig对象以便于传入init()和通过getServletConfig()返回。在GenericServlet类中已经帮我们实现好这个功能。所以当我们继承GenericServletHttpServlet时,不再需要自己实现此方法,直接使用即可。

5、getServletInfo()

getServletInfo()方法的完整签名为:String getServletInfo()

文档:

1、Returns information about the servlet, such as author, version, and copyright.

2、The string that this method returns should be plain text and not markup of any kind (such as HTML, XML, etc.).

3、Returns:
	a String containing servlet information

getServletInfo()方法会返回一个字符串,包含servlet的作者、版本和版权等信息。

该字符串可以有多种格式,如text、HTML和XML等。

2 ServletConfig接口

ServletConfig接口位于javax.servlet包中,用来定义servlet配置信息对象所具备的方法。

public interface ServletConfig{}

2.1 接口介绍

文档:

A servlet configuration object used by a servlet container to pass information to a servlet during initialization.

ServletConfig定义了servlet的配置对象所具有的方法,用来获取各种配置信息。在servlet初始化过程中,servlet container会将此对象传入init()方法以进行初始化。

2.2 方法详解

ServletConfig接口中定义了4个方法,分别用来获取servlet的名字、上下文、初始化参数和初始化参数名:

  • getServletName()
  • getServletContext()
  • getInitParameter()
  • getInitParameterNames()

1、getServletName()

getServletName()方法的完整签名为:String getServletName()

文档:

1、Returns the name of this servlet instance. The name may be provided via server administration, assigned in the web application deployment descriptor, 
or for an unregistered (and thus unnamed) servlet instance it will be the servlet's class name.

2、Returns:
	the name of the servlet instance

getServletName()方法会返回一个字符串,表示servlet实例的的名字。

servlet的名字可以由服务器管理员提供,并在Web应用程序部署的描述符中配置。

如果没有对servlet手动指定名字,则默认为该servlet的类名。

2、getServletContext()

getServletContext()方法的完整签名为:ServletContext getServletContext()

文档:

1、Returns a reference to the ServletContext in which the caller is executing.

2、Returns:
	a ServletContext object, used by the caller to interact with its servlet container

getServletContext()方法会返回当前正在callerServletContext对象的引用。通过ServletContext对象可以与servlet container进行交互。

3、getInitParameter()

getInitParameter()方法的完整签名为:String getInitParameter(String)

文档:

1、Returns a String containing the value of the named initialization parameter, or null if the parameter does not exist.

2、Params:
	name – a String specifying the name of the initialization parameter

3、Returns:
	a String containing the value of the initialization parameter

getInitParameter()方法接收初始化参数的名字(字符串),并返回对应初始化参数的值(字符串)。如果没有该初始化参数,则返回null

4、getInitParameterNames()

getInitParameterNames()方法的完整签名为:Enumeration<String> getInitParameterNames()

文档:

1、Returns the names of the servlet's initialization parameters as an Enumeration of String objects, or an empty Enumeration if the servlet has no 
initialization parameters.

2、Returns:
	an Enumeration of String objects containing the names of the servlet's initialization parameters

getInitParamterNames()方法会返回包含servlet所有初始化参数的Enumeration集合对象。如果servlet没有初始化参数,则会返回空的Enumeration对象。

通过对该Enumeration对象进行遍历,即可得到各初始化参数,后续也可进行相应的操作。

3 Serializable接口

serializable接口位于JDKjava.io包中,是一个标识接口(没有方法),表明实现此接口的类可以进行序列化。

public interface Serializable{}

3.1 接口介绍

1、Serializability of a class is enabled by the class implementing the java.io.Serializable interface.

2、Warning: Deserialization of untrusted data is inherently dangerous and should be avoided. Untrusted data should be carefully validated according to the 
"Serialization and Deserialization" section of the Secure Coding Guidelines for Java SE. Serialization Filtering describes best practices for defensive 
use of serial filters. 

实现Serializable表明该类是可序列化的。

反序列化不受信任的数据是极度危险的,我们应该避免那样做。

3、Classes that do not implement this interface will not have any of their state serialized or deserialized. All subtypes of a serializable class are 
themselves serializable. The serialization interface has no methods or fields and serves only to identify the semantics of being serializable.

4、To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility for saving and restoring the state of the 
supertype's public, protected, and (if accessible) package fields. The subtype may assume this responsibility only if the class it extends has an 
accessible no-arg constructor to initialize the class's state. It is an error to declare a class Serializable if this is not the case. The error will be 
detected at runtime.

5、During deserialization, the fields of non-serializable classes will be initialized using the public or protected no-arg constructor of the class. A no-
arg constructor must be accessible to the subclass that is serializable. The fields of serializable subclasses will be restored from the stream.

如果一个类没有实现Serializable接口,那么无论在什么情况下它都不能序列化或反序列化。一个实现了Serializable接口的类的所有子类都是可序列化和反序列化的。Serializable接口中没有定义方法和属性,它只是一个标识接口,用来表示某类是否可以序列化。

假设父类没有实现Serializable接口,而子类为了能够实现序列化功能,它需要在序列化或反序列化过程中保存或恢复父类中非私有(publicprotecteddefault)的属性。为了实现这一目标,需要完成两个要求:

  1. 子类是可序列化的:实现Serializable接口,并且其属性都是可序列化的。
  2. 父类提供一个无参构造器,其能够完成父类中属性的初始化。

如果子类不能满足以上要求,则会在运行时报错。

反序列化时,不可序列化的父类的属性会使用其无参构造器进行初始化,并且该无参构造器必须是子类能访问的(public/protected)。而可序列化子类的属性则直接从数据流中获得。

6、When traversing a graph, an object may be encountered that does not support the Serializable interface. In this case the NotSerializableException will 
be thrown and will identify the class of the non-serializable object.

在遍历graph时,可能会遇到集合中的对象没有实现Serializable接口的情况。此时会抛出NotSerializableException异常,并标记该类为不可序列化的。

7、Classes that require special handling during the serialization and deserialization process must implement special methods with these exact signatures:
   - private void writeObject(java.io.ObjectOutputStream out) throws IOException
   - private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
   - private void readObjectNoData() throws ObjectStreamException;
   
8、The writeObject method is responsible for writing the state of the object for its particular class so that the corresponding readObject method can 
restore it. The default mechanism for saving the Object's fields can be invoked by calling out.defaultWriteObject. The method does not need to concern 
itself with the state belonging to its superclasses or subclasses. State is saved by writing the individual fields to the ObjectOutputStream using the 
writeObject method or by using the methods for primitive data types supported by DataOutput.

9、The readObject method is responsible for reading from the stream and restoring the classes fields. It may call in.defaultReadObject to invoke the 
default mechanism for restoring the object's non-static and non-transient fields. The defaultReadObject method uses information in the stream to assign 
the fields of the object saved in the stream with the correspondingly named fields in the current object. This handles the case when the class has evolved 
to add new fields. The method does not need to concern itself with the state belonging to its superclasses or subclasses. State is restored by reading 
data from the ObjectInputStream for the individual fields and making assignments to the appropriate fields of the object. Reading primitive data types is 
supported by DataInput.

10、The readObjectNoData method is responsible for initializing the state of the object for its particular class in the event that the serialization 
stream does not list the given class as a superclass of the object being deserialized. This may occur in cases where the receiving party uses a different 
version of the deserialized instance's class than the sending party, and the receiver's version extends classes that are not extended by the sender's 
version. This may also occur if the serialization stream has been tampered; hence, readObjectNoData is useful for initializing deserialized objects 
properly despite a "hostile" or incomplete source stream.

如果某些实现了Serializable接口的类在序列化或反序列化过程中需要对数据进行特殊处理,则可以在该类中实现如下方法:

  • private void writeObject(java.io.ObjectOutputStream) throws IOException
  • private void readObject(java.io.ObjectInputStream) throws IOException, ClassNotFoundException
  • private void readObjectNoData() throws ObjcetStreamException

通过在这些方法中定义特定处理逻辑,即可在序列化或反序列化过程中对数据进行特殊处理。例如实现数据流传输中的单例模式就是利用这些方法。

writeObject()方法负责在序列化过程中将对象属性转换成数据流,而readObject()方法则负责在反序列化过程中将数据流转换成实际对象。序列化时,保存对象属性的默认机制是调用out.defaultWriteObject()方法,该方法并不关心属性是属于父类还是子类的。使用writeObject()方法或DataOutput提供的写出基本数据类型的方法可以对象的属性分别写出到ObjectOutputStream中。

readObject()方法负责将数据从流中读出并保存到对象属性中。其默认机制是调用in.defaultReadObject()方法来恢复对象的non-staticnon-transient属性。defaultReadObject()方法会根据DataInputStream中的信息为对应的对象属性分配数据。如果类新增了属性字段,由于在数据流中找不到对应的属性名,则该字段不会被恢复。该方法并不关心属性是属于父类还是子类的。

当在反序列化过程中,数据流中没有对象某些父类的信息时,readObjectNoData()方法负责初始化相应对象。当序列化和反序列化两端的类的版本不一致时可能会出现这种情况,此时相对于发送端类,接收端类可能新增了几个父类。另外,如果数据流被篡改也可能会出现这种情况。此时,readObjectNoData()方法可以对数据流进行特殊处理,执行正确的初始化过程。

11、Serializable classes that need to designate an alternative object to be used when writing an object to the stream should implement this special method 
with the exact signature:
   ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
   
12、This readResolve method follows the same invocation rules and accessibility rules as writeReplace.

在将对象写入数据流时,如果需要为序列化类指定一个可替换的对象,则需要实现如下方法:ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException

相反,在将对象从数据流中恢复时,如果需要为反序列化类指定一个可替换的对象,则需要实现如下方法:ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException

13、The serialization runtime associates with each serializable class a version number, called a serialVersionUID, which is used during deserialization to 
verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization. If the 
receiver has loaded a class for the object that has a different serialVersionUID than that of the corresponding sender's class, then deserialization will 
result in an InvalidClassException. A serializable class can declare its own serialVersionUID explicitly by declaring a field named "serialVersionUID" 
that must be static, final, and of type long:
   ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

在序列化过程中需要为每个序列化类关联一个版本号——serialVersionUID,它可以保证在反序列化过程中发送端和接收端的序列化类是兼容的。如果在反序列化过程中发现接收端和发送端序列化类的版本号不同,则会抛出一个InvalidClassException异常。任何序列化类都可以且应该声明一个它自己的版本号,可以通过声明一个属性:ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;,属性值是任意的,但必须保持一致。

14、If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value 
for that class based on various aspects of the class, as described in the Java(TM) Object Serialization Specification. However, it is strongly recommended 
that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class 
details that may vary depending on compiler implementations, and can thus result in unexpected InvalidClassExceptions during deserialization. Therefore, 
to guarantee a consistent serialVersionUID value across different java compiler implementations, a serializable class must declare an explicit 
serialVersionUID value. It is also strongly advised that explicit serialVersionUID declarations use the private modifier where possible, since such 
declarations apply only to the immediately declaring class--serialVersionUID fields are not useful as inherited members. Array classes cannot declare an 
explicit serialVersionUID, so they always have the default computed value, but the requirement for matching serialVersionUID values is waived for array 
classes.

如果你没有显示声明一个serialVersionUID,系统会根据多方面因素计算出一个默认的serialVersionUID。具体因素在Java(TM)对象序列化规范中有详细描述。

强烈建议为每一个序列化类都声明serialVersionUID,因为serialVersionUID对类的细节十分敏感,很容易会根据编译器实现的不同而发生变化,从而导致在反序列化过程中发生InvalidClassException异常。因此,为了保证在不同编译器实现中serialVersionUID的一致性,必须为序列化类声明明确的serialVersionUID。并且强烈建议使用private修饰符进行修饰,这样可以保证在子类中对该属性没有访问权限,从而避免恶意修改。数组不能声明serialVersionUID,因为它们使用默认计算的值,但是数组序列化是不需要匹配该值的。

4 GenericServlet抽象类

GenericServlet抽象类位于tomcat-embed-core.jar下的javax.servlet包中。

public abstract class GenericServlet implements Servlet, ServletConfig, 			
		java.io.Serializable

4.1 类介绍

1、Defines a generic, protocol-independent servlet. 

GenericServlet类的作用:定义了通用的、独立于协议的servlet的标准。

2、To write an HTTP servlet for use on the Web, extend javax.servlet.http.HttpServlet instead.

3、GenericServlet implements the Servlet and ServletConfig interfaces. GenericServlet may be directly extended by a servlet, although it's more common to 
extend a protocol-specific subclass such as HttpServlet.

Tomcat为我们提供了一个的专门适用于HTTP协议开发的子类:javax.servlet.http.HttpServlet。在Web开发中,我们应该直接继承HttpServlet进行开发,而不是使用GenericServlet。(后续会对HttpServlet进行详细介绍。)

4、GenericServlet makes writing servlets easier. It provides simple versions of the lifecycle methods init and destroy and of the methods in the 
ServletConfig interface. GenericServlet also implements the log method, declared in the ServletContext interface.

5、To write a generic servlet, you need only override the abstract service method.

GenericServletservlet提供了Servlet接口中生命周期方法(init()destroy())、ServletConfig接口中获取配置的方法以及声明在ServletContext接口中的日志方法的简单实现,从而使得我们编写servlet更加容易。

需要注意的是:GenericServlet中的service()方法是抽象的,需要我们根据不同的业务自己实现。

4.2 属性介绍

GenericServlet中包含两个属性:

  • private static final long serialVersionUID = 1L;
  • private transient ServletConfig config;

serialVersionUID为序列化版本号,主要为了保证序列化和反序列化过程中的一致性。

ServletConfig表示servlet初始化的配置参数,用于传入给init()方法进行初始化,以及getServletConfig()方法获取配置参数。

4.3 方法介绍

GenericServlet中的方法比较多,我们可以按照实现的不同接口或功能进行分类:

  • 实现自Servlet接口:
    • init()
    • service()
    • destroy()
    • getServletConfig()
    • getServletInfo()
  • 实现自ServletConfig接口:
    • getServletName()
    • getServletContext()
    • getInitParameter()
    • getInitParameterNames()
  • ServletContext接口中的日志方法:
    • log()

4.3.1 Servlet接口中的方法

1、init()

GenericServlet中提供了两个init()方法:

  • public void init() throws ServletException
  • public void init(ServletConfig config) throws ServletException

其中带参数的init(ServletConfig)才是Servlet接口中定义的初始化方法,而无参的init()GenericServlet类中新增的方法。

init()方法中的方法体默认为空:

public void init() throws ServletException {
    // NOOP by default
}

init(ServletConfig)方法中做了两件事:①初始化ServletConfig属性,②执行init()方法:

@Override
public void init(ServletConfig config) throws ServletException {
    this.config = config;
    this.init();
}

实际上,当servlet加载到servlet container中时,容器在对其进行初始化的过程中调用的是init(ServletConfig)方法,它能够保证对ServletConfig的保存,避免重写时的恶意或错误代码。同时,它后续还会执行init()方法,这样能够保证我们自定义的初始化逻辑能够正常执行。

因此,当开发人员需要自定义servlet的初始化过程时,应当重写init()方法。而如果必须重写init(ServletConfig)方法,必须在首行调用super.init(ServletConfig)

这种设计方法能够将框架逻辑与业务逻辑分隔开来,使开发人员仅需要关注自己的业务逻辑。

2、service()

如前所述,service()方法中执行的是servlet的业务逻辑。因为每个servlet的功能都不同,service()方法需要自定义,因此GenericServlet中的service()方法是抽象的,我们必须对其进行重写:

@Override
public abstract void service(ServletRequest req, ServletResponse res)
		throws ServletException, IOException;

3、destroy()

destroy()方法主要是为了在servlet移除前释放init()service()方法中占用的资源。默认情况下servlet中并没有使用相关资源,因此它与init()方法一样,默认也是空的:

@Override
public void destroy() {
    // NOOP by default
}

如果servlet运行时涉及到相关系统资源的占用,我们必须重写此方法,在移除前释放相关资源。

4、getServletConfig()4

getServletConfig()方法返回servlet的配置对象,该对象已经通过init(ServletConfig)保存在config属性中,我们只需要将该属性返回即可:

@Override
public ServletConfig getServletConfig() {
    return config;
}

5、getServletInfo()

getServletInfo()方法返回servlet的作者、版本和版权等信息,默认情况下返回空字符串。后续子类可以对其重写,以输出实际有意义的信息:

@Override
public String getServletInfo() {
    return "";
}

4.3.2 ServletConfig接口中的方法

1、getServletName()

getServletName()返回servlet的名字,由于servlet的所有配置信息都保存在属性config中,因此可以通过该属性进行进一步获取servlet的名字:

@Override
public String getServletName() {
    return config.getServletName();
}

2、getServletContext()

getServletContext()返回servletservletContext,也是通过config属性获取:

@Override
public ServletContext getServletContext() {
    return getServletConfig().getServletContext();
}

3、getInitParameter()

getInitParameter(String)根据初始化参数的名字查找其值,也是通过config属性获取:

@Override
public String getInitParameter(String name) {
    return getServletConfig().getInitParameter(name);
}

4、getInitParameterNames()

getInitParameterNames()则是获取所有初始化参数的Enumeration<String>集合,也是通过config属性获取:

@Override
public Enumeration<String> getInitParameterNames() {
    return getServletConfig().getInitParameterNames();
}

4.3.3 日志方法

GenericServlet类提供了两个日志方法,它们实际上都是使用servletContext提供的日志方法进行记录:

  • public void log(String message):以servlet的名字为前缀将信息写入日志。
  • public void log(String message, Throwable t):为可能抛出异常的servlet记录日志,以servlet的名字为前缀,并提供了异常对象以追踪堆栈异常。

它们的源码如下:

public void log(String message) {
    getServletContext().log(getServletName() + ": " + message);
}

public void log(String message, Throwable t) {
    getServletContext().log(getServletName() + ": " + message, t);
}

4.4 总结

通过上面的介绍,我们对GenericServlet类进行总结。它其实只为我们提供了三个功能:

  1. servlet的生命周期方法:init()init(ServletConfig)service()destroy()
  2. 获取配置信息的属性和方法:基于private transient ServletConfig config;
  3. 日志方法:log(String)log(String, Throwable)

我们只需要记住这三个功能,就能够对GenericServlet类和一般的servlet功能有很好的整体理解。

5 HttpServlet抽象类

HttpServlet抽象类位于tomcat-embed-core.jar下的javax.servlet包中,是Servlet开发中使用最多的类。

public abstract class HttpServlet extends GenericServlet{}

5.1 类介绍

1、Provides an abstract class to be subclassed to create an HTTP servlet suitable for a Web site.

HttpServlet抽象类为所有基于HTTP的servlet提供了一套标准的模板。这意味着在Web开发中,我们自己实现的servlet必须继承此类。

2、A subclass of HttpServlet must override at least one method, usually one of these:
	- doGet, if the servlet supports HTTP GET requests
	- doPost, for HTTP POST requests
	- doPut, for HTTP PUT requests
	- doDelete, for HTTP DELETE requests
	- init and destroy, to manage resources that are held for the life of the servlet
	- getServletInfo, which the servlet uses to provide information about itself

HttpServlet的子类中必须实现以下至少一个方法:

  • doGet():用于处理HTTP的GET请求。
  • doPost():用于处理HTTP的POST请求。
  • doPut():用于处理HTTP的PUT请求。
  • doDelete():用于处理HTTP的DELETE请求。
  • init()destroy():用于管理servlet生命周期中所持有的资源。
  • getServletInfo():用于提供servlet的信息。
3、There's almost no reason to override the service method. service handles standard HTTP requests by dispatching them to the handler methods for each 
HTTP request type (the doMethod methods listed above).

4、Likewise, there's almost no reason to override the doOptions and doTrace methods.

需要注意的是:我们理论上不应该重写service()方法。因为service()方法中实现了根据不同HTTP请求方式(GET、POST、PUT和DELETE等)进行分发的逻辑,类似于Spring MVC中的DispatchServlet类。

我们也不应该重写doOptions()doTrace()方法,原因类似。

5、Servlets typically run on multithreaded servers, so be aware that a servlet must handle concurrent requests and be careful to synchronize access to 
shared resources. Shared resources include in-memory data such as instance or class variables and external objects such as files, database connections, 
and network connections.

servlet通常运行在多线程服务器中,我们应该注意处理同步的HTTP请求,同步对共享资源的访问。共享资源包括内存中的对象属性或类属性、外部的文件对象、数据库连接资源和网络连接等。

5.2 静态属性介绍

HttpServlet抽象类中只有静态属性,用于实现不同的功能:

  • 序列化版本号:private static final long serialVersionUID = 1L
  • HTTP请求方法标识:
    • GET:private static final String METHOD_GET = "GET";
    • POST:private static final String METHOD_POST = "POST";
    • PUT:private static final String METHOD_PUT = "PUT";
    • DELETE:private static final String METHOD_DELETE = "DELETE";
    • HEAD:private static final String METHOD_HEAD = "HEAD";
    • OPTIONS:private static final String METHOD_OPTIONS = "OPTIONS";
    • TRACE:private static final String METHOD_TRACE = "TRACE";
  • HTTP请求头部标识:
    • private static final String HEADER_IFMODSINCE = "If-Modified-Since";
    • private static final String HEADER_LASTMOD = "Last-Modified";
  • 配置资源文件的地址以及访问对象:
    • private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
    • private static final ResourceBundle lStrings = ResourceBundle.getBundle(LSTRING_FILE);

5.3 方法介绍

HttpServlet没有重写GenericSerblet抽象类中已经实现的各种方法,只是实现了service()方法,并且增加了许多针对于HTTP协议的特殊方法。我们可以将这些方法分成以下几类:

  • 构造函数:HttpServlet()

  • servlet的业务处理方法:service()

  • 各种HTTP请求方式对应的方法:

    • doGet()
    • doPost()
    • doPut()
    • doDelete()
    • doHead()
    • doOptions()
    • doTrace()
  • 各种HTTP请求头部对应的方法:

    • maybeSetLastModified()
    • getLastModified()
  • 其他方法:

    • sendMethodNotAllowed()
    • getAllDeclaredMethods()

1、HttpServlet()

因为HttpServlet是一个抽象类,所以构造器中没有实现任何逻辑:

public HttpServlet() {
    // NOOP
}

2、service()

GenericServlet抽象类中的init()方法一样,HttpServlet抽象类中的service()方法也提供了两个重载方法:

  • public void service(ServletRequest req, ServletResponse res)
  • protected void service(HttpServletRequest req, HttpServletResponse resp)

其中service(ServletRequest, ServletResponse)是对Servlet接口中的该方法的实现,功能是对请求协议进行初步筛选。当接收到相应的请求时,容器会调用此方法进行处理。此方法默认是对HTTP协议进行处理,因此对参数ServletRequestServletResponse进行了向下转型。如果转型失败,会抛出ServletException异常,否则执行重载的service(HttpServletRequest, HttpServletResponse)方法。

@Override
public void service(ServletRequest req, ServletResponse res)
    throws ServletException, IOException {

    HttpServletRequest  request;
    HttpServletResponse response;

    try {
        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;
    } catch (ClassCastException e) {
        throw new ServletException(lStrings.getString("http.non_http"));
    }
    service(request, response);
}

service(HttpServletRequest, HttpServletResponse)方法中才真正开始了对请求进行处理,它会调用HttpServletRequest.getMethod()方法获取当前HTTP请求的方式,随后根据不同的方式分发到对应的doXxx()方法中进行处理。

protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {

    String method = req.getMethod();

    if (method.equals(METHOD_GET)) {
        long lastModified = getLastModified(req);
        if (lastModified == -1) {
            // servlet doesn't support if-modified-since, no reason
            // to go through further expensive logic
            doGet(req, resp);
        } else {
            long ifModifiedSince;
            try {
                ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            } catch (IllegalArgumentException iae) {
                // Invalid date header - proceed as if none was set
                ifModifiedSince = -1;
            }
            if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                // If the servlet mod time is later, call doGet()
                // Round down to the nearest second for a proper compare
                // A ifModifiedSince of -1 will always be less
                maybeSetLastModified(resp, lastModified);
                doGet(req, resp);
            } else {
                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            }
        }
    } else if (method.equals(METHOD_HEAD)) {
        long lastModified = getLastModified(req);
        maybeSetLastModified(resp, lastModified);
        doHead(req, resp);
    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);
    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);
    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);
    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req,resp);
    } else if (method.equals(METHOD_TRACE)) {
        doTrace(req,resp);
    } else {
        //
        // Note that this means NO servlet supports whatever
        // method was requested, anywhere on this server.
        //
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}

总而言之,两个service()共同完成了HTTP请求的分发功能,针对不同的请求方法,最终的业务逻辑都在doXxx()方法中。服务器只需调用service(ServletRequest, ServletRespose)这一个方法,就能完成类型转换、请求分发的功能。

3、doGet()

重写此方法可以同时支持HTTP的GET和HEAD请求方式。HEAD方式本质上也是GET方式,只不过它的HTTP响应中只有首部信息,没有主体信息。

我们重写此方法的流程一般如下:

  1. 通过HttpServletRequest参数设置请求数据的解码编码集。
  2. 通过HttpServletRequest参数读取请求数据;
  3. 处理数据;
  4. 通过HttpServletResponse参数设置响应头部、输出编码集;
  5. 通过HttpServletResponse参数获取输出对象,并输出响应数据。

在读取请求数据或写出响应数据前,必须设置好相应的编码。响应首部必须在响应数据输出前设置好,因为对于HTTP响应而言,响应首部是先于响应体发送的。

如果请求格式不正确,doGet()方法会返回一个错误信息。

protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
{
    String msg = lStrings.getString("http.method_get_not_supported");
    sendMethodNotAllowed(req, resp, msg);
}

4、doPost()

重写doPost()方法可用于处理POST请求。

HTTP的POST请求允许客户端向服务端发送无限长的数据,这尤其对一些安全数据格外重要。

我们重写此方法的流程一般如下:

  1. 通过HttpServletRequest参数设置请求数据的解码编码集。
  2. 通过HttpServletRequest参数读取请求数据;
  3. 处理数据;
  4. 通过HttpServletResponse参数设置响应头部、输出编码集;
  5. 通过HttpServletResponse参数获取输出对象,并输出响应数据。

读取请求数据或写出响应数据前,必须设置好相应的编码。响应首部必须在响应数据输出前设置好,因为对于HTTP响应而言,响应首部是先于响应体发送的。

如果请求格式不正确,doPost()方法会返回一个错误信息。

protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    String msg = lStrings.getString("http.method_post_not_supported");
    sendMethodNotAllowed(req, resp, msg);
}

5、doPut()

重写doPut()方法用于处理HTTP的PUT请求。

HTTP的PUT请求主要用于客户端向服务器发送文件。

当重写doPut()方法时,需要保留相应的请求头部,包括Content-Length Content-Type Content-Transfer-Encoding Content-Encoding Content-Base Content-Language Content-LocationContent-MD5 Content-Range等。如果重写的doPUt()方法中不能处理这些请求头,则必须发送一个错误消息:HTTP 501 - Not Implemented,并丢弃这个请求。

doPut()方法并不是安全或幂等的,它可能会对数据产生副作用,因此开发人员要对此负责。执行此方法时,临时存储相应的URL在某些情况下很有用。

如果请求格式不正确,doPost()方法会返回一个错误信息。

protected void doPut(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    String msg = lStrings.getString("http.method_put_not_supported");
    sendMethodNotAllowed(req, resp, msg);
}

6、doDelete()

重写doDelete()方法用于处理HTTP的DELETE请求。

DELETE请求允许客户端从服务器上删除相应的数据。

doDelete()方法并不是安全或幂等的,它可能会对数据产生副作用,因此开发人员要对此负责。执行此方法时,临时存储相应的URL在某些情况下很有用。

如果请求格式不正确,doDelete()方法会返回一个错误信息。

protected void doDelete(HttpServletRequest req,
                            HttpServletResponse resp)
        throws ServletException, IOException {
    String msg = lStrings.getString("http.method_delete_not_supported");
    sendMethodNotAllowed(req, resp, msg);
}

7、doHead()

重写doHead()方法用于处理HTTP的HEAD请求。

客户端在只需要获取响应的首部如Content-TypeContent-Length,而不需要响应体时,可以发送HEAD请求。

doHead()方法需要计算响应中的输出字节数,从而准确设置Content-Length首部。而如果你重写了此方法,可以直接设置Content-Length,避免计算过程,从而提高性能。

重写的doHead()应当是安全并且幂等的。

如果请求格式不正确,doHead()方法会返回一个错误信息。

protected void doHead(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    if (DispatcherType.INCLUDE.equals(req.getDispatcherType())) {
        doGet(req, resp);
    } else {
        NoBodyResponse response = new NoBodyResponse(resp);
        doGet(req, response);
        response.setContentLength();
    }
}

8、doOptions()

doPotions()方法用于处理HTTP的OPTIONS请求。

客户端发送OPTIONS请求向服务器询问其支持的HTTP请求方式,服务器会将支持的所有请求方式通过Allow首部传回。

例如,如果servlet只重写了doGet()方法,那么它的doOptions()方法会响应如下首部:Allow: GET, HEAD, TRACE, OPTIONS

一般情况下我们不需要重写doOptions()方法,除非servlet实现了HTTP 1.1之外的新的HTTP请求方法。

doOptions()方法默认实现了返回服务器所有请求方式的逻辑,其通过反射的方式获取本类中重写或者新增的方法,并将所有方法名添加到响应的Allow首部中。

需要注意的是,TRACEOPTIONS请求方式是默认支持的。

protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {

    Method[] methods = getAllDeclaredMethods(this.getClass());

    boolean ALLOW_GET = false;
    boolean ALLOW_HEAD = false;
    boolean ALLOW_POST = false;
    boolean ALLOW_PUT = false;
    boolean ALLOW_DELETE = false;
    boolean ALLOW_TRACE = true;
    boolean ALLOW_OPTIONS = true;

    // Tomcat specific hack to see if TRACE is allowed
    Class<?> clazz = null;
    try {
        clazz = Class.forName("org.apache.catalina.connector.RequestFacade");
        Method getAllowTrace = clazz.getMethod("getAllowTrace", (Class<?>[]) null);
        ALLOW_TRACE = ((Boolean) getAllowTrace.invoke(req, (Object[]) null)).booleanValue();
    } catch (ClassNotFoundException | NoSuchMethodException | SecurityException |
            IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
        // Ignore. Not running on Tomcat. TRACE is always allowed.
    }
    // End of Tomcat specific hack

    for (int i=0; i<methods.length; i++) {
        Method m = methods[i];

        if (m.getName().equals("doGet")) {
            ALLOW_GET = true;
            ALLOW_HEAD = true;
        }
        if (m.getName().equals("doPost"))
            ALLOW_POST = true;
        if (m.getName().equals("doPut"))
            ALLOW_PUT = true;
        if (m.getName().equals("doDelete"))
            ALLOW_DELETE = true;
    }

    String allow = null;
    if (ALLOW_GET)
        allow=METHOD_GET;
    if (ALLOW_HEAD)
        if (allow==null) allow=METHOD_HEAD;
        else allow += ", " + METHOD_HEAD;
    if (ALLOW_POST)
        if (allow==null) allow=METHOD_POST;
        else allow += ", " + METHOD_POST;
    if (ALLOW_PUT)
        if (allow==null) allow=METHOD_PUT;
        else allow += ", " + METHOD_PUT;
    if (ALLOW_DELETE)
        if (allow==null) allow=METHOD_DELETE;
        else allow += ", " + METHOD_DELETE;
    if (ALLOW_TRACE)
        if (allow==null) allow=METHOD_TRACE;
        else allow += ", " + METHOD_TRACE;
    if (ALLOW_OPTIONS)
        if (allow==null) allow=METHOD_OPTIONS;
        else allow += ", " + METHOD_OPTIONS;

    resp.setHeader("Allow", allow);
}

9、doTrace()

doTrace()用于处理HTTP的TRACE请求。

TRACE的请求首部会保存在其响应首部中一起发回给客户端,主要用于调试。

我们一般不必重写doTrace()方法,它默认实现了该逻辑,基本步骤如下:

  1. 通过StringBuilder将请求中的URI、协议以及各种首部信息按照格式写入到TRACE首部中;
  2. 设置当前相应的相应首部:ContentTypeContentLength
  3. 输出响应。
protected void doTrace(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {

    int responseLength;

    String CRLF = "\r\n";
    StringBuilder buffer = new StringBuilder("TRACE ").append(req.getRequestURI())
        .append(" ").append(req.getProtocol());

    Enumeration<String> reqHeaderEnum = req.getHeaderNames();

    while( reqHeaderEnum.hasMoreElements() ) {
        String headerName = reqHeaderEnum.nextElement();
        buffer.append(CRLF).append(headerName).append(": ")
            .append(req.getHeader(headerName));
    }

    buffer.append(CRLF);

    responseLength = buffer.length();

    resp.setContentType("message/http");
    resp.setContentLength(responseLength);
    ServletOutputStream out = resp.getOutputStream();
    out.print(buffer.toString());
    out.close();
}

10、maybeSetLastModified()

maybeSetLASTModified()方法是一个私有方法,用于实现在必要时设置Last-Modified首部的操作。

必须在响应数据前调用此方法,因为HTTP首部必须在响应体前发送。

private void maybeSetLastModified(HttpServletResponse resp, long lastModified) {
    if (resp.containsHeader(HEADER_LASTMOD))
        return;
    if (lastModified >= 0)
        resp.setDateHeader(HEADER_LASTMOD, lastModified);
}

11、getLastModified()

getLastModified()方法返回HttpServletRequest对象的最后修改时间。如果不知道修改时间,该方法会返回-1(默认)。

支持HTTP的GET请求并且能够快速确定其最后修改时间的servlet应该重写此方法。这会提高浏览器或代理服务器缓存的工作效率,减少服务器和网络资源的负载。

protected long getLastModified(HttpServletRequest req) {
    return -1;
}

12、sendMethodNotAllowed()

sendMethodNotAllowed()是一个私有方法,抽象了服务器不支持相关请求,从而向客户端发送错误报告的逻辑。

private void sendMethodNotAllowed(HttpServletRequest req, HttpServletResponse resp, String msg) throws IOException {
    String protocol = req.getProtocol();
    // Note: Tomcat reports "" for HTTP/0.9 although some implementations
    //       may report HTTP/0.9
    if (protocol.length() == 0 || protocol.endsWith("0.9") || protocol.endsWith("1.0")) {
        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
    } else {
        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
    }
}

13、getAllDeclaredMethods()

getAllDeclaredMethods()是一个私有的静态方法,获取所有当前servlet重写、新增的方法。主要用于doOptions()方法中获取servlet提供的服务。

private static Method[] getAllDeclaredMethods(Class<?> c) {

    if (c.equals(javax.servlet.http.HttpServlet.class)) {
        return null;
    }

    Method[] parentMethods = getAllDeclaredMethods(c.getSuperclass());
    Method[] thisMethods = c.getDeclaredMethods();

    if ((parentMethods != null) && (parentMethods.length > 0)) {
        Method[] allMethods = new Method[parentMethods.length + thisMethods.length];
        System.arraycopy(parentMethods, 0, allMethods, 0, parentMethods.length);
        System.arraycopy(thisMethods, 0, allMethods, parentMethods.length, thisMethods.length);

        thisMethods = allMethods;
    }

    return thisMethods;
}

5.4 内部类

HttpServlet抽象类中提供了NoBodyResponseNoBodyOutputStream两个内部类,分别用于对没有响应体的HTTP响应进行包装和写出。

1、NoBodyResponse

NoBodyResponse类对没有响应体的响应进行了包装,以便于自动计算响应的长度等。

class NoBodyResponse extends HttpServletResponseWrapper {
    private final NoBodyOutputStream noBody;
    private PrintWriter writer;
    private boolean didSetContentLength;
    
    // file private
    NoBodyResponse(HttpServletResponse r) {
        super(r);
        noBody = new NoBodyOutputStream(this);
    }

    // file private
    void setContentLength() {
        if (!didSetContentLength) {
            if (writer != null) {
                writer.flush();
            }
            super.setContentLength(noBody.getContentLength());
        }
    }


    // SERVLET RESPONSE interface methods

    @Override
    public void setContentLength(int len) {
        super.setContentLength(len);
        didSetContentLength = true;
    }

    @Override
    public void setContentLengthLong(long len) {
        super.setContentLengthLong(len);
        didSetContentLength = true;
    }

    @Override
    public void setHeader(String name, String value) {
        super.setHeader(name, value);
        checkHeader(name);
    }

    @Override
    public void addHeader(String name, String value) {
        super.addHeader(name, value);
        checkHeader(name);
    }

    @Override
    public void setIntHeader(String name, int value) {
        super.setIntHeader(name, value);
        checkHeader(name);
    }

    @Override
    public void addIntHeader(String name, int value) {
        super.addIntHeader(name, value);
        checkHeader(name);
    }

    private void checkHeader(String name) {
        if ("content-length".equalsIgnoreCase(name)) {
            didSetContentLength = true;
        }
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return noBody;
    }

    @Override
    public PrintWriter getWriter() throws UnsupportedEncodingException {

        if (writer == null) {
            OutputStreamWriter w;

            w = new OutputStreamWriter(noBody, getCharacterEncoding());
            writer = new PrintWriter(w);
        }
        return writer;
    }
}

2、NoBodyOutputStream

NoBodyOutputStream对输出流进行了包装,滤过了所有数据,用于对没有响应体的响应进行输出。

class NoBodyOutputStream extends ServletOutputStream {

    private static final String LSTRING_FILE =
        "javax.servlet.http.LocalStrings";
    private static final ResourceBundle lStrings =
        ResourceBundle.getBundle(LSTRING_FILE);

    private final HttpServletResponse response;
    private boolean flushed = false;
    private int contentLength = 0;

    // file private
    NoBodyOutputStream(HttpServletResponse response) {
        this.response = response;
    }

    // file private
    int getContentLength() {
        return contentLength;
    }

    @Override
    public void write(int b) throws IOException {
        contentLength++;
        checkCommit();
    }

    @Override
    public void write(byte buf[], int offset, int len) throws IOException {
        if (buf == null) {
            throw new NullPointerException(
                    lStrings.getString("err.io.nullArray"));
        }

        if (offset < 0 || len < 0 || offset+len > buf.length) {
            String msg = lStrings.getString("err.io.indexOutOfBounds");
            Object[] msgArgs = new Object[3];
            msgArgs[0] = Integer.valueOf(offset);
            msgArgs[1] = Integer.valueOf(len);
            msgArgs[2] = Integer.valueOf(buf.length);
            msg = MessageFormat.format(msg, msgArgs);
            throw new IndexOutOfBoundsException(msg);
        }

        contentLength += len;
        checkCommit();
    }

    @Override
    public boolean isReady() {
        // TODO SERVLET 3.1
        return false;
    }

    @Override
    public void setWriteListener(javax.servlet.WriteListener listener) {
        // TODO SERVLET 3.1
    }

    private void checkCommit() throws IOException {
        if (!flushed && contentLength > response.getBufferSize()) {
            response.flushBuffer();
            flushed = true;
        }
    }
}
posted @ 2021-10-25 19:14  Xianuii  阅读(400)  评论(0编辑  收藏  举报