【JavaWeb】常用对象Request、Response、ServletConfig、ServletContext学习笔记

Request&Response的学习

  在服务方法中,存在这两个对象:Request主要用于获取请求数据Response主要用于设置响应数据,它们都是由Web服务器进行创建的。

示例

package com.hhxy.servlet;

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;
/**
 * Request&Response初体验
 */
@WebServlet("/ReTest")
public class ReTest extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1、使用Request对象获取请求数据
        String name = req.getParameter("name");//url?name=zhansan
        //2、使用Response对象设置响应数据
        resp.setHeader("content-type","text/html;charset=utf-8");//设置响应头的编码格式
        resp.getWriter().write("<h1>"+name+",欢迎您!</h1>");//返回的响应数据
    }
}

测试:

image-20220804145122305

一、Request学习

image-20220804143730543

1、获取请求数据

1.1 获取请求行数据

请求行包含三块内容,分别是请求方式请求资源路径HTTP协议及版本

1628748240075

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

示例

package com.hhxy.request;

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;

/**
 * 测试Request对象 获取请求行数据常用的5种方法
 */
@WebServlet("/req1")
public class RequestDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1、String getMethod():获取请求方式: GET
        String method = req.getMethod();
        System.out.println(method);//GET
        // 2、String getContextPath():获取虚拟目录(项目访问路径):/day7_servlet
        String contextPath = req.getContextPath();
        System.out.println(contextPath);
        // 3、StringBuffer getRequestURL(): 获取URL(统一资源定位符):http://localhost:8080/day7_servlet/req1
        StringBuffer url = req.getRequestURL();
        System.out.println(url.toString());
        // 4、String getRequestURI():获取URI(统一资源标识符): /day7_servlet/req1
        String uri = req.getRequestURI();
        System.out.println(uri);
        // 5、String getQueryString():获取请求参数(GET方式): username=zhangsan&password=123
        String queryString = req.getQueryString();
        System.out.println(queryString);
    }
}
1.2 获取请求头数据

对于请求头的数据,格式为key: value如下:

1628768652535

  • getHeader根据请求头的名称获取对应值的信息:

    String getHeader(String name)
    

    示例

    package com.hhxy.request;
    
    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;
    /**
     * 测试Request对象获取请求头数据的方法
     */
    @WebServlet("/req2")
    public class RequestDemo2 extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            String value = req.getHeader("user-agent");//获取请求头: user-agent: 浏览器的版本信息
            System.out.println(value);
        }
    }
    

    输出:

    image-20220804152946777

1.3 获取请求体数据

浏览器在发送GET请求的时候是没有请求体的,所以需要把请求方式变更为POST,请求体中的数据格式如下:

1628768665185

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

示例

编写Servlet:

package com.hhxy.request;

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.BufferedReader;
import java.io.IOException;

/**
 * 使用Request对象获取请求体数据
 */
@WebServlet("/req3")
public class RequestDemo3 extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1、获取Post请求的请求参数
        //1.1 获取字符输入流
        BufferedReader br = req.getReader();
        //1.2 读取数据
        String requestData = br.readLine();
        System.out.println(requestData);//输出:
    }
}

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

编写Post提交方式的表单:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Test</title>
</head>
<body>
<!--
    action:form表单提交的请求地址
    method:请求方式,指定为post
-->
<form action="http://localhost:8080/day7_servlet/req3" method="post">
    <input type="text" name="username">
    <input type="password" name="password">
    <input type="submit">
</form>
</body>
</html>

测试:

image-20220804154653945

1.4 获取请求参数

当我们在编码时,由于GET方式和POST方式的请求参数所处位置不同,这就意味着,我们在doGet方法种和doPost方法种都需要写一遍处理请求参数的代码,写两遍请求参数的代码很容易出现重复代码,为了去除重复代码,这就意味着我们需要使用一种统一的请求参数方式,从而只需要写一遍代码避免重复代码的产生(程序员就是"懒",我也要成为一名懒"猿"😆)

image-20220804160037944


拓展:

请求参数和请求数据的关系:请求参数是请求数据子集,请求参数只包括用户数据的数据,请求数据包括请求行、请求头、请求体

1.4.1 原始的请求参数方式
  • getQueryString:用于获取Get请求方式的请求参数

    String getQueryString()
    
  • getReader:用于获取Post请求方式的请求参数

    BufferedReader getReader()
    
1.4.2 请求参数的通用方式

在学习请求参数的通用方式之前,我们有必要先了解请求参数在Request对象中的存储原理!

  • Step1:Request对象会将客户端发送过来的请求参数进行分割

    image-20220804162002598

  • Step2:Request对象会将分割后的参数使用Map集合进行存储

    image-20220804162443936

  • getParameterMap:获取所有请求参数的Map集合

    Map<String,String[]> getParameterMap()
    
  • getParameterValues:获取指定名称的请求参数(数组)

    String[] getParameterValues(String name)
    
  • getParameter:获取指定名称的请求参数(单个值)

    String getParameter(String name)
    

