Detailed analysis of Servlet

First of all, let's talk about why we should learn Servlet. The reason is that when learning Spring MVC, I learned that Servlet is its core, and not knowing its principle will cause obstacles to learning Spring MVC, so I decided to write this article for learning.

1. What is a Servlet

Servlet (Server Applet), the full name of Java Servlet, has no Chinese translation. Is a server-side program written in Java. Its main function is to interactively browse and modify data and generate dynamic Web content. Servlet in the narrow sense refers to an interface implemented by java language. Servlet in the broad sense refers to any class that implements the servlet interface. Generally, people understand servlet as the latter.

The Servlet runs in an application server that supports Java. In terms of implementation, servlets can respond to any type of request, but in most cases, servlets are only used to extend Web servers based on HTTP protocol.

1.1 working mode

  • The client sends a request to the server
  • The server starts and calls the Servlet, which generates the response content according to the client request and transmits it to the server
  • The server returns the response to the client

1.2 Servlet API Preview

Servlet API includes the following four Java packages:

1.javax. Servlets contain classes and interfaces that define the contract between servlets and servlet containers.

2.javax.servlet.http, which defines the relationship between HTTP Servlet and Servlet container.

3.javax.servlet.annotation, which contains annotations of servlet, filter and listener. It also defines metadata for labeled components.

4.javax.servlet.descriptor, which contains the type of configuration information that provides the programmatic login Web application.

2. Servlet interface

2.1 method of servlet interface definition

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;
    
	ServletConfig getServletConfig();
 
	void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
 
	String getServletInfo();
 
	void destroy();
}

In addition

String getServletInfo();//This method will return a description of the Servlet and a string.
ServletConfig getServletConfig();//This method will return the ServletConfig object passed from the Servlet container to the init () method.

2.2 Servlet life cycle

Among them, * * init(), service(), destroy() * * is the method of Servlet life cycle. It represents the process of Servlet from "birth" to "work" and then to "death". The Servlet container (such as TomCat) will call these three methods according to the following rules:

  1. init(), when the Servlet is requested for the first time, the Servlet container will start calling this method to initialize a Servlet object, but this method will not be called by the Servlet container in subsequent requests, just like people can only be "born" once. We can use the init () method to perform the corresponding initialization. When this method is called, the Servlet container will pass in a ServletConfig object to initialize the Servlet object.

  2. service() method. Whenever a Servlet is requested, the Servlet container will call this method. Just like people, you need to constantly accept the boss's instructions and "work". In the first request, the Servlet container will first call the init() method to initialize a Servlet object, and then call its service() method to work. However, in subsequent requests, the Servlet container will only call the service method.

  3. When the time comes, the Servlet will be destroyed, just like when the Servlet method is called. This happens when you uninstall the application or close the Servlet container. Generally, some cleanup code will be written in this method.

2.3 ServletRequest interface

For each Http request received, the Servlet container will create a ServletRequest object and pass this object to the Servlet's service () method.

Let's take a look at part of the ServletRequest interface:

public interface ServletRequest {
    int getContentLength();//Returns the number of bytes of the request body

    String getContentType();//Returns the MIME type of the principal

    String getParameter(String var1);//Returns the value of the request parameter
}

Among them, getParameter is the most commonly used method in ServletRequest, which can be used to obtain the value of query string.

2.4 ServletResponse interface

javax. Servlet. The ServletResponse interface represents a Servlet response. Before calling the Service() method of the Servlet, the Servlet container will first create a ServletResponse object and pass it to the Service() method as the second parameter. ServletResponse hides the complex process of sending a response to the browser.

public interface ServletResponse {
    String getCharacterEncoding();

    String getContentType();

    ServletOutputStream getOutputStream() throws IOException;

    PrintWriter getWriter() throws IOException;

    void setCharacterEncoding(String var1);

    void setContentLength(int var1);

    void setContentType(String var1);

    void setBufferSize(int var1);

    int getBufferSize();

    void flushBuffer() throws IOException;

    void resetBuffer();

    boolean isCommitted();

    void reset();

    void setLocale(Locale var1);

    Locale getLocale();
}

