9、Request和Response

目录

1、Request和Response的概述

2、Request对象

2.1、Request继承体系

2.2、Request获取请求数据

2.2.1 获取请求行数据

2.2.2 获取请求头数据

2.2.3 获取请求体数据

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

2.3 IDEA快速创建Servlet

 2.4、请求参数中文乱码问题

2.4.1、POST请求解决方案

2.4.2 GET请求解决方案

2.5、Request请求转发

3、Response对象

3.1、Response设置响应数据功能介绍

3.2、Respones请求重定向

3.3 路径问题

3.4、Response响应字符数据

3.5、Response响应字节数据

4、用户注册登录案例

4.1、用户登录

4.1.1、需求分析

4.1.2、环境准备

4.1.3、代码实现

 4.2、用户注册

4.2.1、需求分析

 4.2.2、代码编写

4.3 SqlSessionFactory工具类抽取


1、Request和Response的概述

Request是请求对象,Response是响应对象。这两个对象在我们使用Servlet的时候可以看到,如图

 此时,我们就需要思考一个问题:request和response这两个参数的作用是什么:

Request:获取请求数据

        浏览器会发送HTTP请求到后台服务器(Tomcat)

        HTTP的请求中会包含很多请求数据(请求行、请求头、请求体)

        后台服务器会对HTTP请求中的数据进行解析并把解析结果存入到一个对象中

        而这个存储解析结果的对象正是Request对象,所以我们可以从Request对象获取请求的相关参数

        获取到数据后就可以继续后续业务,比如获取用户名和密码就可以实现登录操作的相关业务

Response:设置响应数据

        业务处理完后,后台需要给前端返回业务处理的结果(即响应数据)

        把这些响应数据封装到Response对象中

        后台服务器(tomcat)会解析Response对象,按照(响应行、响应头、响应体)格式拼接结果

        浏览器最终解析结果,把内容展示在浏览器给用户浏览

对于上述所讲内容,我们通过一个案例来初步体验下request和response对象的使用


@WebServlet("/MyServlet3")
public class MyServlet3 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //使用request对象 获取请求数据
        String name = request.getParameter("name");  //url?name=zhangsan


        //使用response对象 设置响应数据
        response.setHeader("content-type","text/html;charset=utf-8");
        response.getWriter().write("<h1>"+name+",欢迎您!</h1>");

    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("post~~~~");
    }
}

启动成功后就可以通过浏览器来访问,并且根据传入参数的不同就可以在页面上展示不同的内容:

 小结

在这节中,我们主要认识了下request对象和reponse对象:

        request对象是用来封装请求数据的对象

        response对象是用来封装响应数据的对象

2、Request对象

2.1、Request继承体系

在学习这节内容之前,我们先思考一个问题,前面在介绍Request和Reponse对象的时候,比较细心的同学可能已经发现:

        当我们的Servlet类实现的是Servlet接口的时候,service方法中的参数是ServletRequest和ServletResponse

        当我们的Servlet类继承的是HttpServlet类的时候,doGet和doPost方法中的参数就变成HttpServletRequest和HttpServletReponse

那么,

        ServletRequest和HttpServletRequest的关系是什么?

        request对象是有谁来创建的?

        request提供了哪些API,这些API从哪里查?

首先,我们先来看下Request的继承体系:

        

从上图中可以看出,ServletRequest和HttpServletRequest都是Java提供的,所以我们可以打开JavaEE提供的API文档[参考: 资料/JavaEE7-api.chm],打开后可以看到:

所以ServletRequest和HttpServletRequest是继承关系,并且两个都是接口,接口是无法创建对象,这个时候就引发了下面这个问题:

 这个时候,我们就需要用到Request继承体系中的RequestFacade:

        该类实现了HttpServletRequest接口,也间接实现了ServletRquest接口。

        Servlet类中的service方法、doGet方法或者是doPost方法最终都是由Web服务器[Tomcat]来调用的,所以Tomcat提供了方法参数接口的具体实现类,并完成了对象的创建

        要想了解RequestFacade中都提供了哪些方法,我们可以直接查看JavaEE的API文档中关于ServletRequest和HttpServletRequest的接口文档,因为RequestFacade实现了其接口就需要重写接口中的方法

对于上述结论,要想验证,可以编写一个Servlet,在方法中把request对象打印下,就能看到最终的对象是不是RequestFacade,代码如下:

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

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

启动服务器,运行访问http://localhost:8080/request-demo/MyServlet4,得到运行结果:

 小结

        Request的继承体系为ServletRequest > HttpServletRequest > RequsetFacade

        Tomcat需要解析请求数据,封装为Request对象,并且创建request对象传递到service方法

        使用request对象,可以查阅JavaEE API文档的HttpServletRequest接口中方法说明

2.2、Request获取请求数据

HTTP请求数据总共分为三部分内容,分别是 请求行、请求头、请求体,对于这三部分内容的数据,分别该如何获取,首先我们先来学习请求行数据如何获取?