示例

编写Servlet
编写html
测试

注意:要先启动Servlet,然后提交表单

Servlet:

package com.hhxy.request;

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;
import java.util.Map;

/**
 * 请求参数的通用方式
 */
@WebServlet("/req4")
public class RequestDemo4 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doGet方法被调用了...");
        //1、获取所有请求参数
        Map<String, String[]> maps = req.getParameterMap();//获取键
        for (String key : maps.keySet()) {//遍历所有键
            System.out.println(key+":");
            //获取值
            String[] values = maps.get(key);
            for (String value : values) {
                System.out.println(value+" ");
            }
            System.out.println();
        }
        System.out.println("===============================");
        //2、获取单个请求参数(数组)
        String[] hobbies = req.getParameterValues("hobby");
        for (String hobby : hobbies) {
            System.out.println("hobby:"+hobby);
        }
        System.out.println("================================");
        //3、获取单个请求参数(单个值)
        String hobby = req.getParameter("hobby");
        System.out.println("hobby:"+hobby);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doPost方法被调用了...");
        this.doGet(req,resp);//doPost方法和doGet方法共用一套代码,减少重复代码
    }
}

html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Test</title>
</head>
<body>
<!--
    action:form表单提交的请求地址
    method:请求方式,指定为post
-->
<form action="http://localhost:8080/day7_servlet/req4" method="get">
    <input type="text" name="username"><br>
    <input type="password" name="password"><br>
    <input type="checkbox" name="hobby" value="1"> 游泳
    <input type="checkbox" name="hobby" value="2"> 爬山 <br>
    <input type="submit">
</form>
</body>
</html>

测试:

image-20220804202457808


1.4.3 解决中文乱码

乱码原因

  • 编码:浏览器发送的请求数据都是先使用UTF-8编码,然后使用URL进行编码

    UTF-8编码规定编码的符号的占位,汉字是占3个字节(24位二进制数),英文占2个字节;

    URL编码是将一个字节(八位)用两个十六进制数组成,然后再前面加一个%

  • 解码:Tomcat先进行URL解码,然后再使用ISO-8859-1进行解码。

而在IDEA中所有代码的编码都默认使用UTF-8,所以当Servlet程序在接收到来自Tomcat解析后的数据,就直接使用UTF-8进行了编码,而来自Tomcat解析的数据都是使用ISOISO-8859-1解码的,就造成了乱码问题w(゚Д゚)w,示意图如下:

image-20220804224509479

代码演示

package com.hhxy.request;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

/**
 * 模拟请求数据的编码和解码过程
 */
public class URLDemo {

    public static void main(String[] args) throws Exception {
        String username = "张三";//模拟用户输入数据

        /*-------------------第一步:模拟浏览器编码-----------------------*/
        //1. URL编码
        String encode = URLEncoder.encode(username, "utf-8");
        System.out.println(encode);//输出:%E5%BC%A0%E4%B8%89
        /*---------------------------------------------------------------*/

        /*-------------------第二步:模拟Tomcat解码------------------------*/
        //2. URL解码
        String decode = URLDecoder.decode(encode, "ISO-8859-1");
        //备注:这一步在Tomcat内部是写死的,无法进行外部操作
        /*---------------------------------------------------------------*/

        /*-----------第三步:Servlet直接默认使用UTF-8进行编码-----------------*/
        System.out.println(decode); //此处打印的是对应的乱码数据:å¼ ä
        /*----------------------------------------------------------------*/

        /*------------------------------从第三步下手------------------------*/
        //3. 转换为字节数据,使用ISO-8859-1编码
        byte[] bytes = decode.getBytes("ISO-8859-1");
        for (byte b : bytes) {
            System.out.print(b + " ");
        }
        //此处打印的是:-27 -68 -96 -28 -72 -119
        //4. 将字节数组转为字符串,使用UTF-8解码
        String s = new String(bytes, "utf-8");
        System.out.println(s); //此处打印的是张三
    }
}

白学警告⚠在新版Tomcat9,已经默认使用UTF-8编码了,所以新版不存在乱码问题😆,本小节当作了解原理吧😄

  • Post请求方式乱码解决方案

    //在获取请求参数前,添加如下代码:
    req.setCharacterEncoding("UTF-8");
    

    原因:因为Post底层是使用BufferedReader getReader(),以BufferedReader流的形式的读取来自Tomcat解析的请求参数;Get底层是使用String getQueryString()String无法直接使用该方法设置编码的方案来解决乱码问题,需要手动转换编码。

  • Get/Post请求方式乱码解决方案

    //在获取请求参数前,添加如下代码;
    result = new String(value.getBytes(StandardCharsets.ISO_8859_1),StandardCharsets.UTF_8);
    

    备注这就是从底层出发,该方法是通用方法!

