Servlet,Request,Response概述

Servlet,Request,Response概述 一、Servlet 1.1 简介 Servlet是JavaWeb最为核心的内容,它是Java提供的一门动态web资源开发技术。 使用Servlet就可以实现,根据不同的登录用户在页面上动态显示不同内容。 Servlet是JavaEE规范之一,其实

Servlet,Request,Response概述

一、Servlet

1.1 简介

image-wavw.png

  • Servlet是JavaWeb最为核心的内容,它是Java提供的一门动态web资源开发技术。
  • 使用Servlet就可以实现,根据不同的登录用户在页面上动态显示不同内容。
  • Servlet是JavaEE规范之一,其实就是一个接口,将来我们需要定义Servlet类实现Servlet接口,并由web服务器运行Servlet

1.2 执行流程

image-sllc.png

  • 览器发出http://localhost:8080/web-demo/demo1请求,从请求中可以解析出三部分内容,分别是localhost:8080web-demodemo1
    • 根据localhost:8080可以找到要访问的Tomcat Web服务器
    • 根据web-demo可以找到部署在Tomcat服务器上的web-demo项目
    • 根据demo1可以找到要访问的是项目中的哪个Servlet类,根据@WebServlet后面的值进行匹配
  • 找到ServletDemo1这个类后,Tomcat Web服务器就会为ServletDemo1这个类创建一个对象,然后调用对象中的service方法
    • ServletDemo1实现了Servlet接口,所以类中必然会重写service方法供Tomcat Web服务器进行调用
    • service方法中有ServletRequest和ServletResponse两个参数,ServletRequest封装的是请求数据,ServletResponse封装的是响应数据,后期我们可以通过这两个参数实现前后端的数据交互

1.3 生命周期

  • 生命周期: 对象的生命周期指一个对象从被创建到被销毁的整个过程。
  • Servlet运行在Servlet容器(web服务器)中,其生命周期由容器来管理,分为4个阶段:
  1. 加载和实例化:默认情况下,当Servlet第一次被访问时,由容器创建Servlet对象
  2. 初始化:在Servlet实例化之后,容器将调用Servlet的==init()==方法初始化这个对象,完成一些如加载配置文件、创建连接等初始化的工作。该方法只==调用一次==
  3. 请求处理:==每次==请求Servlet时,Servlet容器都会调用Servlet的==service()==方法对请求进行处理
  4. 服务终止:当需要释放内存或者容器关闭时,容器就会调用Servlet实例的==destroy()==方法完成资源的释放。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收
默认情况,Servlet会在第一次访问被容器创建,但是如果创建Servlet比较耗时的话,那么第一个访问的人等待的时间就比较长,用户的体验就比较差,那么我们能不能把Servlet的创建放到服务器启动的时候来创建,具体如何来配置?

@WebServlet(urlPatterns = "/demo1",loadOnStartup = 1)
loadOnstartup的取值有两类情况
	(1)负整数:第一次访问时创建Servlet对象
	(2)0或正整数:服务器启动时创建Servlet对象,数字越小优先级越高

1.4 相关方法

Servlet中总共有5个方法

  1. 初始化方法,在Servlet被创建时执行,只执行一次
    void init(ServletConfig config)
  2. 提供服务方法, 每次Servlet被访问,都会调用该方法
    void service(ServletRequest req, ServletResponse res)
  3. 销毁方法,当Servlet被销毁时,调用该方法。在内存释放或服务器关闭时销毁Servlet
    void destroy()
  4. 获取Servlet信息
    String getServletInfo()
  5. 获取ServletConfig对象:ServletConfig对象,在init方法的参数中有,而Tomcat Web服务器在创建Servlet对象的时候会调用init方法,必定会传入一个ServletConfig对象,我们只需要将服务器传过来的ServletConfig进行返回即可。
    ServletConfig getServletConfig()

1.5 体系结构

image-cpwh.png
因为我们将来开发B/S架构的web项目,都是针对HTTP协议,所以我们自定义Servlet,会通过继承HttpServlet

1.6 urlPattern配置