The getWriter method returns a Java that can send text to the client io. PrintWriter object. By default, the * * PrintWriter object uses ISO-8859-1 encoding (this encoding will be garbled when entering Chinese)** When sending a response to the client, most of them use this object to send HTML to the client.

Another method can also be used to send data to the browser. It is getOutputStream. It can be seen from the name that it is a binary stream object, so this method is used to send binary data.

Before sending any HTML, you should first call setContentType() method to set the content type of the response and pass in "text/html" as a parameter. This is to tell the browser that the content type of the response is HTML. You need to interpret the response content in HTML instead of ordinary text, Alternatively, "charset=UTF-8" can be added to change the encoding mode of the response to prevent Chinese garbled code.

How to solve the phenomenon of Chinese garbled code is a very important problem, which will be explained later.

2.5 ServletConfig interface

When the Servlet container initializes the Servlet, the Servlet container will pass in a ServletConfig object to the init() method of the Servlet.

Several methods are as follows:

public interface ServletConfig {
    String getServletName();

    ServletContext getServletContext();

    String getInitParameter(String var1);

    Enumeration getInitParameterNames();
}

2.6 ServletContext object

The ServletContext object represents a servlet application. ServletContext is officially called servlet context. The server will create an object for each project, which is the ServletContext object. This object is globally unique, and all servlets within the project share this object. So it is called global application sharing object.

Each Web application has only one ServletContext object. In a distributed environment where an application is deployed to multiple containers at the same time, the Web application on each Java virtual machine will have a ServletContext object.

By calling the getServletContext method in ServletConfig, you can also get ServletContext objects.

So why should there be a ServletContext object? There must be a reason for its existence, * * because with the ServletContext object, you can share the information accessed from all materials in the application, and you can dynamically register the Web object. The former saves the object in an internal Map in the ServletContext** Objects stored in ServletContext are called properties.

The following methods in ServletContext handle properties:

Object getAttribute(String var1);

Enumeration<String> getAttributeNames();

void setAttribute(String var1, Object var2);

void removeAttribute(String var1);

The following points can be drawn from the summary:

  1. Is a domain object (a domain object is a storage space created by the server in memory for transferring and sharing data between different dynamic resources (servlet s))
  2. Global configuration parameters can be read
  3. You can search the resource files under the current project directory
  4. You can get the name of the current project (understand)

3. GenericServlet abstract class

To use the Servlet interface, you need to manually define all methods of the interface and manually maintain the reference of the object ServletConfig. The emergence of GenericServlet abstract class simplifies this process.

GenericServlet implements Servlet and ServletConfig interfaces. The following is the specific code of GenericServlet abstract class:

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
    private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.LocalStrings");
    private transient ServletConfig config;

    public GenericServlet() {
    }

    public void destroy() {
    }

    public String getInitParameter(String name) {
        ServletConfig sc = this.getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
        } else {
            return sc.getInitParameter(name);
        }
    }

    public Enumeration getInitParameterNames() {
        ServletConfig sc = this.getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
        } else {
            return sc.getInitParameterNames();
        }
    }

    public ServletConfig getServletConfig() {
        return this.config;
    }

    public ServletContext getServletContext() {
        ServletConfig sc = this.getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
        } else {
            return sc.getServletContext();
        }
    }

    public String getServletInfo() {
        return "";
    }

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

    public void init() throws ServletException {
    }

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

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

    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    public String getServletName() {
        ServletConfig sc = this.getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
        } else {
            return sc.getServletName();
        }
    }
}

Compared with directly implementing the Servlet interface, using GenericServlet abstract class has the following advantages:

  1. The default implementation is provided for all methods in the Servlet interface, so the programmer can directly change what he needs, and there is no need to implement all methods by himself.
  2. Provides methods that surround the methods in the ServletConfig object.
  3. The ServletConfig parameter in the init() method is assigned to an internal ServletConfig reference to save the ServletConfig object. Programmers do not need to maintain the ServletConfig themselves.

Why does init() have two methods

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

public void init() throws ServletException {
}

