Servlet详解

458 阅读5分钟

本文的知识点:

  1. servlet快速入门
  2. servlet生命周期
  3. 生命周期详解
  4. 注解配置方式
  5. Servlet继承结构
  6. Servlet路径配置

1. Servlet快速入门

下载servlet的jar包 -> 创建lib文件夹并设置为依赖资源文件夹 -> 实现servlet接口的方法 -> 配置servlet -> 访问配置的url

1.1 下载jar包

推荐在Maven资源网站上下载,非常的全面。

1.2 创建lib并且设置为依赖资源文件夹

1.3 实现servlet方法

package cn.web.servlet;

import javax.servlet.*;
import java.io.IOException;

public class demo1 implements Servlet {

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

    }

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

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("hello servlet!");
    }

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

    @Override
    public void destroy() {

    }
}

1.4 配置servlet

web.xml<web-app>标签内添加:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>demo1</servlet-name>
        <servlet-class>cn.web.servlet.demo1</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>demo1</servlet-name>
        <url-pattern>/demo1</url-pattern>
    </servlet-mapping>
</web-app>

注意:servlet-name要对应上,servlet-class为实现的servlet类,url-pattern为我们访问该servlet的url

1.5 访问'http://localhost:8080/demo1'

我们会发现浏览器中是空白的,因为我们没有写相应的页面,我们只是在实现servlet时写了service方法,我们查看控制台发现:

终端输出了四次hello servlet!,因为我访问了四次。

到这里,servlet快速上手就已经完成啦!

2. Servlet生命周期

2.1 试验代码

package cn.web.servlet;

import javax.servlet.*;
import java.io.IOException;

public class demo2 implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("init....");
    }

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

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("service...");
    }

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

    @Override
    public void destroy() {
        System.out.println("destroy...");
    }
}

2.2 web.xml也要修改

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>demo1</servlet-name>
        <servlet-class>cn.web.servlet.demo1</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>demo1</servlet-name>
        <url-pattern>/demo1</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>demo2</servlet-name>
        <servlet-class>cn.web.servlet.demo2</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>demo2</servlet-name>
        <url-pattern>/demo2</url-pattern>
    </servlet-mapping>
</web-app>

2.3 控制台效果

2.4 总结

servlet 生命周期从初始化,提供服务到最终销毁分别对应的是:

初始化: init(默认情况下只在第一次访问时创建)

提供服务: service(每次访问都会执行)

销毁:destroy(服务器关闭才会执行)

3. 生命周期详解

3.1 init方法

修改web.xml <load-on-startup>可以指定第一次访问创建(负数)或者服务器启动创建(0或者正数)

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>demo1</servlet-name>
        <servlet-class>cn.web.servlet.demo1</servlet-class>
        <load-on-startup>-6</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>demo1</servlet-name>
        <url-pattern>/demo1</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>demo2</servlet-name>
        <servlet-class>cn.web.servlet.demo2</servlet-class>
        <load-on-startup>6</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>demo2</servlet-name>
        <url-pattern>/demo2</url-pattern>
    </servlet-mapping>
</web-app>

因为init只执行一次,所以能够说明servlet是一个单例,会遇到问题:

  • 多个用户同时访问,会出现线程安全问题
  • 尽量定义局部变量,如果定义了成员变量,最好不要有修改它的功能

3.2 service方法

每次访问都执行一次

3.3 destroy方法

只有服务器正常关闭才会执行,并且会在服务器关闭前执行(因为如果已经关闭了,那么就没法执行该方法了)

4. 注解配置方式

4.1 注解配置解释

servlet3.0以上才可以使用注解配置

在类的开头上一行注解配置 @WebServlet(urlPatterns = "url路径")

优势:不再需要在web.xml里去写配置项,方便快捷

package cn.web.servlet;

import javax.jws.WebService;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;

@WebServlet(urlPatterns = "/demo1")
public class demo1 implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("init1....");
    }

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

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("service1...");
    }

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

    @Override
    public void destroy() {
        System.out.println("destroy1...");
    }
}

4.2 注解配置和xml配置对比

<servlet>
        <servlet-name>demo1</servlet-name>
        <servlet-class>cn.web.servlet.demo1</servlet-class>
        <load-on-startup>-6</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>demo1</servlet-name>
        <url-pattern>/demo1</url-pattern>
    </servlet-mapping>

等价于

@WebServlet(urlPatterns = "url路径")

4.3 配置项详情(WebServlet.class源码)

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package javax.servlet.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
    String name() default "";

    String[] value() default {};

    String[] urlPatterns() default {};

    int loadOnStartup() default -1;

    WebInitParam[] initParams() default {};

    boolean asyncSupported() default false;

    String smallIcon() default "";

    String largeIcon() default "";

    String description() default "";

    String displayName() default "";
}

5. Servlet继承结构

我们在实现Servlet接口的时候,总是需要实现initdestroy等方法,即使我们并不需要在这些方法中实现什么功能,我们仅仅是需要在service方法中实现我们的主要功能。这个时候就有以下方便开发的子类来供我们使用。

Servlet(接口) ----> GenericServlet(抽象类) ----> HttpServlet(抽象类)

注:我们常使用HttpServlet

5.1 GenericServlet

GenericServlet继承自Servlet,对其他方法进行了空实现,只将service()方法作为抽象方法,之后定义Servlet时继承GenericServlet,实现Service()方法即可。

  • 部分源码:
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<String> 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();
        }
    }
}

注:我们可以看到,源码中将其他部分的方法都进行了空实现,比如destroy()等,但是把主要的service()方法进行了抽象,后期继承的话只需要实现service()方法即可。

5.2 HttpServlet

HttpServlet继承自GenericServlet,是对HTTP协议的一种封装,后期这个最常用的类,定义继承自HttpServlet然后实现doGet/doPost方法即可。

  • 部分源码:
public abstract class HttpServlet extends GenericServlet {

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_get_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(405, msg);
        } else {
            resp.sendError(400, msg);
        }

    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_post_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(405, msg);
        } else {
            resp.sendError(400, msg);
        }

    }
    
    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) {
                    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);
        }

    }

    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
            HttpServletRequest request = (HttpServletRequest)req;
            HttpServletResponse response = (HttpServletResponse)res;
            this.service(request, response);
        } else {
            throw new ServletException("non-HTTP request or response");
        }
    }
}
 

注:我们可以看到,HttpServlet实现了service()方法,并且根据请求的具体方法(GET/POST)来进行不同方法的选择。

6. Servlet路径配置

  1. 一个servlet可以配置多个路径:@WebServlet({"/xxx","/yyy"})
  2. 路径定义规则:
    • /XXX
    • /XXX/YYY
    • *.XXX

6.1 配置多个路径

@WebServlet({"/demo3","/test"})
public class demo3 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("demo3....get....");
        resp.getWriter().write("here is demo3 get()!");
    }
}

  • 输入路径http://localhost:8080/demo3
  • 输入路径http://localhost:8080/test

注:可以发现访问同一个资源

6.2 路径配置规则

路径定义规则: - /XXX - /XXX/YYY - *.XXX

@WebServlet({"/demo3","/demo3/test","*.xxx"})
public class demo3 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("demo3....get....");
        resp.getWriter().write("here is demo3 get()!");
    }
}

  • 输入地址:http://localhost:8080/demo3

  • 输入地址:http://localhost:8080/demo3/test

  • 输入地址:http://localhost:8080/asdasd.xxx