2.2.1 获取请求行数据

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

对于这三部分内容,request对象都提供了对应的API方法来获取,具体如下:

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

介绍完上述方法后,通过代码演示一下使用


@WebServlet("/requestdemo1")
public class RequestDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // String getMethod():获取请求方式: GET
        String method = req.getMethod();
        System.out.println(method);  // GET
        // String getContextPath():获取虚拟目录(项目访问路径):/web-demo
        String contextPath = req.getContextPath();
        System.out.println(contextPath);
        // StringBuffer getRequestURL(): 获取URL(统一资源定位符):http://localhost:8080/web-demo/requestdemo1
        StringBuffer URL = req.getRequestURL();
        System.out.println(URL.toString());
        // String getRequestURI():获取URI(统一资源标识符): /web-demo/requestdemo1
        String URI = req.getRequestURI();
        System.out.println(URI);
        // String getQueryString():获取请求参数(GET方式): username=zhangsan
        String queryString = req.getQueryString();
        System.out.println(queryString);

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        
    }
}

启动服务器,访问http://localhost:8080/web-demo/requestdemo1?username=zhangsan&passwrod=123,获取的结果如下:

2.2.2 获取请求头数据

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

 所以根据请求头名称获取对应值的方法为:

        String getHeader(String name)

接下来,在代码中如果想要获取客户端浏览器的版本信息,则可以使用


@WebServlet("/requestdemo2")
public class RequestDemo2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取请求头: user-agent: 浏览器的版本信息
        String userAgent = req.getHeader("user-agent");
        System.out.println(userAgent);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }
}

2.2.3 获取请求体数据

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

对于请求体中的数据,Request对象提供了如下两种方式来获取其中的数据,分别是:

        获取字节输入流,如果前端发送的是字节数据,比如传递的是文件数据,则使用该方法

        ServletInputStream getInputStream()  该方法可以获取字节

        获取字符输入流,如果前端发送的是纯文本数据,则使用该方法

        BufferedReader getReader()

接下来,大家需要思考,要想获取到请求体的内容该如何实现?

具体实现的步骤如下:

1.准备一个页面,在页面中添加form表单,用来发送post请求

2.在Servlet的doPost方法中获取请求体数据

3.在doPost方法中使用request的getReader()或者getInputStream()来获取

4.访问测试

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/web-demo/requestdemo3" method="post">
    username
    <input type="text" name="username"><br/>
    password
    <input type="password" name="password"><br/>
    <input type="submit" value="提交">

</form>

@WebServlet("/requestdemo3")
public class RequestDemo3 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //在此处获取请求体中的数据
        // 调用getReader()或者getInputStream()方法,因为目前前端传递的是纯文本数据,所以我们采用getReader()方法来获取
        //1. 获取字符输入流
        BufferedReader br = req.getReader();
        // 2. 读取数据
        String line = br.readLine();
        System.out.println(line);
    }
}

注意:

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

启动服务器,通过浏览器访问http://localhost:8080/web-demo/req.html

 

小结

HTTP请求数据中包含了 请求行、请求头、请求体 ,针对这三部分内容,Request对象提供了相应的API来获取对应的值:

请求行

        getMethod() 获取请求方式

        getContextPath() 获取目录访问路径

        getRequestURL() 获取请求的URL

        getRequestURI() 获取请求的URI

        getQueryString() 获取GET请求方式的请求参数

请求头

        getHeader(String name)根据请求头名称获取其对应的值

请求体

        注意:浏览器发送的POST请求才有请求体

        如果是纯文本数据:getReader()

        如果是字节数据如文件数据:getInputStream()

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

在学习下面内容之前,我们先提出两个问题:

        什么是请求参数?

        请求参数和请求数据的关系是什么?

1.什么是请求参数?

为了能更好的回答上述两个问题,我们拿用户登录的例子来说明

1.1 想要登录网址,需要进入登录页面

1.2 在登录页面输入用户名和密码

1.3 将用户名和密码提交到后台

1.4 后台校验用户名和密码是否正确

1.5 如果正确,则正常登录,如果不正确,则提示用户名或密码错误

上述例子中,用户名和密码其实就是我们所说的请求参数。

2.什么是请求数据?

请求数据则是包含请求行、请求头和请求体的所有数据

3.请求参数和请求数据的关系是什么?

3.1 请求参数是请求数据中的部分内容

3.2 如果是GET请求,请求参数在请求行

3.3 如果是POST请求,请求参数一般在请求体

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

  GET方式

String getQueryString()

POST方式

BufferedReader getReader();

有了上述的知识储备,我们来实现一个案例需求:

(1)发送一个GET请求并携带用户名,后台接收后打印到控制台

(2)发送一个POST请求并携带用户名,后台接收后打印到控制

@WebServlet("/requestdemo4")
public class RequestDemo4 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp);
        String result = req.getQueryString();
        System.out.println(result);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //BufferedReader br = req.getReader();
        String result = req.getReader().toString();
        System.out.println("post~~~");
        System.out.println(result);
    }
}

