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.中文乱码问题



