Servlet类编写好后,要想被访问到,就需要配置其访问路径(urlPattern
@WebServlet(urlPatterns = {"/demo1","/demo2"})

1.6.1 urlPattern配置规则

  1. 精确匹配
    image-lohi.png
  2. 目录匹配
    image-mahj.png
  3. 扩展名匹配
    image-uufw.png

注意:
如果路径配置的不是扩展名,那么在路径的前面就必须要加/否则会报错
如果路径配置的是*.do,那么在*.do的前面不能加/,否则会报错

  1. 任意匹配
    image-rwec.png
    注意://*的区别?
  2. 当我们的项目中的Servlet配置了 "/",会覆盖掉tomcat中的DefaultServlet,当其他的url-pattern都匹配不上时都会走这个Servlet
  3. 当我们的项目中配置了"/*",意味着匹配任意访问路径
  4. DefaultServlet是用来处理静态资源,如果配置了"/"会把默认的覆盖掉,就会引发请求静态资源的时候没有走默认的而是走了自定义的Servlet类,最终导致静态资源不能被访问

1.7 XML配置

前面对应Servlet的配置,我们都使用的是@WebServlet,这个是Servlet从3.0版本后开始支持注解配置,3.0版本前只支持XML配置文件的配置方法。
对于XML的配置步骤有两步:

  • 编写Servlet类
package com.itheima.web;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;

public class ServletDemo13 extends MyHttpServlet {

    @Override
    protected void doGet(ServletRequest req, ServletResponse res) {

        System.out.println("demo13 get...");
    }
    @Override
    protected void doPost(ServletRequest req, ServletResponse res) {
    }
}
  • 在web.xml中配置该Servlet
<?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">
    
    
    
    <!-- 
        Servlet 全类名
    -->
    <servlet>
        <!-- servlet的名称,名字任意-->
        <servlet-name>demo13</servlet-name>
        <!--servlet的类全名-->
        <servlet-class>com.itheima.web.ServletDemo13</servlet-class>
    </servlet>

    <!-- 
        Servlet 访问路径
    -->
    <servlet-mapping>
        <!-- servlet的名称,要和上面的名称一致-->
        <servlet-name>demo13</servlet-name>
        <!-- servlet的访问路径-->
        <url-pattern>/demo13</url-pattern>
    </servlet-mapping>
</web-app>

二、Request

2.1 Request继承体系

Request的继承体系:
image-ozxm.png
从上图中可以看出,ServletRequest和HttpServletRequest都是Java提供的
所以ServletRequest和HttpServletRequest是继承关系,并且两个都是接口,接口是无法创建对象,这个时候就引发了下面这个问题:
image-uewm.png
这个时候,我们就需要用到Request继承体系中的RequestFacade:

  • 该类实现了HttpServletRequest接口,也间接实现了ServletRequest接口。
  • Servlet类中的service方法、doGet方法或者是doPost方法最终都是由Web服务器[Tomcat]来调用的,所以Tomcat提供了方法参数接口的具体实现类,并完成了对象的创建
  • 要想了解RequestFacade中都提供了哪些方法,我们可以直接查看JavaEE的API文档中关于ServletRequest和HttpServletRequest的接口文档,因为RequestFacade实现了其接口就需要重写接口中的方法

2.2 Request获取请求数据

HTTP请求数据总共分为三部分内容,分别是请求行、请求头、请求体

2.2.1 获取请求行数据

请求行包含三块内容,分别是请求方式请求资源路径HTTP协议及版本
image-pdfm.png
对于这三部分内容,request对象都提供了对应的API方法来获取,具体如下:

  • 获取请求方式: GET
String getMethod()
  • 获取虚拟目录(项目访问路径): /request-demo
String getContextPath()
  • 获取URL(统一资源定位符): http://localhost:8080/request-demo/req1
StringBuffer getRequestURL()
  • 获取URI(统一资源标识符): /request-demo/req1
String getRequestURI()
  • 获取请求参数(GET方式): username=zhangsan&password=123
String getQueryString()

2.2.2 获取请求头数据

对于请求头的数据,格式为key: value如下:
image-jaix.png
所以根据请求头名称获取对应值的方法为:

String getHeader(String name)

2.2.3 获取请求体数据

浏览器在发送GET请求的时候是没有请求体的,所以需要把请求方式变更为POST,请求体中的数据格式如下:
image-wlmp.png
对于请求体中的数据,Request对象提供了如下两种方式来获取其中的数据,分别是:

  • 获取字节输入流,如果前端发送的是字节数据,比如传递的是文件数据,则使用该方法
ServletInputStream getInputStream()
该方法可以获取字节
  • 获取字符输入流,如果前端发送的是纯文本数据,则使用该方法
BufferedReader getReader()

注意:BufferedReader流是通过request对象来获取的,当请求完成后request对象就会被销毁,request对象被销毁后,BufferedReader流就会自动关闭,就不需要手动关闭流了。

2.2.4 获取请求参数的通用方式

对于请求参数的获取,常用的有以下两种:

  • GET方式:
String getQueryString()
  • POST方式:
BufferedReader getReader();

可以在doGet中调用doPost,在doPost中完成参数的获取和打印,另外需要注意的是,doGet和doPost方法都必须存在,不能删除任意一个。
GET请求和POST请求获取请求参数的方式不一样,在获取请求参数这块该如何实现呢?
解决方案一:

@WebServlet("/req1")
public class RequestDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取请求方式
        String method = req.getMethod();
        //获取请求参数
        String params = "";
        if("GET".equals(method)){
            params = req.getQueryString();
        }else if("POST".equals(method)){
            BufferedReader reader = req.getReader();
            params = reader.readLine();
        }
        //将请求参数进行打印控制台
        System.out.println(params);
      
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req,resp);
    }
}