对于上述代码,很容易的看出来存在重复代码

如何解决上述重复代码问题呢?

当然,也可以在doGet中调用doPost,在doPost中完成参数的获取和打印,另外需要注意的是,doGet和doPost方法都必须存在,不能删除任意一个。

GET请求和POST请求获取请求参数的方式不一样,在获取请求参数这块该如何实现呢?

要想实现,我们就需要思考:

GET请求方式和POST请求方式区别主要在于获取请求参数的方式不一样,是否可以提供一种统一获取请求参数的方式,从而统一doGet和doPost方法内的代码?

解决方案一:

 使用request的getMethod()来获取请求方式,根据请求方式的不同分别获取请求参数值,这样就可以解决上述问题,但是以后每个Servlet都需要这样写代码,实现起来比较麻烦,这种方案我们不采用

解决方案二:(推荐使用)

request对象已经将上述获取请求参数的方法进行了封装,并且request提供的方法实现的功能更强大,以后只需要调用request提供的方法即可,在request的方法中都实现了哪些操作?

1)根据不同的请求方式获取请求参数,获取的内容如下:

2)把获取到的内容进行分割,内容如下:

 3)把分割后端数据,存入到一个Map集合中:

 注意:因为参数的值可能是一个,也可能有多个,所以Map的值的类型为String数组。

 基于上述理论,request对象为我们提供了如下方法:

Map<String,String[]> getParameterMap() 获取所有参数Map集合
String[] getParameterValues(String name) 根据名称获取参数值(数组)
String getParameter(String name) 根据名称获取参数值(单个值)

小结

        req.getParameter()方法使用的频率会比较高

        以后我们再写代码的时候,就只需要按照如下格式来编写:

public class RequestDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       //采用request提供的获取请求参数的通用方式来获取请求参数
       //编写其他的业务代码...
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req,resp);
    }
}

2.3 IDEA快速创建Servlet

使用通用方式获取请求参数后,屏蔽了GET和POST的请求方式代码的不同,则代码可以定义如下格式:

由于格式固定,所以我们可以使用IDEA提供的模板来制作一个Servlet的模板

这样我们后期在创建Servlet的时候就会更高效,具体如何实现:

1、按照自己的需求,修改Servlet创建的模板内容

 

 如果找不到Servlet,则在File -> Project Structure -> Facets 勾选Source Roots即可,如下图

 2.4、请求参数中文乱码问题

不管是GET还是POST请求,在发送的请求参数中如果有中文,在后台接收的时候,都会出现中文乱码的问题。具体该如何解决呢?

2.4.1、POST请求解决方案

 分析出现中文乱码的原因:

        POST的请求参数是通过request对象的getReader()来获取流中的数据

        Tomcat在获取数据流的使用采用的编码是ISO-8859-1

        ISO-8859-1编码是不支持中文的,所以会乱码

解决方案

        页面设置的编码格式为UTF-8

        把Tomcat在获取数据流之前的编码设置为UTF-8

        通过request.setCharacterEncoding("UTF-8")设置编码,UTF-8也可以写成小写

演示如下:


@WebServlet("/requestdemo6")
public class RequestDemo6 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // //1. 解决乱码: POST getReader()
        // 设置字符输入流的编码,设置的字符集要和页面保持一致
        req.setCharacterEncoding("UTF-8");
        // 2、获取username
        String username = req.getParameter("username");
        System.out.println(username);
    }

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

 至此POST请求中文乱码的问题就已经解决,但是这种方案不适用于GET请求,这个原因是什么呢,咱们下面再分析。

2.4.2 GET请求解决方案

刚才提到一个问题是 POST请求的中文乱码解决方案为什么不适用与GET请求?

        GET请求获取请求参数的方式是 getQueryString()

        POST请求获取请求参数的方式是 getReader()

        request.setCharacterEncoding("utf-8")是设置request处理流的编码

        getQueryString方法并没有通过流的方式获取数据

 所以GET请求不能用设置编码的方式来解决中文乱码问题,那问题又来了,如何解决GET请求的中文乱码呢?

 1、首先我们需要分析下GET请求出现乱码的原因:

(1)浏览器通过HTTP协议发送请求和数据给后台服务器(Tomcat)

(2)浏览器在发送HTTP的过程中会对中文数据进行URL编码

(3)在进行URL编码的时候会采用页面<meta>标签指定的UTF-8的方式进行编码张三编码后的结果为%E5%BC%A0%E4%B8%89

(4)后台服务器(Tomcat)接收到%E5%BC%A0%E4%B8%89后会默认按照ISO-8859-1进行URL解码

(5)由于前后编码与解码采用的格式不一样,就会导致后台获取到的数据为乱码。

思考: 如果把req.html页面的<meta>标签的charset属性改成ISO-8859-1,后台不做操作,能解决中文乱码问题么?

答案是否定的,因为ISO-8859-1本身是不支持中文展示的,所以改了<meta>标签的charset属性后,会导致页面上的中文内容都无法正常展示。