其实方式一的底层原理就是方式二,只是Post请求参数是BufferedReader流,能够直接调用封装好的方法罢了,而Get方法是字符串,没有直接封装好的方法使用(谁叫字符串太原始了呢🤭)

示例

备注:复用1.4.2的测试代码

package com.hhxy.request;

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;
import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * 请求参数的通用方式
 */
@WebServlet("/req4")
public class RequestDemo4 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doGet方法被调用了...");

        /*---------------解决Post请求方式导致的中文乱码问题-------------------*/
        //req.setCharacterEncoding("UTF-8");
        //注意:
        //1、使用了通用方式后,就不需要这行代码,否则会重复导致乱码
        //2、这行代码一定要放在获取请求参数之前!!!
        /*--------------------------------------------------------*/

        //1、获取所有请求参数
        Map<String, String[]> maps = req.getParameterMap();//获取键
        for (String key : maps.keySet()) {//遍历所有键
            System.out.print(key+":");
            //获取值
            String[] values = maps.get(key);
            for (String value : values) {
                System.out.println(value+" ");//原始输出,不进行乱码处理
                /*------------解决Get请求方式导致的乱码问题----------------*/
                //也能同时解决Post请求方式导致的乱码问题
                String result = null;
                /*byte[] bytes = value.getBytes(StandardCharsets.ISO_8859_1);
                result = new String(bytes,StandardCharsets.UTF_8);*/
                result = new String(value.getBytes(StandardCharsets.ISO_8859_1),StandardCharsets.UTF_8);
                System.out.println(result);
                /*--------------------------------------------------------*/
            }
            System.out.println();
        }
        System.out.println("===============================");
        //2、获取单个请求参数(数组)
        String[] hobbies = req.getParameterValues("hobby");
        for (String hobby : hobbies) {
            System.out.println("hobby:"+hobby);
        }
        System.out.println("================================");
        //3、获取单个请求参数(单个值)
        String hobby = req.getParameter("hobby");
        System.out.println("hobby:"+hobby);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doPost方法被调用了...");
        this.doGet(req,resp);
    }
}

image-20220805080303927

2、请求转发

请求转发(forward):是一种在服务器内部的资源跳转方式,本质就是利用Request域对象在Tomcat内部各Servlet进行传值,但是只能传递一次。

image-20220805081600416

请求转发的特点

  • 一次请求,数据只能转发一次
  • 转发只能发生在当前Web服务器内部
  • 发生转发浏览器地址栏不会发生变化,转发是发生在Web服务器内部
2.1 请求转发的实现方式
req.getRequestDispatcher("资源B的访问路径").forward(req,resp);
//只能使用这个语句进行一次转发,多次转发会报错

示例

image-20220805082627081
2.2 转发常用API

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

  • setAttribute:存储数据到request对象中
//存储的数据是 key value 形式,vlaue可以是任何类型
void setAttribute(String name,Object o);
  • getAttribute根据key获取值
Object getAttribute(String name);
  • removeAttribute根据key删除该键值对
void removeAttribute(String name);

示例

image-20220805084826091


image-20220805085101181

二、Response学习

image-20220805100602193

1、设置响应数据

1.1 设置响应行

响应行主要包括三部分,分别是协议状态码状态码描述

image-20220805100658010

对于响应头比较常用的就是设置响应状态码

  • setStatus:设置状态码

    void setStatus(int sc)
    
1.2 设置响应头

对于响应头的数据,格式为key: value如下:

image-20220805100854835

  • setHeader:设置响应头键值对:

    void setHeader(String name,String value)
    
1.3 设置响应体

响应体是返回给浏览器的数据,主要是html标签:

image-20220805100938727

对于响应体是通过字符字节输出流的方式往浏览器写

  • getWriter:获取字符输出流:

    PrintWriter getWriter()
    
  • getOutputStream:获取字节输出流

    ServletOutputStream getOutputStream()
    

2、Response重定向

重定向(Redirect):重定向是一种资源跳转方式,就是通过各种方法将各种网络请求重新定个方向转到其它位置(如:网页重定向、域名的重定向、路由选择的变化也是对数据报文经由路径的一种重定向)

image-20220805104121275

温馨提示可以去类比一下前面请求转发的资源跳转方式😄

重定向的特点

  • 两次请求,浏览器一次发送两次请求数据
  • 可以重定向到任何位置的资源,包括web服务器内部、外部
  • 发生重定向浏览器的地址栏会发生变化,地址栏会变成重定向后的资源访问路径

