4.1 Servlet基础
4.1.1 Servlet概述
Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
4.1.2 Servlet特点
功能强大;可移植;性能高效;安全性高;可扩展
4.1.3 Servlet接口
Servlet接口有5
种方法。分别为:init,service和destroy是servlet的生命周期方法。这些方法由web容器调用。
方法 | 描述 |
---|---|
public void init(ServletConfig config) | 初始化servlet,它是servlet的生命周期方法,由web容器调用一次。 |
public void service(ServletRequest request,ServletResponse response) | 为传入的请求提供响应。它由Web容器的每个请求调用。 |
public void destroy() | 仅被调用一次,并且表明servlet正在被销毁。 |
public ServletConfig getServletConfig() | 返回ServletConfig对象。 |
public String getServletInfo() | 返回有关servlet的信息,如作者,版权,版本等。 |
1)HttpServlet 继承了 GenericServlet,并实现了 service 方法。在 service 方法中,将 ServletRequest 和 ServletResponse 转换为了 HttpServletRequest 和 HttpServletResponse,用来专门处理我们的 Http 请求。
2)Servlet(ServletRequest, ServletResponse) 方法在完成对请求和响应的强转之后调用了方法,在被调用的方法中对请求类型进行了判断,各种请求调用自己相应的 doXXX 方法。而我们常用的就是 doGet() 和 doPost() 方法。
3)在我们以后的使用中,都使用继承 HttpServlet 的方式,重写 doGet 和 doPost 方法即可。
在浏览器发送请求的时候,如果是 get 请求,将会调用 doGet()方法,如果是 post 请求,将会调用 doPost()方法。
4)继承 HttpServlet 的新的 Servlet 写法如下(web.xml 配置与之前相同)
4.2 Servlet开发入门
4.2.1 实现Servlet程序
@WebServlet( "/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Hello Servlet!");
}
}
4.2.2 Servlet的配置
1.使用web.xml配置Servlet
对 webapps\servletDemo\WEB-INF 目录的 web.xml 中进行配置,具体配置代码如下。
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>net.biancheng.www.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/MyServlet</url-pattern> <!--/url-pattern 地址前面要加上/--!>
</servlet-mapping>
web.xml 中各元素含义及用法如下:
-
: 根元素。
-
:用于注册 Servlet,即给 Servlet 起一个独一无二的名字。
-
包含两个主要的子元素 和 ,分别用于指定 Servlet 的名称和 Servlet 的完整限定名(包名+类名)。
-
:用于定义 Servlet 与 URL 之间的映射。
-
包含两个子元素 和 ,分别用于指定 Servlet 的名称和虚拟路径。
2.使用@WebServlet注解配置Servlet
属性名 | 类型 | 标签 | 描述 |
---|---|---|---|
name | String | 指定 Servlet 的 name 属性。如果没有显式指定,则取值为该 Servlet 的完全限定名,即包名+类名。 | |
value | String[ ] | 该属性等价于 urlPatterns 属性,两者不能同时指定。如果同时指定,通常是忽略 value 的取值。 | |
urlPatterns | String[ ] | 指定一组 Servlet 的 URL 匹配模式。 | |
loadOnStartup | int | 指定 Servlet 的加载顺序。 | |
initParams | WebInitParam[ ] | 指定一组 Servlet 初始化参数。 | |
asyncSupported | boolean | 声明 Servlet 是否支持异步操作模式。 | |
description | String | 指定该 Servlet 的描述信息。 | |
displayName | String | 指定该 Servlet 的显示名。 |
@WebServlet("/MyServlet") 省略了 urlPatterns 属性名
@WebServlet(urlPatterns = "/MyServlet")。 完整的写法
注:多个属性之间用逗号隔开!!!!
4.2.3 Servlet的生命周期
![Servlet 生命周期](http://c.biancheng.net/uploads/allimg/210616/14224J192-0.png)
@WebServlet("/servlet")
public class ServletDemo extends GenericServlet{
@Override
public void init() throws ServletException {
System.out.println("Servlet 初始化啦!! init....");
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("Servlet 启动服务啦!! service....");
}
@Override
public void destroy() {
System.out.println("Servlet 消亡啦!! destroy....");
}
4.3 ServletConfig和ServletContext
4.3.1 ServletConfig接口
返回值类型 | 方法 | 功能描述 |
---|---|---|
String | getInitParameter(String name) | 根据初始化参数名 name,返回对应的初始化参数值。 |
Enumeration | getInitParameterNames() | 返回 Servlet 所有的初始化参数名的枚举集合,如果该 Servlet 没有初始化参数,则返回一个空的集合。 |
ServletContext | getServletContext() | 返回一个代表当前 Web 应用的 ServletContext 对象。 |
String | getServletName() | 返回 Servlet 的名字,即 web.xml 中 元素的值。 |
@WebServlet(urlPatterns = "/servletDemo01",
initParams = {@WebInitParam(name="encoding",value="utf-8")})
public class servletDemo01 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取ServletConfig对象
ServletConfig config = this.getServletConfig();
//获取Servlet的名字
String name = config.getServletName();
//获取ServletContext对象
ServletContext context = config.getServletContext();
//获取初始化参数
String encoding = config.getInitParameter("encoding");
resp.getWriter().write("name : "+name+"\tcontext : "+context+"\tencoding : "+encoding);
}
}
4.3.2 ServletContext接口
1.获取Web应用程序的初始化参数
<context-param>
<param-name>city</param-name>
<param-value>anhui</param-value>
</context-param>
<context-param>
<param-name>subject</param-name>
<param-value>java web</param-value>
</context-param>
@WebServlet(urlPatterns = "/servletDemo02",
initParams = {@WebInitParam(name="encoding",value="utf-8")})
public class servletDemo02 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取Web应用的初始化参数
//单个getInitParameter
ServletContext context = this.getServletContext();
String city = context.getInitParameter("city");
String subject = context.getInitParameter("subject");
resp.getWriter().write(" city : "+city+" subject: "+subject);
//多个getInitParameterNames
Enumeration<String> names = context.getInitParameterNames();
while (names.hasMoreElements()){
String name = names.nextElement();
String parameter = context.getInitParameter(name);
resp.getWriter().write(name+" : "+parameter);
}
}
}
2.实现多个Servlet对象共享数据
@WebServlet(urlPatterns = "/servletDemo02",
initParams = {@WebInitParam(name="encoding",value="utf-8")})
public class servletDemo02 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//实现多个Servlet对象数据共享
//获取ServletContext对象
ServletContext context = this.getServletContext();
//通过setAttribute()设置属性值
context.setAttribute("data","this servlet save data");
}
}
@WebServlet(urlPatterns = "/servletDemo03",
initParams = {@WebInitParam(name="encoding",value="utf-8")})
public class servletDemo03 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//实现多个Servlet对象数据共享
//获取ServletContext对象
ServletContext context = this.getServletContext();
//通过getAttribute()获取属性值
String data = (String) context.getAttribute("data");
resp.getWriter().write(data);
}
}
3.读取Web应用下的资源文件
xiao.properties
Company=sanLian
Address=heFei
@WebServlet(urlPatterns = "/servletDemo04",
initParams = {@WebInitParam(name="encoding",value="utf-8")})
public class servletDemo04 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取ServletContext对象
ServletContext context = this.getServletContext();
//获取文件路径
InputStream is = context.getResourceAsStream("/WEB-INF/xiao.properties");
Properties properties = new Properties();
properties.load(is);
String company = properties.getProperty("Company");
String address = properties.getProperty("Address");
resp.getWriter().write("company : "+company+" address: "+address);
}
}
4.4 HttpServletResponse 接口
4.4.1 发送状态码相关的方法
-
public void setStatus ( int statusCode )
该方法设置一个任意的状态码。setStatus 方法接受一个 int(状态码)作为参数。如果您的响应包含了一个特殊的状态码和文档,请确保在使用 PrintWriter 实际返回任何内容之前调用 setStatus。
-
public void sendRedirect(String url)
该方法生成一个 302 响应,连同一个带有新文档 URL 的 Location 头。 -
public void sendError(int code, String message)
该方法发送一个状态码(通常为 404),连同一个在 HTML 文档内部自动格式化并发送到客户端的短消息。
4.4.2 发送响应消息头相关的方法
方法 & 描述 |
---|
void setHeader(String name, String value) 设置一个带有给定的名称和值的响应报头。 |
void addHeader(String name, String value) 添加一个带有给定的名称和值的响应报头。 |
void setIntHeader(String name, int value) 设置一个带有给定的名称和整数值的响应报头。 |
void addIntHeader(String name, int value) 添加一个带有给定的名称和整数值的响应报头。 |
void addCookie(Cookie cookie) 把指定的 cookie 添加到响应。 |
void setLocale(Locale loc) 如果响应还未被提交,设置响应的区域。 |
void setCharacterEncoding(String charset) 设置被发送到客户端的响应的字符编码(MIME 字符集)例如,UTF-8。 |
void setContentType(String type) 如果响应还未被提交,设置被发送到客户端的响应的内容类型。 |
4.4.3 发送响应消息体相关的方法
返回值类型 | 方法 | 描述 |
---|---|---|
ServletOutputStream | getOutputStream() | 用于获取字节输出流对象。 |
PrintWriter | getWriter() | 用于获取字符输出流对象。 |
4.5 HttpServletResponse应用
4.5.1实现请求重定向
HttpServletResponse 接口中的 sendRedirect() 方法用于实现重定向。
![重定向流程图](http://c.biancheng.net/uploads/allimg/210627/112RMB0-0.png)
方法 | 描述 |
---|---|
void sendRedirect(String location) | 向浏览器返回状态码为 302 的响应结果,让浏览器访问新的 URL。若指定的 URL 是相对路径,Servlet 容器会将相对路径转换为绝对路径。参数 location 表示重定向的URL。 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>welcome</title>
</head>
<body>
欢迎您登录成功!!!
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<form action="/LoginServlet" method="post">
用户名: <input type="text" name="username" /><br />
密码: <input type="password" name="password" /><br />
<input type="submit" name="登录" value="登录" />
</form>
</body>
</html>
@WebServlet(name = "LoginServlet",urlPatterns = "/LoginServlet")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
//用HttpServletRequest获取账号,密码
String username = req.getParameter("username");
String password = req.getParameter("password");
//假设密码并登录
if (("zhangsan").equals(username) && ("123").equals(password)){
//如果账号密码正确,则重定向到welcome
resp.sendRedirect("/welcome.html");
}else{
//如果账号密码错误,则定向到login
resp.sendRedirect("/login.html");
}
}
}
4.5.2 解决中文乱码问题
1. 使用字节流输出中文
使用字节流向页面输出中文是否会出现乱码问题?不一定。
ServletOutputStream outptuStream = response.getOutputStream();
outputStream.write(“字节流”.getBytes());
乱码原因:
字节流输出中文是否出现乱码,取决于中文转成字节数组时与浏览器打开时采用的字符集是否一致。若两者保持一致,则不会出现乱码问题,若不一致就会出现乱码问题。
解决方案:
将中文转成字节数组时和浏览器默认采用的字符集保持一致即可,代码如下。
response.setHeader("Content-Type", "text/html;charset=UTF-8");
// 获取字节输出流
OutputStream os = response.getOutputStream();
byte[] str = "字节流".getBytes("UTF-8");
// 输出中文
os.write(str);
2. 使用字符流输出中文
使用字符流向页面输出中文是否会出现乱码问题?一定乱码。
乱码原因:
通过字符流输出的内容是存放在 response 缓冲区的,response 缓冲区的默认字符集是 ISO-8859-1,该字符集不支持中文。
解决方案:
将 response 缓冲区和浏览器采用的字符集保持一致即可,有如下 2 种的方式。
第一种方式:
// 设置response缓冲区的编码
response.setCharacterEncoding("UTF-8");
// 设置浏览器打开文件所采用的编码
response.setHeader("Content-Type", "text/html;charset=UTF-8");
// 输出中文
response.getWriter().write("字符流");
第二种方式:
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("字符流");
4.6 HttpServletRequest对象
4.6.1 获取请求行信息的相关方法
方法声明 | 描述 |
---|---|
String getMethod() | 该方法用于获取 HTTP 请求方式(如 GET、POST 等)。 |
String getRequestURI() | 该方法用于获取请求行中的资源名称部分,即位于 URL 的主机和端口之后,参数部分之前的部分。 |
String getQueryString() | 该方法用于获取请求行中的参数部分,也就是 URL 中“?”以后的所有内容。 |
String getContextPath() | 返回当前 Servlet 所在的应用的名字(上下文)。对于默认(ROOT)上下文中的 Servlet,此方法返回空字符串""。 |
String getServletPath() | 该方法用于获取 Servlet 所映射的路径。 |
String getRemoteAddr() | 该方法用于获取客户端的 IP 地址。 |
String getRemoteHost() | 该方法用于获取客户端的完整主机名,如果无法解析出客户机的完整主机名,则该方法将会返回客户端的 IP 地址。 |
@WebServlet("/RequestLine")
public class RequestLine extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.println("请求方式:" + request.getMethod() + "<br/>" +
"客户端的 IP 地址:" + request.getRemoteAddr() + "<br/>" +
"应用名字(上下文):" + request.getContextPath() + "<br/>" +
"URI:" + request.getRequestURI() + "<br/>" +
"请求字符串:" + request.getQueryString() + "<br/>" +
"Servlet所映射的路径:" + request.getServletPath() + "<br/>" +
"客户端的完整主机名:" + request.getRemoteHost() + "<br/>"
);
}
}
![requestLine](http://c.biancheng.net/uploads/allimg/210627/11035213b-1.png)
4.6.2 获取请求头信息的相关方法
方法声明 | 描述 |
---|---|
String getHeader(String name) | 该方法用于获取一个指定头字段的值。如果请求消息中包含多个指定名称的头字段,则该方法返回其中第一个头字段的值。 |
Enumeration getHeaders(String name) | 该方法返回指定头字段的所有值的枚举集合,在多数情况下,一个头字段名在请求消息中只出现一次,但有时可能会出现多次。 |
Enumeration getHeaderNames() | 该方法返回请求头中所有头字段的枚举集合。 |
String getContentType() | 该方法用于获取 Content-Type 头字段的值。 |
Int getContentLength() | 该方法用于获取 Content-Length 头字段的值 。 |
String getCharacterEncoding() | 该方法用于返回请求消息的字符集编码 。 |
@WebServlet("/RequestHeader")
public class RequestHeader extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
//获得所有请求头字段的枚举集合
Enumeration<String> headers = request.getHeaderNames();
while (headers.hasMoreElements()) {
//获得请求头字段的值
String value = request.getHeader(headers.nextElement());
writer.write(headers.nextElement() + ":" + value + "<br/>");
}
}
}
![requestHeader](http://c.biancheng.net/uploads/allimg/210627/1103525F2-2.png)
4.6.3 请求转发
Servlet 可以通过 2 种方式获得 RequestDispatcher 对象:
- 调用 ServletContext 的 getRequestDispatcher(String path) 方法,参数 path 指定目标资源的路径,必须为绝对路径;
- 调用 ServletRequest 的 getRequestDispatcher(String path) 方法,参数 path 指定目标资源的路径,可以为绝对路径,也可以为相对路径。
RequestDispatcher 接口中提供了以下方法。
方法 | 功能描述 |
---|---|
void forward(ServletRequest request,ServletResponse response) | 用于将请求转发给另一个 Web 资源。该方法必须在响应提交给客户端之前被调用,否则将抛出 IllegalStateException 异常 |
void include(ServletRequest request,ServletResponse response) | 用于将其他的资源作为当前响应内容包含进来 |
请求转发的工作原理
在 Servlet 中,通常使用 forward() 方法将当前请求转发给其他的 Web 资源进行处理。请求转发的工作原理如下图所示。
![请求转发流程图](http://c.biancheng.net/uploads/allimg/210627/11105U1R-0.png)
请求转发的特点
请求转发具有以下特点:
- 请求转发不支持跨域访问,只能跳转到当前应用中的资源。
- 请求转发之后,浏览器地址栏中的 URL 不会发生变化,因此浏览器不知道在服务器内部发生了转发行为,更无法得知转发的次数。
- 参与请求转发的 Web 资源之间共享同一 request 对象和 response 对象。
- 由于 forward() 方法会先清空 response 缓冲区,因此只有转发到最后一个 Web 资源时,生成的响应才会被发送到客户端。
4.6.4 获取请求参数
方法声明 | 功能描述 |
---|---|
String getParameter(String name) | 返回指定参数名的参数值。 |
String [ ] getParameterValues (String name) | 以字符串数组的形式返回指定参数名的所有参数值(HTTP 请求中可以有多个相同参数名的参数)。 |
Enumeration getParameterNames() | 以枚举集合的形式返回请求中所有参数名。 |
Map getParameterMap() | 用于将请求中的所有参数名和参数值装入一个 Map 对象中返回。 |
@WebServlet("/RequestParam")
public class RequestParam extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
// 获取内容,做其他操作
// 获取姓名
String username = request.getParameter("username");
// 获取密码
String password = request.getParameter("password");
writer.write("用户名:" + username + "<br/>" + "密码:" + password +<br/>");
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="/httpServletRequestDemo/RequestParam" method="post">
<table border="1" width="50%">
<tr>
<td colspan="2" align="center">编程帮wwww.biancheng.net</td>
</tr>
<tr>
<td>输入姓名</td>
<td><input type="text" name="username" /></td>
</tr>
<tr>
<td>输入密码</td>
<td><input type="password" name="password" /></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="提交" /></td>
</tr>
</table>
</form>
</body>
</html>
4.6.5 通过Request对象传递数据
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//将数据保存到request对象的属性域中
request.setAttribute("attrName", "attrValueInRequest");
//两个Servlet要想共享request对象中的数据,必须是转发的关系
request.getRequestDispatcher("/ReceiveServlet").forward(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//从request属性域中获取数据
Object attribute = request.getAttribute("attrName");
System.out.println("attrValue="+attribute);
}
4.6.6解决中文乱码问题
根据请求方式的不同,请求一般可以被分为两种:GET 请求和 POST 请求。这两种请求方式都可能会产生中文乱码问题,下面我们分别对它们产生乱码的原因及其解决方案进行介绍。
POST 请求
乱码的原因:POST 提交的数据在请求体中,其所使用的编码格式时页面一致(即 utf-8)。request 对象接收到数据之后,会将数据放到 request 缓冲区,缓冲区的默认字符集是 ISO-8859-1(该字符集不支持中文),两者使用的字符集不一致导致乱码。
解决方案:在获取请求参数之前设置 request 缓冲区字符集为 utf-8 ,代码如下。
//修改request缓冲区的字符集为UTF-8
request.setCharacterEncoding("utf-8");
// 获取用户名
String username = request.getParameter("username");
GET 请求
乱码的原因:Get 请求将请求数据附加到 URL 后面作为参数,浏览器发送文字时采用的编码格式与页面编码保持一致(utf-8)。如果 Tomcat 没有设置字符集,接收 URL 时默认使用 ISO-8859-1 进行解码,ISO-8859-1 不兼容中文,无法正确解码,导致出现乱码。
需要注意的是,在 Tomcat 8 中已解决了 get 方式提交请求中文乱码的问题,使用 Tomcat 8 及以上版本的同学不必再考虑此问题了,如果您使用的是 Tomcat 7 或更早的版本,出现乱码问题可以使用如下的方案解决。
解决方案:解决 GET 请求中文乱码问题,有以下 3 种解决方案。
- 修改 tomcat/conf/server.xml 中的配置,代码如下。
<Connector port="80" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" URIEncoding="UTF-8"/>
- 使用 URLEncoder 和 URLDecoder 进行编码和解码的操作(逆向编解码)。
//得到TOMCAT通过ISO8859-1解码的字符串
String username = request.getParameter("username");
//对字符串使用ISO8859-1进行编码,得到最初浏览器使用UTF-8编码的字符串
username = URLEncoder.encode(username, "ISO8859-1");
//将使用UTF-8编码的字符串使用UTF-8进行解码,得到正确的字符串
username = URLDecoder.decode(username, "UTF-8");
- 使用 String 的构造方法:String(byte[] bytes, String charset) ,对字节数组(bytes)按照指定的字符集(charset)进行解码,返回解码后的字符串,解决乱码问题(推荐使用)。
//获取姓名
String username = request.getParameter("username");
//使用String的构造方法解决乱码的问题
username = new String(username.getBytes("ISO-8859-1"),"UTF-8");