分析完上面的问题后,我们会发现,其中有两个我们不熟悉的内容就是 URL编码 和 URL解码 ,什么是URL编码,什么又是URL解码呢?

URL编码

这块知识我们只需要了解下即可,具体编码过程分两步,分别是:

        (1)将字符串按照编码方式转为二进制

        (2)每个字节转为2个16进制数并在前边加上%

  张三按照UTF-8的方式转换成二进制的结果为:

 其十六进制,并且在每两位前面加一个%,就可以获取到

 对于上述进制转换,如果不借助工具的话,计算是很繁琐的,在Java中已经为我们提供了编码和解码的API工具类,可以让我们更快速的进行编码和解码

编码

java.net.URLEncoder.encode("需要被编码的内容","字符集(UTF-8)")

解码

java.net.URLDecoder.decode("需要被解码的内容","字符集(UTF-8)")

 下面演示一下编解码

public class URLDemo {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String username = "张三";

        // URL编码
        String encode = URLEncoder.encode(username, "UTF-8");
        System.out.println("被编码后:"+encode);
        // URL解码
        String decode = URLDecoder.decode(encode,"utf-8");
        String decode2 = URLDecoder.decode(encode,"iso-8859-1");
        System.out.println("采用utf-8解码后:"+decode);
        System.out.println("采用iso-8859-1解码后:"+decode2);

    }
}

 至此,我们就可以分析出GET请求中文乱码出现的原因了:

        浏览器把中文参数按照 UTF-8进行URL编码

        Tomcat对获取的内容进行了ISO-8859-1的URL解码

        在控制台就会出现类上 å¼ ä¸ 的乱码(最后一位是空格)

  2、分析清楚了原因,接下来就是想办法解决

从上图可以看住,

        在进行编码和解码的时候,不管使用的是哪个字符集,他们对应的%E5%BC%A0%E4%B8%89是一致的

        那他们对应的二进制值也是一样的,为:

 为所以我们可以考虑把å¼ ä¸转换成字节,在把字节转换成张三,在转换的过程中是它们的编码一致,就可以解决中文乱码问题。

具体的实现步骤如下:

        1、按照ISO-8859-1的编码获取乱码å¼ ä¸对应的字节数组

        2、按照UTF-8编码获取字节数组对应的字符串

代码如下

        



public class URLDemo2 {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String username = "张三";

        // URL编码 假设为浏览器进行了UTF-8的URL编码
        String encode = URLEncoder.encode(username, "UTF-8");
        System.out.println("被编码后:"+encode);
        // URL解码 假设为Tomcat进行了ISO-8859-1的URL解码
        String decode = URLDecoder.decode(encode,"iso-8859-1");
        System.out.println("采用iso-8859-1解码后:"+decode);

        // 将经过 ISO-8859-1解码的数据装载到字节数组
        byte[] bytes = decode.getBytes("iso-8859-1");
        System.out.print("字节数组的内容为:");
        for (byte b: bytes){
            System.out.print(b +" ");
        }
        System.out.println();
        // 将字节数组转换为字符串
        String str = new String(bytes);
        System.out.println(str);
    }
}

至此对于GET请求中文乱码的解决方案,我们就已经分析完了,最后在代码中去实现下:

@WebServlet("/requestdemo6")
public class RequestDemo6 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //2. 获取username
        String username = req.getParameter("username");
        System.out.println("解决乱码前:" + username);

        //3. GET,获取参数的方式:getQueryString
        // 乱码原因:tomcat进行URL解码,默认的字符集ISO-8859-1

       /* //3.1 先对乱码数据进行编码:转为字节数组
        byte[] bytes = username.getBytes(StandardCharsets.ISO_8859_1);
        //3.2 字节数组解码
        username = new String(bytes, StandardCharsets.UTF_8);
        */

        username = new String(username.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);

        System.out.println("解决乱码后:" + username);
    }

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

 

 小结

        1、中文乱码解决方案

        POST请求和GET请求如果有中文,后台接收数据就会出现中文乱码问题

        GET请求在Tomcat8.0以后的版本就不会出现了

        POST请求解决方案是:设置输入流的编码

request.setCharacterEncoding("UTF-8");  //注意:设置的字符集要和页面保持一致

        通用方式(GET/POST):需要先解码,再编码

new String(username.getBytes("ISO-8859-1"),"UTF-8");

        2、URL编码实现方式

        编码:

URLEncoder.encode(str,"UTF-8");

        解码:

URLDecoder.decode(s,"ISO-8859-1");

2.5、Request请求转发

1、请求转发(forward):是一种在服务内部的资源跳转方式

(1)浏览器发送请求给服务器,服务器中对应的资源A接收到请求

(2)资源A处理完请求后将请求发给资源B

(3)资源B处理完后将结果响应给浏览器

(4)请求从资源A到资源B的过程就叫请求转发

2、请求转发的实现方式(下面的这行代码写在资源A)

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

具体如何使用,如下需求:

针对上述需求,具体实现步骤为:

        1、创建一个RequestDemo5类,接收/req5的请求,在doGet方法中打印demo5

        2、 创建一个RequestDemo6类,接收/req6的请求,在doGet方法中打印demo6

        3、在RequestDemo5中使用方法

                        req.getRequestDispatcher("/req6").forward(req, resp)进行请求转发

        4、启动测试

1、创建RequestDemo5类

/**
 * 请求转发
 */

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

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

2、创建RequestDemo6类

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

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

3、在RequestDemo5中使用方法

                        req.getRequestDispatcher("/req6").forward(req, resp)进行请求转发

/**
 * 请求转发
 */
@WebServlet("/req5")
public class RequestDemo5 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("demo5~~~~");
        // 请求转发
        request.getRequestDispatcher("/req6").forward(request,response);
    }

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

4、启动测试

访问http://localhost:8080/web-demo/req5,就可以在控制台看到如下内容:

 

 说明请求已经转发到了/req6

3、请求转发资源间共享数据:使用Request对象

此处主要解决的问题是把请求从/req5转发到/req6的时候,如何将数据给/req6

需要使用request对象提供的三个方法:

void setAttribute(String name,Object o); 存储数据到request域[范围,数据是存储在request对象]中
Object getAttribute(String name); 根据key获取值
void removeAttribute(String name); 根据key删除该键值对

接着上一个需求来:

1、在 RequestDemo5的doGet方法中转发请求之前,将数据存入到request域对象中

 2、在RequestDemo6的doGet方法中从request域对象中获取数据,并将其打印到控制台

3、启动测试

1、修改RequestDemo5中的方法

@WebServlet("/req5")
public class RequestDemo5 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("demo5~~~~");
        //  存储数据
        request.setAttribute("msg","hello");
        // 请求转发
        request.getRequestDispatcher("/req6").forward(request,response);
    }

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

2、修改RequestDemo6中的方法

@WebServlet("/req6")
public class RequestDemo6 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("demo6~~~");
        // 获取数据
        String msg = (String)request.getAttribute("msg");
        System.out.println(msg);
    }

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

3、启动测试

访问http://localhost:8080/web-demo/req5,就可以在控制台看到如下内容:

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

4、注意:请求转发的特点:浏览器地址栏路径不发生变化

虽然后台从/req5转发到/req6,但是浏览器的地址一直是/req5,未发生变化

3、Response对象

前面讲解完了Request对象,接下来我们回到刚开始的那张图片

Request:使用request对象来获取请求数据

Response:使用response对象来设置响应数据

Reponse的继承体系和Request的继承体系也非常相似:

3.1、Response设置响应数据功能介绍

HTTP响应数据总共分为三部分内容,分别是响应行、响应头、响应体,对于这三部分内容的数据,respone对象都提供了哪些方法来进行设置?

1、响应行

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

void setStatus(int sc);

2、响应头

设置响应头键值对:

void setHeader(String name,String value);

 3、响应体

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

 获取字符输出流:

PrintWriter getWriter();

获取字节输出流

ServletOutputStream getOutputStream();

3.2、Respones请求重定向

1、Response重定向(redirect):一种资源跳转的方式

        类似于请求转发forward,但与之不同的是,重定向会改变浏览器地址栏路径

(1)浏览器发送请求给服务器,服务器中对应的资源A接收到请求

(2)资源A现在无法处理该请求,就会给浏览器响应一个302的状态码+location的一个访问资源B的路径

(3)浏览器接收到响应状态码为302就会重新发送请求到location对应的访问地址去访问资源B

(4)资源B接收到请求后进行处理并最终给浏览器响应结果,这整个过程就叫重定向

2、重定向的实现方式:

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

具体如何来使用,我们先来看下需求:

 针对上述需求,具体的实现步骤为:

        1、创建一个ResponseDemo1类,接收/resp1的请求,在doGet方法中打印resp1~~~

        2、 创建一个ResponseDemo2类,接收/resp2的请求,在doGet方法中打印resp2~~~

        3、在ResponseDemo1中使用方法

                        resp.setStatus(302);

                        resp.setHear("Location","/web-demo/resp2")来给前端响应数据

        4、启动测试

1、创建ResponseDemo1类

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

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

2、创建ResponseDemo2类

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

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

3、在ResponseDemo1中的doGet方法中给前端响应数据

@WebServlet("/resp1")
public class ResponseDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("resp1~~~");
        // 设置响应状态吗
        response.setStatus(302);
        // 设置响应头 Location
        response.setHeader("Location", "/web-demo/resp2");
    }

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

4、启动测试

访问http://localhost:8080/web-demo/resp1,就可以在控制台看到如下内容:

注意:地址栏也发生了变化 

说明/resp1/resp2都被访问到了。到这重定向就已经完成了。

虽然功能已经实现,但是从设置重定向的两行代码来看,会发现除了重定向的地址不一样,其他的内容都是一模一样,所以request对象给我们提供了简化的编写方式为:

resposne.sendRedirect("/request-demo/resp2")

 所以第三步中的代码可以简化为:


@WebServlet("/resp1")
public class ResponseDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("resp1~~~");
       /* // 设置响应状态吗
        response.setStatus(302);
        // 设置响应头 Location
        response.setHeader("Location", "/web-demo/resp2");*/
        
        // 重定向
        response.sendRedirect("/web-demo/resp2");
    }

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

3、重定向的特点

       1) 浏览器地址栏路径发生变化

              当进行重定向访问的时候,由于是由浏览器发送的两次请求,所以地址会发生变化

        2)可以重定向到任何位置的资源(服务内容、外部均可)

                因为第一次响应结果中包含了浏览器下次要跳转的路径,所以这个路径是可以任意位置资源。

       3) 两次请求,不能在多个资源使用request共享数据

                因为浏览器发送了两次请求,是两个不同的request对象,就无法通过request对象进行共享数据


介绍完 重定向 和 请求转发 以后,接下来需要把他们放在一起对比

以后到底用哪个,还是需要根据具体的业务来决定  

3.3 路径问题

1、问题1:转发的时候路径上没有加/request-demo而重定向加了,那么到底什么时候需要加,什么时候不需要加呢?

 其实判断的依据很简单,只需要记住下面的规则即可:

        浏览器使用:需要加虚拟目录(项目访问路径)

        服务端使用:不需要加虚拟目录(请求转发是发生在服务器内部的)

对于转发来说,因为是在服务端进行的,所以不需要加虚拟目录

对于重定向来说,路径最终是由浏览器来发送请求,就需要添加虚拟目录。

掌握了这个规则,接下来就通过一些练习来强化下知识的学习:

<a href='路劲'> 超链接,从浏览器发送,需要加
<form action='路径'> 表单,从浏览器发送,需要加
req.getRequestDispatcher("路径") 转发,是从服务器内部跳转,不需要加
resp.sendRedirect("路径") 重定向,是由浏览器进行跳转,需要加。

2、问题2:在重定向的代码中,/web-demo是固定编码的,如果后期通过Tomcat插件配置了项目的访问路径,那么所有需要重定向的地方都需要重新修改,该如何优化?

答案也比较简单,我们可以在代码中动态去获取项目访问的虚拟目录,具体如何获取,我们可以借助前面咱们所学习的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.4、Response响应字符数据

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

        通过Response对象获取字符输出流:PrintWriter writer = resp.getWriter();

        通过字符输出流写数据:writer.write("aaa");

接下来,我们实现通过些案例把响应字符数据给实际应用下:

1、返回一个简单的字符串aaa

/**
 * 响应字符数据:设置字符数据的响应体
 */
@WebServlet("/resp3")
public class ResponseDemo3 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PrintWriter writer = response.getWriter();
        writer.write("aaa");
    }

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

2、 返回一串html字符串,并且能被浏览器解析

@WebServlet("/resp3")
public class ResponseDemo3 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PrintWriter writer = response.getWriter();
        //content-type,告诉浏览器返回的数据类型是HTML类型数据,这样浏览器才会解析HTML标签
        response.setHeader("content-type","text/html");
        writer.write("<h1>aaa</h1>");
    }

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

 注意:

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

 3、返回一个中文的字符串你好,需要注意设置响应数据的编码为utf-8

  因为Response对象获取的字符流编码格式默认为ISO-8859-1


@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");
        PrintWriter writer = response.getWriter();
        
        writer.write("你好");
    }

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

3.5、Response响应字节数据

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

        通过Response对象获取字节输出流:ServletOutputStream outputStream = resp.getOutputStream();

        通过字节输出流写数据: outputStream.write(字节数据);

接下来,我们实现通过些案例把响应字符数据给实际应用下:

1、返回一个图片文件到浏览器

/**
 * 响应字节数据:设置字节数据的响应体
 */
@WebServlet("/resp4")
public class ResponseDemo4 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. 读取文件
        FileInputStream fis = new FileInputStream("src/main/webapp/imgs/reg_bg_min.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);

优化后的代码:


import org.apache.commons.io.IOUtils;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * 响应字节数据:设置字节数据的响应体
 */
@WebServlet("/resp4")
public class ResponseDemo4 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. 读取文件
        FileInputStream fis = new FileInputStream("src/main/webapp/imgs/reg_bg_min.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);
    }
}

4、用户注册登录案例

接下来通过两个比较常见的案例,巩固一下前面所学的知识,一个是注册,一个是登录

首先来实现用户登录

4.1、用户登录

4.1.1、需求分析

1、用户在登录界面输入用户名和密码,提交到LoginServlet

2、在LoginServlet中接收和请求数据(用户名和密码)

3、在LoginServlet中通过Mybatis实现调用UserMapper来根据用户名和密码查询数据库

4、将查询结果封装在User对象中

5、在LoginServle中判断放回的User对象是否为null

6、如果不为null,则说明用户存在、密码正确,返回“登录成功”给前端 