实现方式

  • 方式一:原始写法

    response.setStatus(302);
    resp.setHeader("location","资源B的访问路径");
    
  • 方式二:使用Sendredirect方法

    response.Sendredirect("资源B的访问路径");
    

备注:这里的资源B的访问路径可以使用 url 路径:http://localhost:8080/day7_servlet/ResponseDemo2,也可以使用精确匹配:/day7_servlet/ResponseDemo2

示例

image-20220805112045652

3、虚拟目录之资源路径问题

3.1 虚拟目录概述
  • 什么是虚拟目录

    虚拟目录是指通将物理目录进行映射(大白话就是起别名)创建的新目录。

    主要作用是进行互联网访问的,URL就是使用虚拟目录的

  • 相对物理目录虚拟目录的好处

    1. 便于访问、管理。虚拟目录能将分散的文件进行映射,放在分类明确的目录下,便于访问、管理
    2. 增强服务器的安全性。虚拟目录对用户隐藏了其在服务器上的物理位置,从而增强其安全性
    3. 提高目录容量。我们在使用物理目录时,想添加其它文件夹中文件,就需要进行CV;而使用虚拟目录,就可以直接通过映射添加到该文件夹中,从而无需创建新的文件或新的目录。
3.2 设置虚拟目录

问题:如何解决什么时候添加虚拟目录这个问题?

【虚拟目录如果不手动设置,默认就是斜杠+你的模块名

转发的时候路径上没有加虚拟目录:/day7_servlet,而重定向加了,那么到底什么时候需要加,什么时候不需要加呢?

image-20220805112704644

是否添加虚拟目录的判断方法

1)如果访问路径是给浏览器访问,就需要添加虚拟目录

2)如果访问路径是给Web服务器访问,就不需要添加虚拟目录

常见的情况

  1. 超链接<a href="路径">,从浏览器发送,需要加虚拟目录
  2. 表单<form action="路径">,从浏览器发送,需要加虚拟目录
  3. 请求转发,是从服务器内部跳转,不需要加虚拟目录
  4. 重定向,是由浏览器进行跳转,需要加虚拟目录

虽然我们知道了判断方法,但是每次判断起来还是很麻烦,那么有没有一种更加好的方式不需要进行判断就能直接使用路径呢?程序员的终极目的就是=>化繁为简🐩

  • 静态设置

    <!--给虚拟目录起别名-->
    <Path></Path>
    

    我们可以直接在添加Tomcat插件依赖时,通过path标签自定义虚拟目录

    示例:image-20220805160045911


    image-20220805144411704

这样就不用在意需不需要加虚拟目录了😃,但是这种方式存在缺陷,当我们的虚拟目录不止一个,且我们只想访问其中一个目录中的Servlet时,这种方式就Out了,因为存在硬编码,每次访问其它的目录都需要进行修改,所以需要一种更加简单的方式!

  • 动态获取

    String contextPath = request.getContextPath()
    

    动态获取会根据是谁发送,自动获取虚拟目录。如果是浏览器发送就自动添加虚拟目录,如果是Web服务器就不添加

    示例

    image-20220805161331059

4、Response响应字符&字节数据

4.1 响应字符数据

响应字符数据是使用Response对象的getWriter()方法:

PrintWriter pw = response.getWriter();//获取打印流
pw.write("Hello!");//输出

备注:writer流是随着response对象创建的,当请求完毕,response对象被销毁,writer流也跟着自动关闭

当响应数据是中文或者是html文本时,可以在前面添加以下代码:

//response.setHeader("contenx-type","text/html");//单独设置html文本
response.setContentType("text/html;charset=utf-8");//设置显示html文本,同时设置编码,防止中文乱码

中文乱码原因可以参考1.4.3

image-20220805185259554

4.2 响应字节数据

响应字符数据主要是使用getOutputStream()方法,用一个简单的案例来演示一下

案例

当浏览器向Web服务器请求数据时,Servlet将本地的照片用字节输入流读取,读取完毕后,拷贝到ServletOutputStream对象中,然后将响应数据发送给浏览器展示。

如果你对字节流的操作不眼熟了,推荐阅读:

往期回顾:JavaSE基础【回顾+总结】的15节IO流

导入依赖
编写Servlet
测试

目录结构:

