Servlet介绍

716 阅读6分钟

什么是Servlet

Servlet是用Java编写的服务器端程序,大多情况下它被理解为任何实现Servlet接口的类。servlet接口定义的是一套处理网络请求的规范,所有实现servlet的类,都需要实现它的五个方法。

也就是说,所有想要处理网络请求的类,都需要思考三件事情:

(1)初始化时需要做什么?

(2)销毁时需要做什么

(3)接收到请求时需要做什么

不同于网络编程,我们不需要在servlet中编写 监听某端口的代码,也就是说,servlet不会直接和客户端打交道。那请求又是如何到达servlet的呢?答案是Tomcat。

什么是Tomcat

Tomcat是Web服务器和Servlet容器的结合体:Web服务器存放着网站文件等资源,可向Web客户端提供文档;而Servlet容器存放着Servlet对象。

当我们通过Web服务器映射的URL访问资源时,主要是三个过程:

  • 接收请求
  • 处理请求
  • 响应请求

接收和响应的步骤交由Web服务器处理,由于处理请求的逻辑(方式)是不同的,因此便通过Servlet来交给程序员编写。

当我们使用到Tomcat后,就不再需要写main方法,也不再需要new一个类,Tomcat通过web.xml将这些事情做完,

Servlet的五个方法

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest request, ServletResponse reponse) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

其中init、service、destroy是生命周期方法。init和destroy各自只执行一次,而service方法会在每次有新请求到来时被调用,我们的主要逻辑操作写在service中。

ServletConfig类

翻译的意思是Servlet配置,我们是在web.xml中配置的Servlet,因此ServletConfig对象包装了Servlet的一些参数信息。

Request/Response

当请求到达Tomcat后,Tomcat通过字符串解析,将各个请求头,请求地址,请求参数都封装进了Request对象中,我们通过此对象来获取发送的请求信息。

当Tomcat传送信息给Request时,Response对象是空的,我们可以逻辑处理后通过response.write()方法将结果写入response内部缓冲区。Tomcat拿到reponse里的信息后,组成响应发给客户端。

抽象类HttpServlet

浏览器发送请求有多种方式,最基本的是Get/Post,如果单单通过继承Servlet来编写逻辑代码,那么就需要根据不同的请求来编写代码。

为了简化这个过程,便有了HttpServlet抽象类:

public abstract class HttpServlet extends GenericServlet {
        ...
}

它继承自抽象类GenericServlet.

public abstract class GenericServlet implements Servlet, 
                                ServletConfig, Serializable {
                                
    private static final long serialVersionUID = 1L;
    private transient ServletConfig config;   
    
    //init 方法是随 Servlet 实例化而被调用的
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }
    public abstract void service(ServletRequest var1, ServletResponse var2) 
                                    throws ServletException, IOException;
    
    public void init() throws ServletException {}

相比于原本的Servlet,GenericServlet类做了一部分改动:

(1)原本是形参的ServletConfig对象变成了成员变量,这方便其他方法使用。

(2)init方法还调用了一个init空参方法,若我们希望在Servlet创建时做一些其他操作,可以继承自GenericServlet,覆盖init空参方法。

GenericServlet类并没有重写service方法,它的子类HttpServlet实现了service方法:

    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();    //请求方式method
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader("If-Modified-Since");
                } catch (IllegalArgumentException var9) {
                    ifModifiedSince = -1L;
                }

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

    }

HttpServlet类要求其子类重写doGet,doPost等方法,若没有重写则会显示405错误。

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

Servlet的生命周期

(1)加载:当Tomcat第一次访问Servlet的时候,Tomcat会负责创建Servlet的实例;

(2)初始化:当Servlet被实例化后,Tomcat会调用init()方法初始化对象

(3)处理:当浏览器访问Servlet时,Servlet会调用service()方法处理请求。

(4)销毁:当Tomcat关闭或检测到Servlet要从Tomcat删除时,会自动调用destroy()方法。

(5)卸载:当Servlet调用完destroy()方法后,等待垃圾回收。如果有需要再次使用这个Servlet,会重新调用init()方法进行初始化操作。

第一个Servlet程序

以IDEA2019.3.1为例:

创建完后结构大致如下:

在WEB-INF下右键NEW->Directory,创建classes和lib两个目录,分别用于存放编译后的class文件和存放依赖的jar包。

点击右上角的倒数第三个图标,进入 Project Structure窗口。

点击Modules ->选定项目->切换到Path选项,将Output path” 和 “Test output path” 都改为之前创建的classes目录,这样后面编译的class文件默认生成到classes目录下

切换到Dependencies 选项,然后点击右边的+号,选择JARs or directories,然后选择Jar Directory,并选择之前创建的lib目录。

点击右上角的Tomcat图标,如下图中的Tomcat8.5.3.5,然后点击Edit Configurations。

然后按下图配置:

在编写Servlet程序前可能还还需要引入Servlet的jar包,在下图中点击+选择Java,找到Tomcat安装目录,在lib目录下选择servlet-api.jar包即可。

最后是程序部分:

public class MyServlet extends HttpServlet {
    private String message;

    @Override
    public void init() throws ServletException {
        message = "C2y";
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        PrintWriter out = resp.getWriter();
        out.println("<h1>" + message + "</h1:>");
    }
}

在web.xml中的< web-app>之间添加。你可以在MyServlet 类前添加@WebServlet("/MServlet"),这样就不必在web.xml中配置了。

    <servlet>
        <servlet-name>MServlet</servlet-name>
        <servlet-class>MyServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>MServlet</servlet-name>
        <url-pattern>/MServlet</url-pattern>
    </servlet-mapping>

ServletContext

当Tomcat启动时,就会创建一个对应的ServletContext,它代表当前Web应用,所有Servlet共享一个ServletContext对象,因此Servlet之间可以通过ServletContext进行通讯。

ServletContext是一个域对象,它类似于HashMap,我们可以往里面存key-value,存入的方法是setAttribute(String name,Object obj)

 ServletContext sc = this.getServletContext();
 sc.setAttribute("name", "C2y")

取出数据:

 ServletContext sc = this.getServletContext();
 String val = (String)sc.getAttribute("name", "C2y")

参考资料

知乎:servlet的本质是什么,它是如何工作的? 答主:bravo1988