7、如果为null,说明根据用户名和密码没有查询到用户,登录失败,返回“登录失败”给前端 

4.1.2、环境准备

1、创建新模块 Project Structure -> Modules ->New Module  -> 

 选择Maven -> 使用骨架Create from archetype -> 选择maven-archetype-webapp -> Next

为该模块命名 -> Next

 确定Maven仓库的一些信息 ->Finsh

 点击Apply -> OK

删除pom.xml下的一些无用坐标,至如下所示 

2、补全项目结构

右击main -> New -> Directory 

依次点击 java resoures

右击src -> Directory  补全test

 直至项目完整结构如下所示

3、在项目的pom.xml导入相关坐标

  <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.34</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

        <!--junit 单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
        <!--添加slf4j日志api-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.20</version>
        </dependency>
        <!--添加logback-classic依赖-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!--添加logback-core依赖-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
            </plugin>
        </plugins>
    </build>

导入logback.xml

在resources导入logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--
        CONSOLE :表示当前的日志信息是可以输出到控制台的。
    -->
    <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%level]  %cyan([%thread]) %boldGreen(%logger{15}) - %msg %n</pattern>
        </encoder>
    </appender>

    <logger name="com.clear" level="DEBUG" additivity="false">
        <appender-ref ref="Console"/>
    </logger>

    <!--

      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
     , 默认debug
      <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
      -->
    <root level="DEBUG">
        <appender-ref ref="Console"/>
    </root>
</configuration>

 4、将准备好的一些页面添加到webapp目录下

 5、创建数据库db1,创建表tb_user,创建实体类User

建库建表语句如下

create database db1;
use db1;

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'),
    ('wangwu','12345'),
    ('laoliu','666666');

实体类如下

右击java -> New -> Java Class 

 

6、创建mybatis 核心配置文件 mybatis-config.xml

 在 src/main/java/resoures 下创建mybatis核心配置文件

  右击resoures -> New -> File

去mybtis官网导入核心配置文件的基础,然后在自行修改

mybatis官网https://mybatis.net.cn/ 

如下

<?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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/db1?useSSL=false&amp;
                useServerPrepStmts=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/clear/Mapper/UserMapper.xml"/> <!--SQL映射文件目录-->
    </mappers>
</configuration>