image-20220805210847890

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <!--项目坐标-->
    <artifactId>day7_servlet</artifactId>
    <groupId>com.hhxy</groupId>
    <version>1.0-SNAPSHOT</version>
    <modelVersion>4.0.0</modelVersion>
    <packaging>war</packaging>
    <!--项目所依赖的JDK版本-->
    <properties>
        <maven.compiler.source>16</maven.compiler.source>
        <maven.compiler.target>16</maven.compiler.target>
    </properties>

    <!--导入项目的依赖-->
    <dependencies>
        <!--导入javax.servlet-api-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <!--
              此处为什么需要添加该标签?
              provided指的是在编译和测试过程中有效,最后生成的war包时不会加入
               因为Tomcat的lib目录中已经有servlet-api这个jar包,如果在生成war包的时候生效就会和Tomcat中的jar包冲突,导致报错
            -->
            <scope>provided</scope>
        </dependency>
        <!--导入commons-io-->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>

    </dependencies>
    <build>
        <plugins>
            <!--添加tomcat7-maven-plugin插件 -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <port>8080</port><!--设置端口号-->
                    <!--<path>/</path>--><!--设置虚拟目录-->
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Servlet:

读取文件
调用Response对象的getOutputStream方法
获取字节输出流对象
数据拷贝
package com.hhxy.response;

import org.apache.commons.io.IOUtils;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * 测试使用字节流响应数据
 */
@WebServlet("/ResponseDemo4")
public class ResponseDemo4 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1、使用字节输入流读取文件
        InputStream is = new FileInputStream("D:\\Test\\1.gif");
        //2、获取response字节输出流对象
        ServletOutputStream sos = response.getOutputStream();
        //3、将输入流对象的数据拷贝给输出流对象

        /*方式一:使用字节流按字节数组复制文件*/
        /*byte[] buff1 = new byte[1024];
        int len = 0;
        while(((len = is.read(buff1)) != 1)){
            sos.write(buff1,0,len);
        }
        is.close();*/
        //is是自己创建的所以需要手动关,ServletOutputStream是response创建的,不用手动关,后面同理

        /*方式二:使用字节缓冲流按字节数组复制文件*/
        /*InputStream bis = new BufferedInputStream(is);
        byte[] buff2 = new byte[1024];
        int len = 0;
        while((len = bis.read(buff2)) != -1){
            sos.write(buff2,0,len);
        }
        bis.close();*/

        /*方式三:使用Java提供的API*/
        /*byte[] buff3 = is.readAllBytes();
        sos.write(buff3);*/

        /*方式四:使用commons-io工具包提供的方法*/
        IOUtils.copy(is,sos);
    }
}

测试结果如下:

image-20220805203015718

三、综合案例

5.1 用户登录

任务:编写一个登录的网页Login.html,同时编写一个服务器程序LoginServlet,当用户在登录页面输入用户名和密码时,服务器程序能够判断。

要求

  • 项目使用的软件:Maven、Tomcat
  • 项目使用的技术:Mybatis(使用Mapper代理、包扫描)
  • 项目所需的依赖:mysql-connector-javamybatisjavax.servlet-api、``
  • 项目所需的插件:MavenHelper、tomcat7-maven-plugin、MyBatisX

备注:案例已经上传到Gitee上Github上了,欢迎大家参观我的仓库(●’◡’●)

  • 我的Gitee仓库:
  • 我的Github仓库:

image-20220806141900650

前期准备
建表
创建Web项目
导入依赖
编写实体类
编写配置文件
  • Step1:建立一个数据库,名字为:servlet;然后建立一张表,名字为:tb_user

    -- 创建用户表
    CREATE TABLE tb_user(
                            id int primary key auto_increment,
                            username varchar(20) unique,
                            password varchar(32)
    );
    -- 添加数据
    INSERT INTO tb_user(username,password) values('zhangsan','123'),('lisi','234');
    
    SELECT * FROM tb_user;
    

    建议直接在IDEA中完成,当然你也可以在Navicat中完成,只是完成后,你仍然需要在IDEA中连接数据库

    image-20220806150136945

  • Step2:使用Maven创建一个Web项目。Web项目目录结构如下:

    image-20220806202407480

  • Step3:使用Maven导入项目所需要的依赖

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <!--=============项目信息===================-->
        <!--Maven版本-->
        <modelVersion>4.0.0</modelVersion>
        <!--项目坐标-->
        <groupId>com.hhxy</groupId>
        <artifactId>day8_LoginAndRegister</artifactId>
        <version>1.0-SNAPSHOT</version>
        <!--项目打包方式-->
        <packaging>war</packaging>
        <!--项目所依赖的JDK版本-->
        <properties>
            <maven.compiler.source>16</maven.compiler.source>
            <maven.compiler.target>16</maven.compiler.target>
        </properties>
        <!--===================================-->
    
        <!--==========导入项目依赖==============-->
        <!--导入项目所依赖的jar包-->
        <dependencies>
            <!--导入MySQL驱动jar包-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.27</version>
            </dependency>
            <!--导入MyBatis依赖-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.5</version>
            </dependency>
            <!--导入Servlet依赖-->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>3.1.0</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    
        <!--导入项目所依赖的插件-->
        <build>
            <plugins>
                <!--导入Tomcat7-maven插件-->
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <version>2.2</version>
                    <configuration>
                        <port>8080</port><!--设置端口号-->
                        <!--<path>/</path>--><!--设置虚拟目录-->
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
        <!--=============================-->
    </project>
    
  • Step4:编写实体类(POJO)

    User

    package com.hhxy.pojo;
    
    public class User {
    
        private Integer id;
        private String username;
        private String password;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    }
    
  • Step5:编写项目所需要的配置文件

    1)编写MyBatis核心配置文件mybatis-config.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <!--给包起别名,简化书写-->
        <typeAliases>
            <package name="com.hhxy.pojo"/>
        </typeAliases>
        <!--数据库连接环境配置-->
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <!--数据库的连接信息-->
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql:///servlet?useSSL=false&amp;characterEncoding=utf8&amp;useServerPrepStmts=true"/>
                    <property name="username" value="root"/>
                    <property name="password" value="32345678"/>
                </dataSource>
            </environment>
        </environments>
        <!--指定SQL映射文件-->
        <mappers>
            <!-- 原始写法:<mapper resource="com/hhxy/mapper/UserMapper.xml"/>-->
            <!--使用包扫描方式-->
            <package name="com.hhxy.mapper"/>
        </mappers>
    
    </configuration>
    

    2)编写Mapper代理的配置文件UserMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--SQL映射语句-->
    <mapper namespace="com.hhxy.mapper.UserMapper">
        <!--statement语句-->
    
    </mapper>
    