You can see that there are two init() methods in the GenericServlet abstract class. What is the original intention of the designer? Let's talk about the conclusion first. If you only rewrite the init() method, you can save the servletConfig in the GenericServlet object.

We know that an abstract class cannot directly generate an instance. If another class needs to inherit this abstract class, the problem of method coverage will occur. If the init() method of the GenericServlet abstract class is covered in the class, the programmer must manually maintain the ServletConfig object and call super Init (ServletConfig) method to call the initialization method of the parent GenericServlet to save the ServletConfig object, which will bring great trouble to programmers. The second init() method without parameters provided by GenericServlet is to solve the above problems.

The init() method without parameters is called by the first init(ServletConfig servletconfig) method with parameters after the ServletConfig object is assigned to the ServletConfig reference. This means that if the programmer needs to override the initialization method of this GenericServlet, he only needs to override the init() method without parameters. At this time, The GenericServlet object is still saved with ServletConfig.

4. More powerful HttpServlet

HttpServlet inherits the GenericServlet abstract class. Why is HttpServlet more powerful? There are two main reasons:

  1. HttpServlet is extended from the GenericServlet abstract class

    public abstract class HttpServlet extends GenericServlet implements Serializable 
    
  2. Now most applications have to be combined with HTTP, which means that we can use the characteristics of HTTP to complete more and more powerful tasks.

4.1 Service method

HttpServlet overrides the service method. Let's first take a look at the service method of the GenericServlet abstract class:

public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

It can be seen that this is an abstract method, that is, HttpServlet needs to implement this service method by itself. We are looking at how HttpServlet covers this service method:

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
    HttpServletRequest request;
    HttpServletResponse response;
    try {
        request = (HttpServletRequest)req;
        response = (HttpServletResponse)res;
    } catch (ClassCastException var6) {
        throw new ServletException("non-HTTP request or response");
    }

    this.service(request, response);
}

We found that the service method in HttpServlet converts the received object of ServletRequsest type into an object of HttpServletRequest type and an object of ServletResponse type into an object of HttpServletResponse type. The reason for this forced conversion is that * * when calling the service method of the Servlet, the Servlet container will always pass in an HttpServletRequest object and an HttpServletResponse object, ready to use HTTP** Therefore, the conversion type will certainly not make mistakes.

After the conversion, the service method passes the two converted objects into another service method, which is the core of completing the task:

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String method = req.getMethod();
    long lastModified;
    if (method.equals("GET")) {
        lastModified = this.getLastModified(req);
        if (lastModified == -1L) {
            this.doGet(req, resp);
        } else {
            long ifModifiedSince = req.getDateHeader("If-Modified-Since");
            if (ifModifiedSince < lastModified / 1000L * 1000L) {
                this.maybeSetLastModified(resp, lastModified);
                this.doGet(req, resp);
            } else {
                resp.setStatus(304);
            }
        }
    } else if (method.equals("HEAD")) {
        lastModified = this.getLastModified(req);
        this.maybeSetLastModified(resp, lastModified);
        this.doHead(req, resp);
    } else if (method.equals("POST")) {
        this.doPost(req, resp);
    } else if (method.equals("PUT")) {
        this.doPut(req, resp);
    } else if (method.equals("DELETE")) {
        this.doDelete(req, resp);
    } else if (method.equals("OPTIONS")) {
        this.doOptions(req, resp);
    } else if (method.equals("TRACE")) {
        this.doTrace(req, resp);
    } else {
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[]{method};
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(501, errMsg);
    }

}

We found that the parameters of this service method are HttpServletRequest object and HttpServletResponse object, which just received the two objects passed from the previous service method.

Next, let's look at how the service method works. We will find that there is still no service logic in the service method, but we are parsing the method parameters in HttpServletRequest and calling one of the following methods: * * doGet, doPost, dohead, doput, dotrace, doptions and doDelete** Each of the seven methods represents an Http method** doGet and doPost are the most commonly used** Therefore, if we need to implement specific service logic, we no longer need to override the service method, but just override doGet or doPost.

To summarize the characteristics of GenericServlet:

  1. Instead of overriding the service method, you override the doGet or doPost methods. In a few cases, the other five methods will be covered.
  2. HttpServletRequest and HttpServletResponse objects are used.