使用request的getMethod()来获取请求方式,根据请求方式的不同分别获取请求参数值,这样就可以解决上述问题,但是以后每个Servlet都需要这样写代码,实现起来比较麻烦,这种方案我们不采用
解决方案二:
request对象已经将上述获取请求参数的方法进行了封装,并且request提供的方法实现的功能更强大,以后只需要调用request提供的方法即可,在request的方法中都实现了哪些操作?
(1)根据不同的请求方式获取请求参数,获取的内容如下:
image-ertz.png
(2)把获取到的内容进行分割,内容如下:
image-jnlb.png
(3)把分割后端数据,存入到一个Map集合中:
image-lvsi.png
注意:因为参数的值可能是一个,也可能有多个,所以Map的值的类型为String数组。
基于上述理论,request对象为我们提供了如下方法:

//获取所有参数Map集合
Map<String,String[]> getParameterMap()

//根据名称获取参数值(数组)
String[] getParameterValues(String name)

//根据名称获取参数值(单个值)
String getParameter(String name)

2.3 Request请求转发

2.3.1 简介

请求转发(forward):一种在服务器内部的资源跳转方式。
image-jwhl.png
(1)浏览器发送请求给服务器,服务器中对应的资源A接收到请求
(2)资源A处理完请求后将请求发给资源B
(3)资源B处理完后将结果响应给浏览器
(4)请求从资源A到资源B的过程就叫请求转发

2.3.2 请求转发的实现方式

req.getRequestDispatcher("资源B路径").forward(req,resp);

2.3.3 请求转发资源间共享数据

请求转发资源间共享数据:使用Request对象
需要使用request对象提供的三个方法:

//存储数据到request域[范围,数据是存储在request对象]中
void setAttribute(String name,Object o);

//根据key获取值
Object getAttribute(String name);

//根据key删除该键值对
void removeAttribute(String name);

可以实现在转发多个资源之间共享数据

2.3.4 请求转发的特点

  1. 浏览器地址栏路径不发生变化
    虽然后台从/req5转发到/req6,但是浏览器的地址一直是/req5,未发生变化
  2. 只能转发到当前服务器的内部资源
    不能从一个服务器通过转发访问另一台服务器
  3. 一次请求,可以在转发资源间使用request共享数据
    虽然后台从/req5转发到/req6,但是这个只有一次请求

三、Response对象

3.1 简介

image-yooc.png

  • Request:使用request对象来获取请求数据
  • Response:使用response对象来设置响应数据
    Reponse的继承体系和Request的继承体系也非常相似:
    image-bnhd.png

3.2 Response设置响应数据功能介绍

HTTP响应数据总共分为三部分内容,分别是响应行、响应头、响应体

3.2.1 响应行

image-ovtq.png
对于响应头,比较常用的就是设置响应状态码:

void setStatus(int sc);

3.2.2 响应头

image-vrwo.png
设置响应头键值对:

void setHeader(String name,String value);

3.2.3 响应体

image-xjiu.png
对于响应体,是通过字符、字节输出流的方式往浏览器写,

//获取字符输出流:
PrintWriter getWriter();

//获取字节输出流
ServletOutputStream getOutputStream();

3.3 Respones请求重定向

3.3.1 简介