正式开始
编写Mapper接口
编写Servlet
  • Step1:编写Mapper接口

    UserMapper

    package com.hhxy.mapper;
    
    import com.hhxy.pojo.User;
    import org.apache.ibatis.annotations.Param;
    import org.apache.ibatis.annotations.Select;
    
    public interface UserMapper {
        /**
         * 根据用户名和密码查询用户对象
         * @param username 账号
         * @param password 密码
         * @return 返回一个User对象
         */
        @Select("select * from tb_user where username=#{username} and password=#{password}")
        User select(@Param("username")String username, @Param("password")String password);
    
    }
    

备注:这里建议如果遇到长一点的SQL语句在SQL映射文件中写,因为文件中只要你的IDEA连接了数据库就能有提示,在注解中写没有提示【≧ ﹏ ≦

  • Step2:编写Servlet

    LoginServlet

    package com.hhxy.servlet;
    
    import com.hhxy.mapper.UserMapper;
    import com.hhxy.pojo.User;
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    
    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;
    import java.io.InputStream;
    import java.io.PrintWriter;
    
    @WebServlet("/LoginServlet")
    public class LoginServlet extends HttpServlet {
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            System.out.println("doPost方法被调用了。。。");
            this.doGet(request, response);
        }
    
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            System.out.println("doGet方法被调用了。。。");
            //1、接收请求数据,接收用户输入的用户名和密码
            String username = request.getParameter("username");
            String password = request.getParameter("password");
    
            //2、使用MyBatis完成查询
            //2.1 获取SqlSessionFactory对象
            String resource = "./mybatis-config.xml";
            InputStream is = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSF = new SqlSessionFactoryBuilder().build(is);
            //2.2 获取SqlSession对象
            SqlSession sqlS = sqlSF.openSession();
            //2.3 获取Mapper接口对象
            UserMapper userMapper = sqlS.getMapper(UserMapper.class);
            /*----------------------核心代码(其他的可以CV)----------------------------*/
            //2.4 执行SQL语句
            User user = userMapper.select(username, password);
            /*-------------------------------------------------------------------------*/
            //2.5 释放资源
            sqlS.close();
    
            //3、处理结果,发送响应数据
            //3.1 设置响应数据格式
            response.setContentType("text/html;charset=utf-8");
            //3.2 获取响应数据输出流,用于响应数据
            PrintWriter pw = response.getWriter();
            //3.2 判断user对象是否为空,确定账号是否存在
            if(user == null){
                pw.write("登陆失败,请重新登录!");
            }else{
                pw.write("登陆成功!");
            }
            pw.write("你好丫");
        }
    }
    
测试
  • Step1:启动Servlet

    image-20220806202825789

  • Step2:启动html页面,模拟用户输入数据

    image-20220806203137034

    image-20220806203231176

  • Step3:查看结果

    image-20220806203504143

5.2 用户注册

任务:编写一个注册页面:register.html,同时编写一个服务器程序:RegisterServlet,当用户访问注册页面并进行注册时,服务器程序会对账号信息进行判断,账号已存在,返回注册失败,账号不存在返回注册成功。

接着上面的来写,所以要求和环境前期准备直接省略了🤭

image-20220806213540524

image-20220807065126554

Servlet

package com.hhxy.servlet;

