Day58
1.Servlet的生命周期
创建:第一次发送给该Servlet请求时
调用:构造方法、init()
销毁:服务器正常关闭
调用:destroy()
Welcome.html
没有明确写出是什么请求,那就是get请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>欢迎页面</h1>
<a href="ser01">向Servlet01发送请求</a>
</body>
</html>
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">
<welcome-file-list> <!--设置首页-->
<welcome-file>Welcome.html</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>Servlet01</servlet-name>
<servlet-class>com.qf.Servlet.Servlet01</servlet-class>
<init-param> <!--初始化参数-->
<param-name>code</param-name>
<param-value>UTF-8</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Servlet01</servlet-name>
<url-pattern>/ser01</url-pattern>
</servlet-mapping>
</web-app>
Servlet01
public class Servlet01 extends HttpServlet {
//构造方法
public Servlet01() {
System.out.println("Servlet01 -- Servlet01()");
}
//初始化方法
@Override
public void init() throws ServletException {
//获取该Servlet的配置文件对象
ServletConfig servletConfig = this.getServletConfig();
//获取配置数据
String code = servletConfig.getInitParameter("code");
System.out.println("Servlet01 -- init() -- " + code);
}
/**
* 客户端发送给Servlet的是get请求,就调用doGet方法
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet01 -- doGet()");
}
/**
* 客户端发送给Servlet的是post请求,就调用doPost方法
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet01 -- doPost()");
}
/**
* 销毁方法
1.调用时机:内存释放或者服务器关闭的时候,Servlet对象会被销毁,调用
2.调用次数: 1次
*/
@Override
public void destroy() {
System.out.println("Servlet01 -- destroy()");
}
}
Servlet生命周期中涉及到的三个方法,这三个方法是什么?什么时候被调用?调用几次?
涉及到三个方法,分别是 init()、service()、destroy()
1.init方法在Servlet对象被创建的时候执行,只执行1次
2.service方法在Servlet被访问的时候调用,每访问1次就调用1次
3.destroy方法在Servlet对象被销毁的时候调用,只执行1次
2.深入生命周期(重要)
理解Servlet调用图
1.加载和实例化:默认情况下,当Servlet第一次被访问时,由Servlet容器创建Servlet对象
2.初始化:在Servlet创建对象后,容器将调用Servlet的init()方法初始化这个对象,完成一些如加载配置文件等初始化的工作。该方法只调用一次
3.请求处理:每次请求Servlet时,Servlet容器都会调用Servlet的service()方法对请求进行处理
4.服务终止:当需要释放内存或者服务器关闭时,就会调用Servlet实例的destroy()方法完成资源的释放。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收
Servlet的生命周期分为5个阶段:加载、创建、初始化、处理客户请求、卸载
流程:
Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
1.Web服务器首先检查是否已经存在并创建了该Servlet的实例对象。如果是,则直接执行第4步,否则,执行第2步。
2.创建该Servlet的一个实例对象。
3.调用Servlet实例对象的init()方法。(已经创建好Servlet对象)
4.创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
5.WEB应用程序被停止或重新启动之前,调用Servlet的destroy()方法完成资源的释放。
针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象,也就是说Servlet实例对象一旦创建,它就会留在内存中,为后续的其它请求服务,直至web容器退出,servlet实例对象才会销毁。
在Servlet的整个生命周期内,Servlet的init方法只被调用一次。
而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。
注意:前端发送GET和POST请求的时候,参数的位置不一致,GET请求参数在请求行中,POST请求参数在请求体中,为了能处理不同的请求方式,我们得在service方法中进行判断
面试题:Servlet何时被创建?
1.第一次发送给该Servlet请求时会创建Servlet对象
2.在web.xml里 配置了1(数字从1开始,越小优先级越高),项目启动时会创建Servlet对象
3.在@WebServlet 配置了loadOnStartup = 1时会创建Servlet对象
方式一:
<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>Servlet01</servlet-name>
<servlet-class>com.qf.servlet.Servlet01</servlet-class>
<init-param>
<param-name>code</param-name>
<param-value>UTF-8</param-value>
</init-param> -->
//这句话
<load-on-startup>1</load-on-startup>
</servlet> -->
<servlet-mapping> -->
<servlet-name>Servlet01</servlet-name>
<url-pattern>/ser01</url-pattern>
</servlet-mapping>
<servlet>
</web-app>
方式二:
@WebServlet(value = "/ser01",initParams = {@WebInitParam(name="code",value="UTF-8")},loadOnStartup = 1)
public class Servlet01 extends HttpServlet {
public Servlet01() {
System.out.println("Servlet01 -- Servlet01()");
}
/**
* 初始化方法
*/
@Override
public void init() throws ServletException {
//获取该Servlet的配置文件对象
ServletConfig servletConfig = this.getServletConfig();
//获取配置数据
String code = servletConfig.getInitParameter("code");
System.out.println("Servlet01 -- init() -- " + code);
}
/**
* 客户端发送给Servlet的是get请求,就调用doGet方法
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet01 -- doGet()");
}
/**
* 客户端发送给Servlet的是post请求,就调用doPost方法
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet01 -- doPost()");
}
/**
* 销毁方法
*/
@Override
public void destroy() {
System.out.println("Servlet01 -- destroy()");
}
}
3.Servlet线程安全问题
出现原因:servlet 对象在 tomcat 服务器是单实例多线程的。 因为 servlet 是多线程的,所以当多个客户端访问同一个Servlet中的资源时,如成员变量,有可能会出现线程安全问题
面试题:Servlet是否是单例的?
不是单例,但是一般情况下是单例,如果Servlet实现了SingleThreadModel接口,该Servlet对象在第一次线程阻塞时会创建新的对象 – 已过时
解决方案:
1.将Servlet实现SingleThreadModel(已过时),因为当线程阻塞,就会创建新的Servlet对象
2.加锁、使用线程安全的类
把使用到共享数据的代码块进行同步(使用 synchronized 关键字进行同步)
经验:尽可能的不要使用成员变量,而是使用局部变量
package com.qf.servlet;
@WebServlet("/ser01")
//public class Servlet01 extends HttpServlet implements SingleThreadModel {
public class Servlet01 extends HttpServlet {
public Servlet01() {
System.out.println("Servlet01被创建了");
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
private int num;//成员变量,类中方法外面的变量
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
synchronized (this){
num++;
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
response.getWriter().println(num);
}
}
}
4.页面跳转
4.1 页面 跳 页面
三种:超链接;window.location;表单提交
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>页面 跳 页面</h1>
<a href="page01.html">跳转到页面1</a><br/>
<button onclick="fun01()">跳转到页面1</button><br/>
<form action="page01.html" method="post">
<input type="submit" value="跳转到页面1"/>
</form><br/>
<script type="text/javascript">
function fun01(){
window.location = "page01.html";
}
</script>
</body>
</html>
page01.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>页面1</h1>
</body>
</html>
4.2 页面 跳 Servlet
三种:超链接;window.location;表单提交
注意:get请求可以在访问路径上加上数据包
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>页面 跳 Servlet</h1>
<a href="ser01?username=hhy&password=123123&nickName=千锋彭于晏">跳转到Servlet01</a><br/><!--get请求-->
<button onclick="fun02()">跳转到Servlet01</button><br/><!--get请求-->
<form action="ser01" method="post"><!--可选请求方式-->
账号:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
昵称:<input type="text" name="nickName"/><br/>
<input type="submit" value="跳转到Servlet01"/>
</form><br/>
<script type="text/javascript">
function fun02(){
window.location = "ser01?username=hhy&password=123123&nickName=千锋彭于晏";
}
</script>
</body>
</html>
Servlet01
路径:@WebServlet(“/ser01”)
package com.qf.servlet;
@WebServlet("/ser01")
public class Servlet01 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
String nickName = request.getParameter("nickName");
System.out.println("Servlet01接收到来自客户端的请求了:" + username + " -- " + password + " -- " + nickName);
response.getWriter().println(username + " -- " + password + " -- " + nickName);
}
}
4.3 Servlet 跳 Servlet
两种方式:
转发方式:request.getRequestDispatcher(“ser03”).forward(request,response);
重定向方式:response.sendRedirect(“ser05”);
Welcome.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Servlet 跳 Servlet</h1>
<p>转发方式:Servlet02 -> Servlet03</p>
<p>浏览器地址栏输入:http://localhost:8080/Day18_03_war_exploded/ser02</p>
<p>重定向方式:Servlet04 -> Servlet05</p>
<p>浏览器地址栏输入:http://localhost:8080/Day18_03_war_exploded/ser04</p>
<hr/>
</body>
</html>
Servlet02
@WebServlet("/ser02")
public class Servlet02 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//转发方式
request.getRequestDispatcher("ser03").forward(request,response);
}
}
Servlet04
@WebServlet("/ser04")
public class Servlet04 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//重定向方式
response.sendRedirect("ser05");
}
}
4.4 Servlet 跳 页面
两种方式:
转发方式:request.getRequestDispatcher(“page02.html”).forward(request,response);
重定向方式:response.sendRedirect(“page03.html”);
和上面一样的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Servlet 跳 页面</h1>
<p>转发方式:Servlet06 -> page02.html</p>
<p>浏览器地址栏输入:http://localhost:8080/Day18_03_war_exploded/ser06</p>
<p>重定向方式:Servlet07 -> page03.html</p>
<p>浏览器地址栏输入:http://localhost:8080/Day18_03_war_exploded/ser07</p>
<hr/>
</body>
</html>
Servlet06
@WebServlet("/ser06")
public class Servlet06 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//转发方式
request.getRequestDispatcher("page02.html").forward(request,response);
}
}
Servlet07
@WebServlet("/ser07")
public class Servlet07 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//重定向方式
response.sendRedirect("page03.html");
}
}
5.转发和重定向的区别
1.转发:一种在服务器内部的资源跳转方式。
转发:req.getRequestDispatcher(“资源B路径”).forward(req,resp);
2.重定向: response.sendRedirect(“资源B的访问路径”);
注意:因为浏览器发送了两次请求,是两个不同的request对象,就无法通过request对象进行共享数据,所以使用response
5.1 转发和重定向的区别
区别一:发送请求次数的区别
转发发送1次请求,浏览器地址路径不会发生改变
重定向发送2次请求,浏览器地址路径会发生改变
区别二:访问外部服务器资源
转发不能访问外部资源(因为服务器不能访问其他的服务器)
重定向可以访问外部资源(因为本服务器通过响应告诉客户端重新向其他服务器发送请求)
eg:response.sendRedirect(“https://www.baidu.com”);
区别三:访问受保护的页面(WEB-INF/page02.html)
转发可以访问受保护的页面(因为转发是服务器内部的跳转)
重定向不可以访问受保护的页面(因为客户端不能直接访问受保护文件夹里的资源)
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/ser01")
public class Servlet01 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/**
* 知识点:转发和重定向的区别
*/
//区别一:发送请求次数的区别
//注意:转发发送1次请求
//request.getRequestDispatcher("page01.html").forward(request,response);//http://localhost:8080/Day18_04_war_exploded/ser01
//注意:重定向发送2次请求
//response.sendRedirect("page01.html");//http://localhost:8080/Day18_04_war_exploded/page01.html
//区别二:访问外部资源
//注意:转发不能访问外部资源(因为服务器不能访问其他的服务器)
//request.getRequestDispatcher("https://www.baidu.com").forward(request,response);
//注意:重定向可以访问外部资源(因为本服务器通过响应告诉客户端重新向其他服务器发送请求)
//response.sendRedirect("https://www.baidu.com");
//区别三:访问受保护的页面
//注意:转发可以访问受保护的页面(因为转发是服务器内部的跳转)
//request.getRequestDispatcher("WEB-INF/page02.html").forward(request,response);
//注意:重定向不可以访问受保护的页面(因为客户端不能直接访问受保护文件夹里的资源)
response.sendRedirect("WEB-INF/page02.html");
}
}
6.中文乱码问题
设置请求和响应的编码格式
request.setCharacterEncoding(“UTF-8”);
response.setContentType(“text/html;charset=UTF-8”);
关注页面名字是中文的情况:
response.sendRedirect(URLEncoder.encode(“页面1.html”,“UTF-8”));
WebServlet("/ser01")
public class Servlet01 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置请求和响应的编码格式
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
//跳转中文页面
response.sendRedirect(URLEncoder.encode("页面1.html","UTF-8"));
}
}
总结
1.Servlet的生命周期 – 重要
2.Servlet线程安全问题
3.页面跳转 – 重要
4.转发和重定向的区别 – 重要
1.发送请求次数
2.访问外部服务器资源
3.访问受保护的资源
5.中文乱码问题