跳转至

Servlet

990 个字 73 行代码 预计阅读时间 6 分钟

为了简化Java EE的开发,Servlet应运而生。在 JavaEE 平台上,处理 TCP 连接,解析 HTTP 协议这些底层工作由 Web 服务器完成,Servlet 则是运行在 Web 服务器上的程序,负责处理 HTTP 请求,生成 HTTP 响应。Servlet 通过 Servlet API Web 服务器进行交互。

Servlet 通常情况下与使用 CGI(Common Gateway Interface,公共网关接口)实现的程序有异曲同工的效果,但 Servlet 具有很多优势:

  1. 性能显著优于 CGI
  2. Servlet Web 服务器的地址空间内执行,不需要单独创建进程处理每个请求。
  3. Servlet 是独立于平台的,因为它们是用 Java 编写的。
  4. 服务器上的 Java 安全管理器执行了一系列限制,以保护服务器计算机上的资源。因此,Servlet 是可信的。
  5. Java 类库的全部功能对 Servlet 来说都是可用的。它可以通过 sockets RMI 机制与 applets、数据库或其他软件进行交互。
block-beta
    columns 3
    space:2 A["Myservlet"]
    space:2 B["Servlet API"]
    Browser blockArrowId<["HTTP"]>(x) C["Web Server"]

Servlet 基础

通常情况下定义 Servlet 需要继承javax.servlet.http.HttpServlet类并重写doXXX ( doGet、doPost ) 方法或者service方法,重写HttpServlet类的service方法可以获取到上述七种 Http 请求方法的请求。也可以继承GenericServlet或者自己实现Servlet接口。

javax.servlet.http.HttpServlet类继承于javax.servlet.GenericServlet,而GenericServlet又实现了javax.servlet.Servletjavax.servlet.ServletConfig

javax.servlet.Servlet接口中只定义了servlet基础生命周期方法:init(初始化)getServletConfig(配置)service(服务)destroy(销毁), HttpServlet不仅实现了servlet的生命周期并通过封装service方法抽象出了doGet/doPost/doDelete/doHead/doPut/doOptions/doTrace方法用于处理来自客户端的不一样的请求方式。

Example
package cc.servlet;

import java.io.IOException;
import java.io.PrintWriter;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

// WebServlet注解表示这是一个Servlet,并映射到地址/
@WebServlet(urlPatterns = "/")
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        PrintWriter pw = resp.getWriter();
        pw.write("<h1>Hello, world!(Servlet Test)</h1>");
        pw.flush();
    }
}
运行后访问http://localhost:8080/即可看到Hello, world!(Servlet Test)alt text

Servlet 的打包类型为 war,表示 Java Web Application Archive。普通的 java 程序通过启动 JVM,执行 jar 包中的 main 方法,而 war 则需要部署到 Web 服务器中,由 Web 服务器加载。常用的支持 Servlet API Web 服务器有 Tomcat、Jetty、WebLogic、WebSphere 等。

一个 Webapp 可以有多个 Servlet,分别映射不同的路径。例如:

@WebServlet(urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
    ...
}

@WebServlet(urlPatterns = "/signin")
public class SignInServlet extends HttpServlet {
    ...
}

@WebServlet(urlPatterns = "/")
public class IndexServlet extends HttpServlet {
    ...
}

alt text

浏览器会根据路由访问不同的 Servlet,这种功能被称为 dispatch。值得注意的是,\ 实际匹配所有路径\*

多线程问题

一个 Servlet 类在服务器中只有一个实例,但对于每个 HTTP 请求,Web 服务器会使用多线程执行请求。因此,一个 Servlet doGet()doPost() 等处理请求的方法是多线程并发执行的。如果 Servlet 中定义了字段,要注意多线程并发访问的问题

重定向与转发

Redirect Servlet 可以通过 HttpServletResponse#sendRedirect() 发送 302 重定向,使浏览器重新请求一个新的 URL。

// 将 /hi 重定向到 /hello
@WebServlet(urlPatterns = "/hi")
public class RedirectServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 构造重定向的路径:
        String name = req.getParameter("name");
        String redirectToUrl = "/hello" + (name == null ? "" : "?name=" + name);
        // 发送重定向响应:
        resp.sendRedirect(redirectToUrl);
    }
}

如果要实现 301 永久重定向,可以使用HttpServletResponse#setStatus()方法设置状态码为 301

resp.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
resp.setHeader("Location", "/hello");

Forwward

Forward 指内部转发,即 Servlet 在处理请求时,将请求转发给另一个 Servlet 处理。

@WebServlet(urlPatterns = "/morning")
public class ForwardServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("/hello").forward(req, resp);
    }
}

对于 Web 应用程序来说,我们总是通过 HttpSession 这个高级接口访问当前 Session。可以认为 Web 服务器在内存中自动维护了一个 ID HttpSession 的映射表,我们可以用下图表示: alt text

Servlet 容器会在第一次调用request.getSession()时自动创建一个 SessionID,服务器将其通过JSSESIONID这个 Cookie 发送给浏览器。浏览器在后续的请求中会自动发送这个 Cookie,服务器就可以根据这个 Cookie 找到对应的 Session

除了上述提到的 JSESSIONID 外,还可以通过getCookies()

Filter

JavaEE Servlet 规范提供了 Filter 组件,即过滤器,它的作用是,在 HTTP 请求到达 Servlet 之前,可以被一个或多个 Filter 预处理,类似打印日志、登录检查等逻辑。

Example
@WebFilter("/user/*")
public class AuthFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("AuthFilter: check authentication");
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        if (req.getSession().getAttribute("user") == null) {
            // 未登录,自动跳转到登录页:
            System.out.println("AuthFilter: not signin!");
            resp.sendRedirect("/signin");
        } else {
            // 已登录,继续处理:
            chain.doFilter(request, response);
        }
    }
}

Servlet 规范并没有对 @WebFilter 注解标注的 Filter 规定顺序。如果一定要给每个 Filter 指定顺序,就必须在 web.xml 文件中对这些 Filter 进行配置


最后更新: 2024年8月16日 12:44:21
创建日期: 2024年8月16日 12:44:21

评论