import com.hhxy.mapper.UserMapper;
import com.hhxy.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

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;
import java.io.InputStream;
import java.io.PrintWriter;

@WebServlet("/RegisterServlet")
public class RegisterServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("doPost方法被调用了");
        this.doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("doGet方法被调用了");
        //1、接收请求数据,接收用户注册的用户名和密码
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        User user = new User();
        user.setUsername(username);
        user.setPassword(password);

        //2、使用MyBatis完成查询
        //2.1 获取SqlSessionFactory对象
        String resource = "./mybatis-config.xml";
        InputStream is = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSF = new SqlSessionFactoryBuilder().build(is);
        //2.2 获取SqlSession对象
        SqlSession sqlS = sqlSF.openSession();
        //2.3 获取Mapper接口对象
        UserMapper userMapper = sqlS.getMapper(UserMapper.class);
        /*----------------------核心代码(其他的可以CV)----------------------------*/
        //2.4 执行SQL语句
        User user1 = userMapper.selectByUsername(username);

        //3、处理结果,发送响应数据
        //3.1 设置响应数据格式
        response.setContentType("text/html;charset=utf-8");
        //3.2 获取响应数据输出流,用于响应数据
        PrintWriter pw = response.getWriter();
        //3.3 判断是否注册成功
        if (user1 != null) {
            pw.write("该用户名已存在,注册失败!");
        }else{
            userMapper.add(user);
            sqlS.commit();
            pw.write("注册成功!<br/>");
            pw.write("你注册的用户名:username"+username+"<br/>");
            pw.write("你注册的密码:"+password);
        }
        /*-------------------------------------------------------------------------*/
        //4、 释放资源
        sqlS.close();
    }
}

存在一个bug:当我们使用pw.write()方法输出字符串对象时,且字符串的值含有中文就会导致乱码,charset=utf-8直接解决直接输出中文不乱码,示意图:

image-20220806231339577

charset只是将数据的响应编码改成urf-8,而响应数据本身就出现了乱码

相当于一条河上游源头有人下毒,下游再怎么发解药也于事无补

解决方法:

image-20220817112404033

Mapper接口:

package com.hhxy.mapper;

import com.hhxy.pojo.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

public interface UserMapper {
    /*登录的SQL语句*/
    /**
     * 根据用户名和密码查询用户对象
     * @param username 账号
     * @param password 密码
     * @return 返回一个User对象
     */
    @Select("select * from tb_user where username=#{username} and password=#{password}")
    User select(@Param("username")String username, @Param("password")String password);


    /*注册的SQL语句*/
    /**
     * 根据用户输入的账号查询用户是否存在
     *
     * @param s
     * @param username
     * @return
     */
    @Select("select * from tb_user where username=#{username}")
    User selectByUsername(@Param("username") String username);

    /**
     *
     * @param user
     */
//    @Insert("insert into tb_user values(#{username},#{password})")
    //id字段自增长无需设置。长一点的SQL语句推荐导SQL映射文件中去写,有提示
    void add(User user);

}