4.2 HttpServletRequest interface

HttpServletRequest represents a Servlet request in an Http environment. It extends to javax Servlet. ServletRequest interface and added several methods.

String getContextPath();//Returns the request URI part of the request context

Cookie[] getCookies();//Returns an array of cookie objects

String getHeader(String var1);//Returns the value of the specified HTTP header

String getMethod();//Returns the name of the HTTP method that generated the request

String getQueryString();//Returns the query string in the request URL

HttpSession getSession();//Returns the session object associated with this request

Because Request represents a Request, we can obtain the Request line, Request header and Request body of the HTTP Request through this object.

4.2.1 method of obtaining request line

Suppose the query string is: username = Zhangsan & password = 123

Request method for obtaining the client: String getMethod()

Get the requested resource:

String getRequestURI()

StringBuffer getRequestURL()

String getContextPath()//Name of the web application

String getQueryString()//get parameter string after submitting url address

4.2.2 method of obtaining request header

long getDateHeader(String name)

String getHeader(String name)

Enumeration getHeaderNames()

Enumeration getHeaders(String name)

int getIntHeader(String name)

//The function of the referer header: the source of this access and the anti-theft chain

4.2.3 method of obtaining requestor

The content in the request body is the request parameter submitted through post (get), and the format is key value:

username=zhangsan&password=123&hobby=football&hobby=basketball

The request parameters can be obtained through the following methods:

String getParameter(String name)

String[] getParameterValues(String name)

Enumeration getParameterNames()

Map<String,String[]> getParameterMap()

4.3 HttpServletResponse interface

In the Service API, an HttpServletResponse interface is defined, which inherits from the ServletResponse interface and is specially used to encapsulate HTTP response messages.

Since the HTTP request message is divided into three parts: status line, response message header and response message body, the method of sending response status code, response message header and response message body to the client is defined in the HttpServletResponse interface.

The following are all the methods in the interface, of which @ deprecated is deleted, that is, the eliminated methods:

void addCookie(Cookie var1);//Add a cookie to this response

boolean containsHeader(String var1);

String encodeURL(String var1);

String encodeRedirectURL(String var1);

void sendError(int var1, String var2) throws IOException;

void sendError(int var1) throws IOException;

void sendRedirect(String var1) throws IOException;//Send a response code saying that the browser jumps to the specified location

void setDateHeader(String var1, long var2);

void addDateHeader(String var1, long var2);

void setHeader(String var1, String var2);

void addHeader(String var1, String var2);//Add a response header to this request

void setIntHeader(String var1, int var2);

void addIntHeader(String var1, int var2);

void setStatus(int var1);//Set the status code of the response line

4.3.1 character stream and byte stream acquisition

PrintWriter getWriter() throws IOException;

Get the character stream, set the string into the response buffer through the write(String s) method of the character stream, and then Tomcat will assemble the contents in the response buffer into an Http response and return it to the browser.

ServletOutputStream getOutputStream() throws IOException;

Get the byte stream. write(byte[] bytes) of the byte stream can write bytes to the response buffer, and then the Tomcat server will form an Http response composed of byte contents and return it to the browser. To output the corresponding body in binary format, you need to use the getOutputStream() method.

Note: the two methods cannot be used at the same time, otherwise an error will be reported.

4.3.2 Response garbled code

Since the HttpServletResponse uses the ISO8859-1 code table by default, and the browser uses the GB2312 code table, the inconsistency between the two will lead to the problem of garbled code. Therefore, it is necessary to inform the sender, the server and the browser to use UTF-8 coding to decode, so as to solve the problem of garbled code.

response.setContentType("text/html;charset=UTF-8")

4.3.3 Response workflow

5. Servlet workflow

6. ServletContextListener (Global listener)

First of all, ServletContextListener is an interface in the Servlet container, and the class that implements the interface is responsible for listening to ServletContext. Let's take a look at the interface content first:

public interface ServletContextListener extends EventListener {
    void contextInitialized(ServletContextEvent var1);//initialization

    void contextDestroyed(ServletContextEvent var1);//Destroy
}

