摘要:本文学习了Servlet的基本概念,以及如何使用Servlet技术开发Web应用。
环境
Windows 10 企业版 LTSC 21H2
Java 1.8
Tomcat 8.5.50
1 简介
1.1 背景
早期的Web应用主要用于浏览新闻等静态页面,用户通过HTTP协议请求服务器上的静态页面,服务器上的应用接收到请求后,读取URI标识的资源,再加上消息报头发送给客户端浏览器,浏览器负责解析响应,将结果呈现出来。
随着时间发展,用户已经不满足于仅浏览静态页面。用户需要一些交互操作,获取一些动态结果。基于HTTP协议开发服务端应用太过复杂,所以需要一些扩展机制来实现用户想要的功能。早期使用的Web服务器扩展机制是CGI(Common Gateway Interface,公共网关接口)。用户点击链接或输入网址来访问CGI程序,Web服务器收到请求后,运行该CGI程序,对用户请求进行处理,然后将处理结果响应给Web服务器,Web服务器对响应进行包装,以HTTP响应的方式返回给浏览器。
CGI程序在一定程度上解决了用户需求,不过还存在一些不足之处,比如CGI程序编写困难,响应时间较长,以进程方式运行导致性能受限。于是1997年,SUN公司推出了Servlet技术,作为Java阵营的CGI解决方案。
1.2 定义
Servlet(Server Applet,服务端应用程序)是运行在Web服务器或应用服务器上的Java程序,作为Web客户端(比如浏览器)请求与服务器上的数据库或应用程序之间的中间层。
Servlet程序是JavaWeb三大组件之一,其他两个是Listener监听器和Filter过滤器。
1.3 对比
Servlet与CGI的区别:
- 运行方式:Servlet是运行在服务器上的Java程序,以线程方式运行,而CGI是运行在客户端的程序,以进程方式运行。
- 数据处理:Servlet可以直接处理HTTP请求,而CGI需要通过解析HTTP请求才能获取用户数据。
- 状态保持:Servlet可以保持状态,而CGI难以保持请求间的状态。
- 可移植性:Servlet可以运行在任何支持Java的服务器上,而CGI只能运行在特定的服务器上。
2 生命周期
2.1 创建
当Servlet首次被请求时,会调用init()方法创建Servlet实例。该方法只执行一次,用于加载资源和建立数据库连接等一次性设置。
在服务器启动时创建Servlet实例的两种方式:
- 在
web.xml文件中配置<load-on-startup>标签。
- 在
@WebServlet注解中配置loadOnStartup属性,只有Servlet版本在3.0以上才支持。
2.2 服务
当Servlet被请求时,会调用service()方法处理请求。该方法会被多次调用,用于处理用户请求。
根据请求类型不同,Servlet会自动调用相应的处理方法。
2.3 销毁
当Servlet不再被请求时,会调用destroy()方法销毁Servlet实例。该方法只执行一次,用于释放资源和关闭数据库连接等一次性清理。
3 技术体系
3.1 实现方式
3.1.1 Servlet接口
Servlet接口是最底层的接口,所有Servlet都必须实现该接口。
实现javax.servlet.Servlet接口是最基础的方式。
需要实现全部抽象方法:
TestServlet.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class TestServlet implements Servlet { @Override public void init(ServletConfig config) { System.out.println("init ..."); }
@Override public void service(ServletRequest req, ServletResponse res) { System.out.println("service ..."); }
@Override public void destroy() { System.out.println("destroy "); }
@Override public ServletConfig getServletConfig() { return null; }
@Override public String getServletInfo() { return null; } }
|
3.1.2 GenericServlet类
GenericServlet类实现了Servlet类,并且实现大部分业务无关的方法,GenericServlet类的子类只需要实现service()方法。
继承javax.servlet.GenericServlet类是最通用的方式,适用于非HTTP协议的场景。
简化接口实现,只需要实现service()方法:
TestServlet.java1 2 3 4 5 6
| public class TestServlet extends GenericServlet { @Override public void service(ServletRequest req, ServletResponse res) { System.out.println("service ..."); } }
|
3.1.3 HttpServlet类
HttpServlet类继承了GenericServlet类,并且进一步实现了service()方法,根据请求方法调用HTTP专属的请求处理方法,子类只需要重写处理方法。
继承javax.servlet.http.HttpServlet类是最常用的方式,适用于HTTP协议的场景。
只需要重写与请求方法对应的处理方法,最常用的是doGet()方法和doPost()方法:
TestServlet.java1 2 3 4 5 6 7 8 9 10 11
| public class TestServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) { System.out.println("doGet ..."); }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse res) { System.out.println("doPost ..."); } }
|
3.2 核心接口
3.2.1 ServletConfig接口
ServletConfig接口用于获取Servlet的配置信息,包括名称、初始化参数、上下文对象等。
在创建Servlet时创建,在销毁Servlet时销毁。
常用方法:
java1 2 3 4 5 6 7 8
| String getServletName();
ServletContext getServletContext();
String getInitParameter(String name);
Enumeration<String> getInitParameterNames();
|
3.2.2 ServletContext接口
ServletContext接口代表整个应用,在所有客户端之间共享数据。
在应用启动时创建,在应用关闭时销毁。
常用方法:
java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| String getServletContextName();
String getServerInfo();
String getContextPath();
String getRealPath(String path);
Set<String> getResourcePaths(String path);
InputStream getResourceAsStream(String path);
RequestDispatcher getRequestDispatcher(String path);
Object getAttribute(String name);
Enumeration<String> getAttributeNames();
void setAttribute(String name, Object object);
void removeAttribute(String name);
|
3.2.3 ServletRequest接口
ServletRequest接口用于获取请求信息,包括请求方法、请求路径、请求参数、请求头等。
每次请求时创建,请求结束时销毁。
常用方法:
java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| String getParameter(String name);
String[] getParameterValues(String name);
Enumeration<String> getParameterNames();
Map<String, String[]> getParameterMap();
String getProtocol();
String getScheme();
String getServerName();
int getServerPort();
ServletContext getServletContext();
String getRemoteAddr();
String getRemoteHost();
int getRemotePort();
String getLocalAddr();
String getLocalName();
int getLocalPort();
Object getAttribute(String name);
Enumeration<String> getAttributeNames();
void setAttribute(String name, Object value);
void removeAttribute(String name);
String getCharacterEncoding();
void setCharacterEncoding(String charset) throws UnsupportedEncodingException;
String getContentType();
ServletInputStream getInputStream() throws IOException;
BufferedReader getReader() throws IOException;
RequestDispatcher getRequestDispatcher(String path);
|
3.2.4 ServletResponse接口
ServletResponse接口用于设置响应信息,包括响应状态、响应头、响应体等。
每次请求时创建,请求结束时销毁。
常用方法:
java1 2 3 4 5 6 7 8 9 10 11 12
| String getCharacterEncoding();
void setCharacterEncoding(String charset);
String getContentType();
void setContentType(String type);
ServletOutputStream getOutputStream() throws IOException;
PrintWriter getWriter() throws IOException;
|
3.2.5 HttpServletRequest接口
HttpServletRequest接口继承了ServletRequest接口,并进一步实现了HTTP专属的请求信息。
新增的常用方法:
java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| String getMethod();
Cookie[] getCookies();
HttpSession getSession();
String getContextPath();
String getServletPath();
String getPathInfo();
String getRequestURI();
StringBuffer getRequestURL();
String getQueryString();
String getAuthType();
String getRemoteUser();
boolean isUserInRole(String role);
Principal getUserPrincipal();
void authenticate(HttpServletResponse response) throws IOException, ServletException;
void login(String username, String password) throws ServletException;
void logout() throws ServletException;
Part getPart(String name) throws IOException, ServletException;
Collection<Part> getParts() throws IOException, ServletException;
String getHeader(String name);
Enumeration<String> getHeaderNames();
|
3.2.6 HttpServletResponse接口
HttpServletResponse接口继承了ServletResponse接口,并进一步实现了HTTP专属的响应信息。
每次请求时创建,请求结束时销毁。
新增的常用方法:
java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void addCookie(Cookie cookie);
int getStatus();
void setStatus(int sc);
void sendError(int sc, String msg) throws IOException;
void sendError(int sc) throws IOException;
void sendRedirect(String location) throws IOException;
void setHeader(String name, String value);
void addHeader(String name, String value);
String getHeader(String name);
Collection<String> getHeaders(String name);
Collection<String> getHeaderNames();
|
3.2.7 RequestDispatcher接口
RequestDispatcher接口用于将请求资源交由指定路径处理。
获取方式:
java1 2 3 4
| RequestDispatcher rd = context.getRequestDispatcher("/target");
RequestDispatcher rd = request.getRequestDispatcher("target");
|
常用方法:
java1 2 3 4
| void forward(ServletRequest, ServletResponse);
void include(ServletRequest, ServletResponse);
|
4 配置文件
在Web应用根目录下创建WEB-INF目录,在目录中创建web.xml文件,用于声明Web应用的组件规则和全局配置,以及运行时行为。
4.1 全局参数
配置全局上下文参数,需要通过ServletContext对象获取:
web.xml1 2 3 4
| <context-param> <param-name>contextParam</param-name> <param-value>全局参数</param-value> </context-param>
|
4.2 欢迎页面
配置欢迎页面,按照顺序查找:
web.xml1 2 3 4
| <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.jsp</welcome-file> </welcome-file-list>
|
4.3 错误页面
配置错误页面:
web.xml1 2 3 4 5 6 7 8
| <error-page> <error-code>404</error-code> <location>/error/404.html</location> </error-page> <error-page> <error-code>500</error-code> <location>/error/500.html</location> </error-page>
|
5 配置组件
5.1 基于文件
在配置文件中配置Servlet组件:
web.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <servlet> <servlet-name>TestServlet</servlet-name> <servlet-class>com.example.servlet.TestServlet</servlet-class> <init-param> <param-name>initParam</param-name> <param-value>局部参数</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>TestServlet</servlet-name> <url-pattern>/test</url-pattern> </servlet-mapping>
|
5.2 基于注解
使用@WebServlet注解代替在文件中配置Servlet组件:
TestServlet.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @WebServlet( name = "TestServlet", urlPatterns = "/test", initParams = { @WebInitParam(name = "initParam", value = "局部参数") }, loadOnStartup = 1 ) public class TestServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { System.out.println("contextParam: " + getServletContext().getInitParameter("contextParam")); System.out.println("initParam: " + getServletConfig().getInitParameter("initParam")); System.out.println("username: " + req.getParameter("username")); PrintWriter out = res.getWriter(); out.write(req.getParameter("username")); out.flush(); out.close(); } }
|
6 简单使用
目录结构:
code1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 项目根目录 ├── src │ └── main │ ├── java │ │ └── com │ │ └── example │ │ └── servlet │ │ └── TestServlet.java │ ├── resources │ │ └── db.properties │ └── webapp │ ├── error │ │ ├── 404.html │ │ └── 500.html │ ├── WEB-INF │ │ └── web.xml │ └── index.html └── pom.xml
|
导入依赖:
pom.xml1 2 3 4 5 6
| <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency>
|
配置文件:
web.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| <?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_3_1.xsd" version="3.1">
<context-param> <param-name>contextParam</param-name> <param-value>全局参数</param-value> </context-param>
<welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.jsp</welcome-file> </welcome-file-list>
<error-page> <error-code>404</error-code> <location>/error/404.html</location> </error-page> <error-page> <error-code>500</error-code> <location>/error/500.html</location> </error-page>
<servlet> <servlet-name>TestServlet</servlet-name> <servlet-class>com.example.servlet.TestServlet</servlet-class> <init-param> <param-name>initParam</param-name> <param-value>局部参数</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>TestServlet</servlet-name> <url-pattern>/test</url-pattern> </servlet-mapping>
</web-app>
|
后端代码:
TestServlet.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class TestServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { System.out.println("contextParam: " + getServletContext().getInitParameter("contextParam")); System.out.println("initParam: " + getServletConfig().getInitParameter("initParam")); System.out.println("username: " + req.getParameter("username")); res.setContentType("text/html;charset=UTF-8"); PrintWriter out = res.getWriter(); out.write(req.getParameter("username")); out.flush(); out.close(); } }
|
前端代码:
index.html1 2 3 4 5 6 7 8 9 10 11
| <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>测试</title> </head> <body> <a href="test?username=lisi">测试</a> </body> </html>
|
7 进阶技巧
7.1 请求转发
服务器收到请求后,将请求转发给另一个Servlet处理,然后将响应返回给客户端。
特点:
- 地址栏没有变化。
- 属于同一次请求,请求数据可以共享。
- 可以转发到WEB-INF目录下的资源,不能转发到其他应用中的资源。
- 路径以
/开头,表示相对于当前应用的路径,不需要拼接当前应用。
转发请求:
TestServlet.java1 2 3 4 5 6 7 8 9 10
| @WebServlet("/test") public class TestServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { System.out.println("username: " + req.getParameter("username")); req.getRequestDispatcher("/testForward").forward(req, res); } }
|
处理转发:
TestForwardServlet.java1 2 3 4 5 6 7 8 9 10
| @WebServlet("/testForward") public class TestForwardServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { System.out.println("username: " + req.getParameter("username")); res.getWriter().println(req.getParameter("username")); } }
|
7.2 响应重定向
服务器收到请求后,将响应状态码设置为302,并将Location头设置为新的URL,客户端收到响应后会自动向新的URL发送请求。
特点:
- 地址栏会发生变化。
- 属于两次请求,请求数据不能共享。
- 不能重定向到WEB-INF目录下的资源,可以重定向到其他应用中的资源。
- 路径以
/开头,表示相对于当前服务器的路径,需要拼接当前应用。
重定向响应:
TestServlet.java1 2 3 4 5 6 7 8 9 10
| @WebServlet("/test") public class TestServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { System.out.println("username: " + req.getParameter("username")); res.sendRedirect("testRedirect"); } }
|
处理重定向:
TestRedirectServlet.java1 2 3 4 5 6 7 8 9 10
| @WebServlet("/testRedirect") public class TestRedirectServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { System.out.println("username: " + req.getParameter("username")); res.getWriter().println(req.getParameter("username")); } }
|
条