SQL映射文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--SQL映射语句-->
<mapper namespace="com.hhxy.mapper.UserMapper">
    <!--statement语句-->
    <insert id="add">
        insert into tb_user(username, password) value(#{username},#{password});
    </insert>
</mapper>
5.3. SqlSessionFactory工具类抽取

上面的用户登录用户注册代码虽然能达到要求,但是仍存在不足:每次启动一个新的Servlet都会创建一个新的SqlSessionFactory对象,很浪费内存,所以我们可以使用设计模式中的单例模式进行对代码进行优化(准确来讲应该是单例模式中的饿汉单例模式)。

往期回顾:JavaSE常用关键字详解

具体步骤如下:将重复代码封装成一个工具类,然后将创建对象的代码放在静态代码块中,同时工具类提供一个get方法,用于其它类获取SqlSessionFactory对象。这样就能做到多次获取并使用SqlSessionFactory对象,对象只被创建一次,从而大大减少内存的消耗

image-20220807001403352

工具类:

package com.hhxy.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class sqlSFUtils {
    private static SqlSessionFactory sqlSF;

    static{
        String resource = "./mybatis-config.xml";
        InputStream is = null;
        try {
            is = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        sqlSF = new SqlSessionFactoryBuilder().build(is);
        //System.out.println("我被执行了。。。");
    }
    public static SqlSessionFactory sqlSF(){
        return sqlSF;
    }
}

Servlet每次像创建SqlSessionFactory对象就只需要调用工具类的静态方法即可:

image-20220806235538631

测试:

启动一个连续访问两个Servlet,静态代码块只被执行了一次

image-20220807000253975


拓展:

是不是所有获取相同类型对象都可以进行工具类抽取?

首先答案肯定是不可以的!只有当每次创建的对象它的所有数据都是一样时,才可以进行抽取,变成公用对象。SqlSessionFactory对象的创建可以被抽取,主要原因是所有的SqlSessionFactory对象的目的都是用来获取数据库连接对象的,加载配置文件的,每个SqlSessionFactory对象的数据都是一样的。而有些对象虽然一个类中多个方法中会用到,但是不会进行抽取,比如SqlSession对象的获取,他是数据库连接对象,在登录中它存储的LoginServlet和数据库的连接信息,在注册中他存储的是RegisterServlet和数据库的连接信息,如果公用会造成数据混乱,产生严重的bug!


四、ServletConfig&ServletContext

1、ServletConfig

ServeltConfig对象,会在Servlet初始化阶段被Tomcat创建,该对象存储了Servlet相关的初始化配置信息,即获取we.xml文件中<init-param>标签中的信息

ServletConfig对象常用API

方法作用
String getInitParameter(String name)根据指定的参数名获取参数值
Enumeration getInitParameterNames()返回一个Enumeration对象,该对象包含所有参数名称列表
ServletContext getServletContext()返回一个当前Web应用的ServletContext对象
String getServletName()返回Servlet的访问名1

示例

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">
    <servlet>
        <!--给ServletConfigTestServlet起别名-->
        <servlet-name>ServletConfigTestServlet</servlet-name>
        <servlet-class>com.hhxy.servlet.ServletConfigTestServlet</servlet-class>

        <!--初始化配置-->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>张三</param-name>
            <param-value>123</param-value>
        </init-param>
    </servlet>
    <!--Context参数配置,也属于初始化配置-->
    <context-param>
        <param-name>username</param-name>
        <param-value>ls</param-value>
    </context-param>
    <context-param>
        <param-name>password</param-name>
        <param-value>123</param-value>
    </context-param>
    
    <!--设置Servlet访问路径-->
    <servlet-mapping>
        <servlet-name>ServletConfigTestServlet</servlet-name>
        <url-pattern>/servletConfigTestServlet</url-pattern>
    </servlet-mapping>
</web-app>

Servlet:

注意事项

  • 只能访问</init-param>标签中的<param-name>标签的值
  • 必须使用配置文件设置Servlet访问路径,如果使用注解设置Servlet访问路径,获取的参数值是null
package com.hhxy.servlet;

import javax.servlet.ServletConfig;
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;

/**
 * @author ghp
 * @date 2022/9/8
 */
//@WebServlet("/servletConfigTestServlet")
public class ServletConfigTestServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1、获取ServletConfig对象
        ServletConfig config = this.getServletConfig();
        //2、获取参数encoding对应的值,同时打印测试
        String param = config.getInitParameter("encoding");
        System.out.println(param);//输出:UTF-8

        String name = config.getInitParameter("张三");
        System.out.println(name);//输出:123

        String servletConfigTestServlet = config.getInitParameter("ServletConfigTestServlet");
        System.out.println(servletConfigTestServlet);//输出:null

        String username = config.getInitParameter("username");
        System.out.println(username);//输出:null
    }
}

2、ServletContext

ServletContext官方叫servlet上下文,是一个域对象,实现了HttpSession。在Tomcat启动时,Tomcat会为每一个部署在其上的Servlet创建一个唯一的ServletCentext对象,该对象不仅封装了当前Web应用的所有信息,而且还实现了多个Servlet之间数据的共享(因为它是一个域对象)


推荐阅读:

ServletContext常用API

方法作用
String getInitParameter(String name)根据指定的参数名获取参数值
Enumeration<String> getInitParameterNames()获取Enumberation对象,该对象包含所有参数名称列表

示例

注意事项:

  • 只能访问<context-param>标签中的<param-name>标签的值
  • 必须使用配置文件设置Servlet访问路径,如果使用注解设置Servlet访问路径,获取的参数值是null
package com.hhxy.servlet;

import javax.servlet.ServletContext;
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;
import java.util.Enumeration;

/**
 * @author ghp
 * @date 2022/9/8
 */
@WebServlet("/servletContextTestServlet")
public class ServletContextTestServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1、获取ServletContext对象
        ServletContext context = this.getServletContext();
        //2、获取所有初始化配置的参数列表
        Enumeration<String> params = context.getInitParameterNames();
        //3、遍历参数列表,同时打印各参数对应的值
        while(params.hasMoreElements()){
            String name = params.nextElement();
            String value = context.getInitParameter(name);
            System.out.println(name+"="+value);
        }
    }
}

image-20220908195845175


  1. 如果是使用注解设置Servlet访问路径,则是@WebServlet()中的值;如果是使用配置文件设置Servlet访问路径,则是<Servlet-name>中的值 ↩︎

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知识汲取者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值