7、编写UserMapperSQL映射文件,UserMapper接口(切记映射文件要与mapper在同一目录下

去mybatis官网复制映射文件,然后再修改即可

  接下来,在com/clear/Mapper目录下创建UserMapper接口

package com.clear.Mapper;

public interface UserMapper {
}

 然后把刚刚创建的UserMapper.xml映射文件移动至与UserMapper接口在同一目录下:

步骤如下:

 

 

注意:UserMapper.xml移动位置以后,需要去mybatis核心配置文件这修改mapper

如下所示

4.1.3、代码实现

1、在UserMapper接口中提供一个根据用户名和密码查询用户对象的方法

   说明:

         @Param注解的作用:用于传递参数,是方法的参数可以与SQL中的字段名相对应。

package com.clear.Mapper;

import com.clear.pojo.User;
import org.apache.ibatis.annotations.Param;

public interface UserMapper {
    /**
     * 根据用户名与密码查询用户对象
     * @param userName
     * @param passWord
     * @return
     */
    public abstract User select(@Param("userName") String userName,
                                @Param("passWord") String passWord);
}

2、编写SQL映射文件

          其中namespace名称空间 这里填写 SQL映射文件的全限定名

          resultType填写pojo实体类

<?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">

<mapper namespace="com.clear.Mapper.UserMapper">
    <select id="select" resultType="com.clear.pojo.User">
        select * from tb_user
            where username=#{userName} and password=#{passWord};
    </select>
</mapper>

3、修改login.html

4、编写LoginServlet

 

package com.clear.web;


import com.clear.Mapper.UserMapper;
import com.clear.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 {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1、接收用户名和密码
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        // 2、调用Mybatis完成查询
        // 2.1、获取SqlSessionFactory对象
        String resource = "mybatis-config.xml";
        InputStream inputStream =  Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 2.2、获取SqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 2.3、获取Mapper
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 2.4、执行方法
        User user = userMapper.select(username, password);
        System.out.println(user);
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        if (user != null){
            writer.write("登录成功");
        } else{
            writer.write("登录失败");
        }
    }

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

 4、启动服务器测试

在login.html页面中输入正确的username 和password后显示登陆成功

以上的代码还有优化空间,接下来进行优化

mybaits核心配置文件中的mappers可以优化成包扫描的形式,当有很多映射文件时,就不用挨个挨个写,如下所示

mybatis核心配置文件还可以加上<typeAliases>标签,这样在SQL映射文件中 resultType属性和<resultMap>标签的type属性就可以不用书写全限定名了  

还可以在SQL映射文件中添加一个resultMap标签(为了防止sql语句中参数与pojo实体类中参数不同而导致的错误) 

        其中将selset标签中的resultType属性更换成了 resultMap 

        resultMap标签中的type也可以不用写全限定名,因为mybatis核心配置文件中typeAliases标签中  定义了子标签package

 4.2、用户注册

4.2.1、需求分析

        

  1. 用户在注册页面输入用户名和密码,提交请求给RegisterServlet

  2. 在RegisterServlet中接收请求和数据[用户名和密码]

  3. 在RegisterServlet中通过Mybatis实现调用UserMapper来根据用户名查询数据库表

  4. 将查询的结果封装到User对象中进行返回

  5. 在RegisterServlet中判断返回的User对象是否为null

  6. 如果为nul,说明根据用户名可用,则调用UserMapper来实现添加用户

  7. 如果不为null,则说明用户不可以,返回"用户名已存在"数据给前端

 4.2.2、代码编写

1、编写UserMapper接口

在接口中定义添加用户的方法和根据用户名查询的方法

下面就采用注解的方式实现

public interface UserMapper {
    /**
     * 根据用户名与密码查询用户对象
     * @param userName
     * @param passWord
     * @return
     */
    public abstract User select(@Param("userName") String userName,
                                @Param("passWord") String passWord);

    /**
     * 根据用户名查询
     * @param userName
     * @return
     */
    @Select("select * from tb_user where username=#{userName}")
    User selectByUserName(String userName);

    // 注:这里的参数和映射文件的写法一致
    @Insert("insert into tb_user(username, password) values(#{userName},#{passWord})")
    void add(User user);
}

2、修改register.html

将表单提交至 /login-demo/RegisterServlet 采用Post的方式

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>欢迎注册</title>
    <link href="css/register.css" rel="stylesheet">
</head>
<body>

<div class="form-div">
    <div class="reg-content">
        <h1>欢迎注册</h1>
        <span>已有帐号?</span> <a href="login.html">登录</a>
    </div>
    <form id="reg-form" action="/login-demo/RegisterServlet" method="post">

        <table>

            <tr>
                <td>用户名</td>
                <td class="inputs">
                    <input name="username" type="text" id="username">
                    <br>
                    <span id="username_err" class="err_msg" style="display: none">用户名不太受欢迎</span>
                </td>

            </tr>

            <tr>
                <td>密码</td>
                <td class="inputs">
                    <input name="password" type="password" id="password">
                    <br>
                    <span id="password_err" class="err_msg" style="display: none">密码格式有误</span>
                </td>
            </tr>

        </table>

        <div class="buttons">
            <input value="注 册" type="submit" id="reg_btn">
        </div>
        <br class="clear">
    </form>

</div>
</body>
</html>

3、RegisterServlet类

package com.clear.web;

import com.clear.Mapper.UserMapper;
import com.clear.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 {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1、接收用户名和密码
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        // 封装对象
        User user = new User();
        user.setUserName(username);
        user.setPassWord(password);
        // 2、调用Mybatis完成查询
        // 2.1、获取SqlSessionFactory对象
        String resource = "mybatis-config.xml";
        InputStream inputStream =  Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 2.2、获取SqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession(false);
        // 2.3、获取Mapper
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 2.4、执行方法
        User u = userMapper.selectByUserName(username);

        resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        if (u == null){
            // 用户名不存在,添加用户
            userMapper.add(user);
            // 提交事务
            sqlSession.commit();
            sqlSession.close();
            writer.write("注册成功");
        } else{

            writer.write("注册失败,用户名已存在");
        }

    }

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

查询数据库        

发现有中文乱码,现在先不解决,后续在解决吧

4.3 SqlSessionFactory工具类抽取

上面两个功能已经实现,但是在写Servlet的时候,因为需要使用Mybatis来完成数据库的操作,所以对于Mybatis的基础操作就出现了些重复代码,如下

	String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new 
	SqlSessionFactoryBuilder().build(inputStream);

有了这些重复代码就会造成一些问题:

        重复代码不利于后期的维护

        SqlSessionFactory工厂类进行重复创建

                就相当于每次买手机都需要重新创建一个手机生产工厂来给你制造一个手机一样,资源消耗非常大但性能却非常低。所以这么做是不允许的。

那如何来优化呢?

        代码重复可以抽取工具类

        对指定代码只需要执行一次可以使用静态代码块

有了这两个方向后,代码具体该如何编写?

在项目下建立一个utils目录

package com.clear.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 SqlSessionFactoryUtils {
    private static SqlSessionFactory sqlSessionFactory;

    static {
        //静态代码块会随着类的加载而自动执行,且只执行一次
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSessionFactory getSqlSessionFactory() {
        return sqlSessionFactory;
    }

}

工具类抽取以后,以后在对Mybatis的SqlSession进行操作的时候,就可以直接使用

SqlSessionFactory sqlSessionFactory =SqlSessionFactoryUtils.getSqlSessionFactory();

这样就可以很好的解决上面所说的代码重复和重复创建工厂导致性能低的问题了。

猜你喜欢

转载自blog.csdn.net/weixin_56058578/article/details/127885076