Response重定向(redirect):一种资源跳转方式。
image-hris.png
(1)浏览器发送请求给服务器,服务器中对应的资源A接收到请求
(2)资源A现在无法处理该请求,就会给浏览器响应一个302的状态码+location的一个访问资源B的路径
(3)浏览器接收到响应状态码为302就会重新发送请求到location对应的访问地址去访问资源B
(4)资源B接收到请求后进行处理并最终给浏览器响应结果,这整个过程就叫重定向

3.3.2 重定向的实现方式

resp.setStatus(302);
resp.setHeader("location","资源B的访问路径");

//request对象给我们提供了简化的编写方式
resposne.sendRedirect("资源B的访问路径")

3.3.3 重定向的特点

  1. 浏览器地址栏路径发送变化
    当进行重定向访问的时候,由于是由浏览器发送的两次请求,所以地址会发生变化
  2. 可以重定向到任何位置的资源(服务内容、外部均可)
    因为第一次响应结果中包含了浏览器下次要跳转的路径,所以这个路径是可以任意位置资源。
  3. 两次请求,不能在多个资源使用request共享数据
    因为浏览器发送了两次请求,是两个不同的request对象,就无法通过request对象进行共享数据

3.3.4 请求重定向和请求转发对比

请求重定向请求转发 区别对比:
image-lovt.png

3.4 路径问题

问题1:转发的时候路径上没有加/request-demo而重定向加了,那么到底什么时候需要加,什么时候不需要加呢?
image-vnuh.png
其实判断的依据很简单,只需要记住下面的规则即可:

  • 浏览器使用:需要加虚拟目录(项目访问路径)
  • 服务端使用:不需要加虚拟目录
    对于转发来说,因为是在服务端进行的,所以不需要加虚拟目录
    对于重定向来说,路径最终是由浏览器来发送请求,就需要添加虚拟目录。

问题2:在重定向的代码中,/request-demo是固定编码的,如果后期通过Tomcat插件配置了项目的访问路径,那么所有需要重定向的地方都需要重新修改,该如何优化?
image-kfzk.png
答案也比较简单,我们可以在代码中动态去获取项目访问的虚拟目录,具体如何获取,我们可以借助前面咱们所学习的request对象中的getContextPath()方法,修改后的代码如下:

@WebServlet("/resp1")
public class ResponseDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("resp1....");

        //简化方式完成重定向
        //动态获取虚拟目录
        String contextPath = request.getContextPath();
        response.sendRedirect(contextPath+"/resp2");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

3.5 Response响应字符数据

要想将字符数据写回到浏览器,我们需要两个步骤:

  • 通过Response对象获取字符输出流: PrintWriter writer = resp.getWriter();
  • 通过字符输出流写数据: writer.write("aaa");
/**
 * 响应字符数据:设置字符数据的响应体
 */
@WebServlet("/resp3")
public class ResponseDemo3 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        //1. 获取字符输出流
        PrintWriter writer = response.getWriter();
		 writer.write("aaa");
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

注意:一次请求响应结束后,response对象就会被销毁掉,所以不要手动关闭流。

3.6 Response响应字节数据

要想将字节数据写回到浏览器,我们需要两个步骤:

  • 通过Response对象获取字节输出流:ServletOutputStream outputStream = resp.getOutputStream();
  • 通过字节输出流写数据: outputStream.write(字节数据);
  /**
 * 响应字节数据:设置字节数据的响应体
 */
@WebServlet("/resp4")
public class ResponseDemo4 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. 读取文件
        FileInputStream fis = new FileInputStream("d://a.jpg");
        //2. 获取response字节输出流
        ServletOutputStream os = response.getOutputStream();
        //3. 完成流的copy
        byte[] buff = new byte[1024];
        int len = 0;
        while ((len = fis.read(buff))!= -1){
            os.write(buff,0,len);
        }
        fis.close();
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

上述代码中,对于流的copy的代码还是比较复杂的,所以我们可以使用别人提供好的方法来简化代码的开发,具体的步骤是:
(1)pom.xml添加依赖

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

(2)调用工具类方法

//fis:输入流
//os:输出流
IOUtils.copy(fis,os);

优化后代码:

/**
 * 响应字节数据:设置字节数据的响应体
 */
@WebServlet("/resp4")
public class ResponseDemo4 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. 读取文件
        FileInputStream fis = new FileInputStream("d://a.jpg");
        //2. 获取response字节输出流
        ServletOutputStream os = response.getOutputStream();
        //3. 完成流的copy
      	IOUtils.copy(fis,os);
        fis.close();
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
LICENSED UNDER CC BY-NC-SA 4.0
Comment