As you can see, there are only two functions inside the interface, which are initialization and destruction. Based on this, we can roughly guess the working mechanism of ServletContextListener:

  1. When the application starts, the ServletContext is initialized, and then the Servlet container will automatically call the void contextInitialized(ServletContextEvent var1) method of the ServletContextListener listening to the ServletContext, and pass in a ServletContextEvent object.
  2. When the application stops, the ServletContext is destroyed. At this time, the Servlet container will automatically call the void contextDestroyed(ServletContextEvent var1) method of the ServletContextListener listening to the ServletContext.

According to the above two methods, we can achieve some functions. For example, in practical applications, it is often necessary to count the number of Web pages accessed by the client since the Web application was published, which requires that when the Web application is terminated, the value of the counter is permanently stored in a file or database. When the Web application is restarted, first read the initial value of the counter from the file or database, and then continue counting on this basis. This function can be realized by implementing the ServletContextListener interface.

6.1 application in Spring

First, we need to review the concept of ServletContext. In fact, ServletContext is a "domain object". It exists in the whole application and has only one copy in the whole application. It represents the "state" of the current whole application. You can also understand that the ServletContext at a certain time represents the "snapshot" of the application at a certain time. This "snapshot" contains a lot of information about the application, All components of the application can obtain the status information of the current application from the ServletContext.

The program is destroyed as the servlet context is created. Generally speaking, we can "save things" in this ServletContext domain object, and then "take them out" in other places.

On the web Register Spring IOC container in XML:

<listener>

    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>

<context-param>

    <param-name>contextConfigLocation</param-name>

    <param-value>

        classpath:applicationContext.xml
    </param-value>

</context-param>

The listener class org springframework. web. context. Contextloaderlistener implements the ServletContextListener interface, which can monitor the "initialization" and "destruction" in the life cycle of ServletContext. Among them, org springframework. web. context. The listener class written by contextloaderlistener for the Spring team.

What is the internal situation of ContextLoaderListener? Let's take a look:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }

    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }

    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

As you can see, ContextLoaderListener implements two methods in the ServletContextListener interface. Among them, after the ServletContext is initialized, the contextInitialized(ServletContextEvent event) method is called and starts to execute the initWebApplicationContext(event.getServletContext() method. Since this method is not declared in this class, let's trace the source to its code:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
    } else {
        servletContext.log("Initializing Spring root WebApplicationContext");
        Log logger = LogFactory.getLog(ContextLoader.class);
        if (logger.isInfoEnabled()) {
            logger.info("Root WebApplicationContext: initialization started");
        }

        long startTime = System.currentTimeMillis();

        try {
            if (this.context == null) {
                this.context = this.createWebApplicationContext(servletContext);
            }

            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
                if (!cwac.isActive()) {
                    if (cwac.getParent() == null) {
                        ApplicationContext parent = this.loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }

                    this.configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }

            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            if (ccl == ContextLoader.class.getClassLoader()) {
                currentContext = this.context;
            } else if (ccl != null) {
                currentContextPerThread.put(ccl, this.context);
            }

            if (logger.isInfoEnabled()) {
                long elapsedTime = System.currentTimeMillis() - startTime;
                logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
            }

            return this.context;
        } catch (Error | RuntimeException var8) {
            logger.error("Context initialization failed", var8);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
            throw var8;
        }
    }
}

A long code is probably a process of creating an IoC container.

So far, we found that the Spring container has been instantiated in this method. Next, let's summarize the overall idea:

  1. When the Servlet container starts, the ServletContext object is initialized
  2. Then the Servlet container calls web The public void contextInitialized(ServletContextEvent event) method of the registered listener in XML is invoked, and this. is called in the listener. Initwebapplicationcontext (event. Getservletcontext()) method, in which the Spring IOC container is instantiated. ApplicationContext object.
  3. Therefore, when the ServletContext is created, we can create the applicationContext object. When the ServletContext is destroyed, we can destroy the applicationContext object. In this way, applicationContext and ServletContext "live and die together".

7. Full text summary


Keywords: Java Spring

Added by gtanzer on Tue, 08 Feb 2022 22:06:01 +0200