摘要

记录Servlet的学习使用过程,另外typora的自带的小表情挺有意思。格式是**:emoji:**,可惜我这款主题还没添加这个功能,网上有别人写的现成的插件,傲娇的我又不是很想用。打算哪天空闲的时候,自己写一套支持emoji的插件

正文

先附上JavaEE官方文档

之前课堂上学过jsp,jsp的每次修改之后不需要重启服务器,而Servlet修改一次需要重启一次。

servlet和jsp本质都是servlet,运行时都是运行.class文件。但是它们的部署方式不一样。

servlet是先编译后部署,修改完以后,IDE进行编译,然后部署.class文件到servlet容器中。如果web服务器已启动,则之前class已被servlet容器加载,可能修改后的class文件不会被servlet容器执行。

而jsp是web服务器进行编译。tomcat可以设置为监视jsp文件的改动,改动之后则重新编译、执行。

一、入门Servlet

概念:运行在服务器端的小程序

本质:Servlet就是一个接口,定义了java类被浏览器访问到(Tomcat识别到)的规则。

1.1 快速入门

  1. 创建JavaEE项目
  2. 创建类,实现Servlet接口
  3. 实现接口中的抽象方法
  4. 配置Servlet

配置Servlet,修改WEB-INF下的web.xml

xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?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-name>demo01</servlet-name>
        <servlet-class>demo01_servlet.Demo01Servlet</servlet-class>
    </servlet>
    <!--Servlet映射-->
    <servlet-mapping>
        <servlet-name>demo01</servlet-name>
        <!-- 访问路径,过滤器中这个地方是拦截路径 -->
        <url-pattern>/demo01</url-pattern>
    </servlet-mapping>
</web-app>

Demo01Servlet.java

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
 * Servlet快速入门
 * @author https://github.com/meethigher
 */
public class Demo01Servlet implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    /**
     * 提供服务的方法
     * @param servletRequest
     * @param servletResponse
     * @throws ServletException
     * @throws IOException
     */
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("Hello Servlet");
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}

访问localhost:8080/demo01,然后控制台就会输出Hello Servlet

1.png

之前一直在用eclipse,最近改用idea,不得不说,真香!

二、详解Servlet

2.1 执行原理

浏览器访问localhost:8080/demo01,tomcat从web.xml找资源路径/demo01,通过url找到配对的servlet-name,然后找到类文件(看到全类名,首先应该想到反射)

tomcat将全类名对应的字节码文件加载进内存Class.forName,创建对象class.newInstance,调用方法service,这些过程都是在服务器完成的。

总结

  1. 当服务器接收到客户端浏览器的请求后,会解析请求URL路径,获取访问的Servlet的资源路径
  2. 查找web.xml,是否有对应的<url-pattern>便签体内容
  3. 如果有,则再找到对应的<servlet-class>找到全类名,Tomcat将字节码文件加载进内存,并创建其对象
  4. 调用方法

2.2 生命周期

  1. 被创建:执行init方法,只执行一次。
    • 默认情况下,第一次访问时,Servlet被创建
    • 可以指定Servlet启动的时机为启动服务器时,Servlet被创建,一般用于加载资源
  2. 提供服务:执行service方法,执行多次
    • 每次访问Servlet时,service都会被调用一次
  3. 被销毁:执行destroy方法(服务器正常关闭时),只执行一次
    • Servlet被销毁时执行。如果服务器不是正常关闭,不会执行。destroy可以理解为“临终遗言”,在Servlet销毁之前执行。一般用于释放资源。
java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class Demo02Servlet implements Servlet {
    /**
     * 初始化方法
     * 在Servlet被创建时执行。只会执行一次
     * @param servletConfig
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("init...");
    }

    /**
     * 获取Servlet对象
     * @return
     */
    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    /**
     * 提供服务的方法
     * 每一次Servlet被访问,都会被执行。执行多次
     * @param servletRequest
     * @param servletResponse
     * @throws ServletException
     * @throws IOException
     */
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("service...");
    }

    /**
     * 获取Servlet的一些信息,版本、作者等等
     * @return
     */
    @Override
    public String getServletInfo() {
        return null;
    }

    /**
     * 销毁方法
     * 在服务器正常关闭时,执行一次
     */
    @Override
    public void destroy() {
        System.out.println("destory...");
    }
}

配置Servlet启动的时机

  1. 第一次被访问时创建。<load-on-startup>为负数,一般为-1
  2. 在服务器启动时创建。<load-on-startup>为0或正整数,一般为0-10
xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<!--配置Servlet-->
    <servlet>
        <servlet-name>demo02</servlet-name>
        <servlet-class>demo01_servlet.Demo02Servlet</servlet-class>
        <!--指定Servlet的创建时机
        1. 第一次被访问时,创建。<load-on-startup>为负数,一般为-1
        2. 在服务器启动时,创建。<load-on-startup>为0或正整数,一般为0-10
        -->
        <load-on-startup>1</load-on-startup>
    </servlet>

Servlet的init方法,只执行一次,说明一个Servlet在内存中只存在一个对象,Servlet是单例的。

多个用户同时访问时,可能存在线程安全问题。就比方说买票,容易数据错乱

解决方法:尽量不要在Servlet中定义成员变量。即使定义了成员变量,也不要对其赋值和修改值。

2.3 注解配置

在Servlet3.0之前,需要通过配置文件web.xml来配置。但是,太麻烦。

在Servlet3.0之后,可以通过注解来配置,web.xml可以不需要了。

从JavaEE6之后,才开始支持Servlet3.0。

步骤

  1. 创建JavaEE项目,选择Servlet的版本为3.0以上,可以不创建web.xml
  2. 定义一个类,实现Servlet接口
  3. 实现接口中的抽象方法
  4. 在类上使用注解@WebServlet
    • @WebServlet("资源路径"),当然@WebServlet还可以配置别的内容,具体参照其源码

2.png

参照文章注解

如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值

如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略(可以参考@SuppressWarnings的源码)

数组赋值时,用{}包裹。如果其中只有一个值的时候,{}可以省略不写

3.png

2.4 编译器与其Tomcat相关配置

如果直接使用tomcat的bin目录下脚本打开服务器,他的配置文件就放在服务器根目录下了。

4.png

对于编译器,他们会给集成到自身的tomcat单独建立配置文件。eclipse和idea这两款编译器,我都用过。

如果使用eclipse,那么eclipse会给他单独配置一个路径

5.png

同样地,idea也会单独配置路径来存储配置文件

6.png

项目分为工作空间目录和tomcat部署的Web项目

  • tomcat真正访问的是tomcat部署的Web项目,而tomcat会将编译后的工作空间目录内容的字节码文件放到WEB-INF下的classes中
  • WEB-INF下的文件不能直接被浏览器访问
  • 断点调试:不能直接运行服务器,需要通过Debug模式来运行服务器

2.5 Servlet体系结构

每次都像上面那样实现Servlet接口,却只用到了service方法,多余!那就需要通过一个实现类来帮助完成。Servlet自身有这种体系结构。

HttpServlet继承自GenericServlet,GenericServlet实现Servlet接口。看文档或者源码

GenericServlet:将Servlet接口中的其他方法做了默认空实现,只将service()方法作为抽象

  • 将来定义Servlet类时,可以继承GenericServlet,实现service()方法即可。其他方法可以自选重写

HttpServlet:对http协议的一种封装,简化操作

  1. 定义类继承HttpServlet
  2. 重写doGet和doPost方法

在实际使用中,继承HttpServlet的方法会更好

2.6 Servlet相关配置

urlPattens:Servlet访问路径

  • 一个Servlet可以定义多个访问路径:@WebServlet({"/d6","/dd6","/demo06"})
  • 路径定义规则:如/xxx/xxx/xxx(多层路径,目录结构)、 /**的优先级很低,如果在访问已经配置的路径时,则会访问已配置的路径)、*.do(如果写*.do是会报错的,这个只是换了一个扩展名而已)

在注解中,如果属性值是value,那么value可以省略。

在配置访问路径的时候,是走的value,而不是urlPattens。我就在想,那么实现类是如何将value转换成url的呢?

找了半天,百度,和官网源码,也没找到@WebServlet的实现类。

后来再文档中看到说,value跟urlPatterns是一个东西。WTF!可能官方是为了让书写更加简洁?

7.png

三、HTTP请求协议

概念:Hyper Text Transfer Protocol 超文本传输协议

传输协议:定义了客户端和服务端通信时,发送数据的格式

特点

  1. 基于TCP/IP的高级协议
  2. 默认端口号:80
  3. 基于请求/响应模型的:一次请求对应一次响应
  4. 无状态的协议:每次请求之间相互独立

历史版本

  • 1.0:每一次请求响应都会建立新的连接
  • 1.1:复用连接

具体的各版本的区别

3.1 请求消息数据格式

  1. 请求行
    • 请求方式 请求url 请求协议/版本。如GET /login.html HTTP/1.1
  2. 请求头
    • 请求头名称:请求头值1,请求头值2,...
  3. 请求空行:作分割作用
  4. 请求体(正文):get方式是没有请求体的。用来封装post请求消息的请求参数的

字符串格式的请求数据

http
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
POST http://meethigher.top/admin/login.php HTTP/1.1
Host: meethigehr.top
Connection: keep-alive
Content-Length: 35
Cache-Control: max-age=0
Origin: http://baixiu-dev.top
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://meethigher.top/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=cdtnkpj4apnarjhtqugqegsfmn

email=admin@qq.com&password=admin

请求方式

Http协议中有7种请求方式,常用的有2种

  • get
    • 请求参数在请求行中(也可以理解在url中)
    • 请求的url长度是有限制的
    • 相对不安全
  • post
    • 请求参数在请求体中
    • 请求的url长度是没有限制的
    • 相对安全

请求头

常见的请求头

  1. User-Agent:浏览器访问服务器内容时,使用的浏览器版本信息
  2. Referer:告诉服务器,当前请求从哪里来
  3. Connection:keep-alive一直连接,表示可复用

Referer有两个作用

  1. 防盗链(盗取超链接)
  2. 统计功能

比如看斗罗大陆,一般是通过选了集数,跳转到腾讯播放页观看,这是用户正常的播放流程。但是如果,用户通过自己建的网页,直接指向腾讯播放页,这个过程就叫做盗链

3.2 响应消息数据格式

  1. 响应行
    • 请求协议/版本 响应状态码 状态码描述
  2. 响应头
    • 响应头名称:响应头值1,响应头值2,...
  3. 响应空行
  4. 响应体

字符串格式的响应数据

http
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
HTTP/1.1 302 Found
Date: Tue, 09 Jun 2020 01:43:26 GMT
Server: Apache/2.4.34 (Win64) PHP/7.2.10
X-Powered-By: PHP/7.2.10
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Location: /admin/index.php
Content-Length: 2508
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

<html>返回一套html代码,此处省略</html>

响应状态码

服务器告诉客户端浏览器本次请求和响应的状态,状态码都是三位数字

状态码分类

  1. 1xx:服务器接收客户端消息,但是没有接收完成,等待一段时间后,发送1xx多状态码
  2. 2xx:表示成功。最常见的就是200
  3. 3xx:表示重定向。最常见的302(重定向),304(访问缓存)
  4. 4xx:客户端错误。最常见的404(代表请求路径没有对应的资源),405(请求方式没有相应的方法,比方说在Servlet中,没有doGet方法或者doPost方法)
  5. 5xx:服务器端错误 。最常见的500(代表服务器内部错误)

响应头

常见的响应头

  1. Content-Type:服务器告诉客户端本次响应体数据格式以及编码格式
  2. Content-disposition:服务器告诉客户端以什么格式打开响应体数据
    • in-line:默认值(在当前页面内打开)
    • attachment;filename=xxx:以附件形式打开响应体。在文件下载中,会使用这个,文件名是xxx

响应体

响应体就是真实的传输的数据

四、Request

Request对象和Response对象的原理

  1. request和response是由服务器来创建,开发者来使用
  2. request获取请求消息,response设置响应消息

4.1 Request对象继承体系结构

tomcat里面的RequestFacade类实现了继承自ServletRequest接口的实现类HttpServletRequest

具体的实现可以在Tomcat的源码中查看。

4.2 Request功能

  1. 获取请求消息
    • 获取请求行数据(标❤比较常用)
      • 获取请求方式:String getMethod()
      • ❤获取虚拟目录:String getContextPath()
      • 获取Servlet路径:String getServletPath()
      • 获取get请求参数:String getQueryString()
      • ❤获取请求URI:String getRequestURI()和String getRequestURL()
      • 获取协议以及版本:String getProtocol()
      • 获取客户机ip地址:String getRemoteAddr()
    • 获取请求头数据(标❤比较常用,请求头是不区分大小写的)
      • ❤String getHeader(String name):通过请求头的名称,获取请求头的值
      • Enumeration getHeaderNames():获取所有的请求头名称
    • 获取请求体数据:只有post请求方式,才有请求体,在请求体中封装了post请求的请求参数
      1. 获取流对象
        • BufferedReader getReader():获取字符输入流,只能操作字符
        • ServletInputStream getInputStream():获取字节输入流,可以操作所有类型数据。用于文件上传比较好
      2. 流对象中拿数据
  2. ❤其他功能
    • 获取请求参数通用方式:不论get或是post请求,都可以使用下面这些方法
      • String getParameter(String name):根据参数名称获取参数值
      • String[] getParameterValues(String name):根据参数名称获取参数值数组,多用于复选框
      • Enumeration getParameterNames():获取所有请求的参数名称
      • Map<String,String[]> getParameterMap():获取所有参数的map集合
    • 请求转发:一种在服务器内部的资源跳转方式
    • 共享数据
    • 获取ServletContext对象

获取请求消息

获取请求行

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@WebServlet("/demo01request")
public class Demo01Request extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /*
         * 获取请求方式:String getMethod()
         * ❤获取虚拟目录:String getContextPath()
         * 获取Servlet路径:String getServletPath()
         * 获取get请求参数:String getQueryString()
         * ❤获取请求URI:String getRequestURI()和String getRequestURL()
         * 获取协议以及版本:String getProtocol()
         * 获取客户机ip地址:String getRemoteAddr()
         */
        System.out.println(request.getMethod());// GET
        System.out.println(request.getContextPath());// /demo
        System.out.println(request.getServletPath());// /demo01request
        System.out.println(request.getQueryString());// user=hh&password=123
        System.out.println(request.getRequestURI()+"---"+request.getRequestURL());// /demo/demo01request---http://localhost:8080/demo/demo01request
        System.out.println(request.getProtocol());// HTTP/1.1
        System.out.println(request.getRemoteAddr());// 这是获取的ipv6地址 0:0:0:0:0:0:0:1
    }
}

URL 统一资源定位符

URI 统一资源标识符

URI代表的范围更大一些(并不是取决于路径的长度的),URL限制了指定的服务器,而URI是没有限制的,应该这样理解

8.png

获取请求头

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@WebServlet("/demo02request")
public class Demo02Request extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String str = headerNames.nextElement();
            if ("user-agent".equals(str)) {
                String agent = request.getHeader(str);
                if (agent.contains("Chrome")) {
                    System.out.println("Chrome");
                } else if (agent.contains("Firefox")) {
                    System.out.println("Firefox");
                }

            }
        }

    }
}

获取请求体

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@WebServlet("/demo03request")
public class Demo03Request extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取请求消息体 请求参数
        request.setCharacterEncoding("utf-8");
        //1.获取字符流
        BufferedReader br=request.getReader();
        //2.读取数据
        String line=null;
        while((line=br.readLine())!=null){
            System.out.println(line);
        }
    }
}

这个部分出了点问题,就是它输出的内容,将字符串以url编码形式打印出来了。而后面的getParameter是直接原样输出的。那这两个的区别是啥?我尝试查看HttpServletRequest这个接口的实现类,最后通过request.getClass().getName()输出全类名,发现实现类是在tomcat里面。

结果里面的代码,也是超级简洁,我也没咋看明白两者的不同之处,惆怅。

4.3 其他功能

获取请求参数通用方式

这个方式既适用于post也适用于get,所以说是通用方式

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@WebServlet("/demo04request")
public class Demo04Request extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        //post获取请求参数
        //根据参数名称获取参数值
//        String user = request.getParameter("user");
//        String password = request.getParameter("password");
//        System.out.println(user+"--"+password);
//        //根据参数名称获取参数值数组
//        String[] hobbies=request.getParameterValues("beauty");
//        for (String hobby: hobbies) {
//            System.out.println(hobby);
//        }
        //获取所有请求的参数名称
//        Enumeration<String> parameterNames = request.getParameterNames();
//        while(parameterNames.hasMoreElements()){
//            System.out.println(parameterNames.nextElement());
//        }
        //测试热部署
        System.out.println("奥利给");//没卵用,百度一下,发现要用debug模式运行才可以

        //获取所有参数的Map集合
        Map<String, String[]> parameterMap = request.getParameterMap();
        //遍历
        Set<String> strings = parameterMap.keySet();
        for(String key:strings){
            System.out.println(key+"↓↓↓↓↓↓");
            //根据key获取值
            String[] strings1 = parameterMap.get(key);
            //遍历值数组
            for (String value:strings1) {
                System.out.println(value);
            }
            System.out.println("------");
        }

    }

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

请求转发

步骤

  1. 通过request对象获取请求转发器对象:RequestDispatcher getRequestDispatcher(String path)
  2. 使用RequestDispatcher对象进行转发:void forward(ServletRequest request, ServletResponse response)

特点

  1. 浏览器地址栏路径没有发生变化
  2. 只能转发到当前的服务器内部中
  3. 转发是一次请求

共享数据

域对象:一个有作用范围的对象,可以在范围内共享数据

request域:代表一次请求的范围。一般用于请求转发的多个资源中共享数据

方法

  1. setAttribute(String name,Object object):存储数据
  2. Object getAttribute(String name):通过键获取值
  3. removeAttribute(String name):通过键移除值
java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@WebServlet("/demo05request")
public class Demo05Request extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("demo05request");
        request.setAttribute("女神","胡列娜");
        RequestDispatcher requestDispatcher = request.getRequestDispatcher("/demo06request");
        requestDispatcher.forward(request,response);
    }

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

demo05将请求转发到demo06,同时在request域内共享数据

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@WebServlet("/demo06request")
public class Demo06Request extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("demo06request");
        System.out.println(request.getAttribute("女神"));
    }

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

获取ServletContext

ServletContext getServletContext()

4.4 中文乱码问题

get方式:Tomcat8已经将get方式乱码解决了

post方式:会乱码。解决方式是在获取参数前,将request的编码设置为utf-8

java
1
request.setCharacterEncoding("utf-8")

五、用户登录案例

5.1 需求

  1. 编写登录页面,账号密码
  2. 使用Druid数据库连接技术
  3. 使用JDBCTemplate技术封装JDBC
  4. 登录成功,跳转SuccessServlet,提示登录成功
  5. 登录失败,跳转到FailServlet,提示登录失败

5.2 实现

  1. 创建项目、登录界面,配置文件以及数据库,导入jar包(在Web项目下,jar包一般是放在WEB-INF的lib目录下)
  2. 创建包domain,包下建User表,对应着数据库表
  3. 创建包dao,包下建UserDao,提供Login方法
  4. 创建包utils,包下建JDBCUtils(连接池工具类)
  5. 创建包servlet,包下建响应的Servlet
  6. 创建包test,通过Junit进行单元测试

代码

login.html

html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<form action="loginServlet" method="post">
    名:<input type="text" name="user"><br>
    密:<input type="password" name="password"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

dao层下的UserDao.java

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
 * 操作数据中user表的类
 * @author https://github.com/meethigher
 */
public class UserDao {
    private JdbcTemplate temp=new JdbcTemplate(JDBCUtils.getDs());
    /**
     * 登录方法
     * @param loginUser 只有用户名和密码
     * @return 包含用户全部方法
     */
    public User login(User loginUser){
        try {
            //1.编写sql,只查询一条数据,因为我数据库里存储的内容有重复值,所有...
            String sql="select * from user where user=? and password=? limit 0,1";
            //2.调用template的查询方法
            User user = temp.queryForObject(sql,
                    new BeanPropertyRowMapper<User>(User.class),
                    loginUser.getUser(),loginUser.getPassword());
            return user;
        } catch (DataAccessException e) {
            //如果没有查到内容,返回null
            e.printStackTrace();//后期不再会使用打印异常,而是通过记录日志的方式
            return null;
        }
    }
}

domain层的User.java

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
 * 用户的JavaBean
 * @author https://github.com/meethigher
 */
public class User {
    private int id;
    private String user;
    private String password;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

utils层下的JDBCUtils.java

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
 * JDBC工具类,使用Druid连接池
 *
 * @author https://github.com/meethigher
 */
public class JDBCUtils {
    private static DataSource ds;

    /**
     * 静态代码块初始化连接池对象
     */
    static {
        try {
            //1.加载配置文件
            Properties pro = new Properties();
            InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties");
            pro.load(is);
            //2.初始化连接池对象
            ds = DruidDataSourceFactory.createDataSource(pro);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    /**
     * 获取连接池对象
     * @return
     */
    public static DataSource getDs() {
        return ds;
    }

    /**
     * 获取连接对象
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }
}

test层下的UserDaoTest.java

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/**
 * @author https://github.com/meethigher
 */
public class UserDaoTest {
    @Test
    public void testLogin(){
        User u=new User();
        u.setUser("胡列娜");
        u.setPassword("123f");
        UserDao udao=new UserDao();
        udao.login(u);
    }
}

servlet层下的LoginServlet.java

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@WebServlet("/example/loginServlet")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置编码
        request.setCharacterEncoding("utf-8");
        //获取请求参数
        String user = request.getParameter("user");
        String password = request.getParameter("password");
        //封装User对象
        User loginUser = new User();
        loginUser.setUser(user);
        loginUser.setPassword(password);
        //调用UserDao的登录方法
        User login = new UserDao().login(loginUser);
        //判断是否登录成功
        if(login!=null){
            request.setAttribute("user",login);
            request.getRequestDispatcher("/example/successServlet").forward(request,response);
        }else{
            request.getRequestDispatcher("/example/failServlet").forward(request,response);
        }

    }
}

servlet层下的SuccessServlet和FailServlet

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//SuccessServlet.java
@WebServlet("/example/successServlet")
public class SuccessServle extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        User user = (User) request.getAttribute("user");
        response.getWriter().write(user.getUser()+"登录成功");
    }
}
//----------------------------------------------

//FailServlet.java
@WebServlet("/example/failServlet")
public class FailServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write("登录失败");
    }
}

BeanUtils工具类

JavaBean:标准的Java类

JavaBean要求

  • 类必须被public修饰
  • 必须提供空参的构造方法
  • 成员变量必须使用private修饰
  • 提供公共的getter和setter方法
  • 一般在项目中放在domain或者entity下

功能:简化数据封装

概念

  • 成员变量
  • 属性:setter和getter方法截取后的产物,比方说getUser(),对应的属性就是user

10.png

方法

  • setProperty()
  • getProperty()
  • populate()

我个人的理解,其实属性跟成员变量是一回事。只不过有的库在封装(比如BeanUtils)的时候,需要拆分出方法的设置或获取的属性,由此产生属性这个概念。

其实,从这个角度来想,好多东西,都是人规定的,因为你整的东西好,用的人多了,自然而然也就成了规定。就像js,php,这种叫做弱类型语言,底层可能还是通过基本数据类型来实现,但是我也可以说,他没有数据类型,因为在用的时候,根本不需要去定义数据类型。

java
1
2
3
4
5
6
7
8
9
public class BeanUtilsTest {
    @Test
    public void test() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        User user = new User();
        BeanUtils.setProperty(user,"user","胡列娜");
        String user1 = BeanUtils.getProperty(user, "user");
        System.out.println(user+"--"+user1);// User{id=0, user='胡列娜', password='null'}--胡列娜
    }
}

优化

登录功能只有两条数据需要封装成User对象。获取数据,创建对象,赋值,6步完成。如果是一个很多条数据,要获取十几次数据,赋值十几次,岂不是很麻烦。这就需要优化一下了。

一次能获取所有数据,一次赋完所有值。可以通过下载BeanUtils来完成数据的封装

优化后的代码

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@WebServlet("/example/loginServlet")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置编码
        request.setCharacterEncoding("utf-8");
//        //获取请求参数
//        String user = request.getParameter("user");
//        String password = request.getParameter("password");
//        //封装User对象
//        User loginUser = new User();
//        loginUser.setUser(user);
//        loginUser.setPassword(password);

        //改用BeanUtils优化
        //获取所有请求参数
        Map<String, String[]> parameterMap = request.getParameterMap();
        //创建对象
        User loginUser = new User();
        //使用BeanUtils封装
        try {
            BeanUtils.populate(loginUser,parameterMap);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        //调用UserDao的登录方法
        User login = new UserDao().login(loginUser);
        //判断是否登录成功
        if(login!=null){
            request.setAttribute("user",login);
            request.getRequestDispatcher("/example/successServlet").forward(request,response);
        }else{
            request.getRequestDispatcher("/example/failServlet").forward(request,response);
        }

    }
}

六、Response

6.1 Response功能

设置响应消息

  • 设置响应行
    • 设置状态码:setStatus(int sc)
  • 设置响应头:setHeader(String name,String value)
  • 设置响应体
    • 获取输出流
      • 字符输出流:PrintWriter getWriter();
      • 字节输出流:ServletOutputStream getOutputStream()
    • 使用输出流,将输出流输出到客户端

6.2 重定向

重定向:资源跳转的方式

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@WebServlet("/demo01Response")
public class Demo01Response extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("demo01Response...");
        //访问 /demo01Response会自动跳转/demo02Response
//        //1.设置状态码为302
//        response.setStatus(302);
//        //2.设置响应头location
//        response.setHeader("location","/demo02Response");

        //因为重定向的步骤是一样的,所以java提供了简化版
        System.out.println(response.getClass().getName());
        response.sendRedirect("/demo02Response");
    }

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

转发的特点(forward)

  1. 浏览器地址栏路径没有发生变化
  2. 只能转发到当前的服务器内部中
  3. 转发是一次请求,可以使用request来共享数据

重定向的特点(redirect)

  1. 地址栏发生变化
  2. 可以访问其他站点资源
  3. 重定向是两次请求,不能使用request来共享数据

6.3 路径

分类

  1. 相对路径:通过相对路径,不可以确定唯一资源
    • 如./index.html(当前路径下的index.html,./是可以省略的),../index.html(上一级的index.html,上上级之类的以此类推)
    • 不以/开头,以.开头路径;./表示当前路径,当然,这个是可以省略的。
    • 规则:要使用相对路径,要确定当前的资源和目标资源之间的相对位置关系
  2. 绝对路径:通过绝对路径,可以确定唯一的资源
    • 如https://meethigher.top/blog/2020/blogs、/blog/2020/blogs
    • 完整地址或者以/开头都是绝对路径

规则:判断定义的路径是给谁用的

  • 给客户端浏览器使用:需要加虚拟目录(项目的访问路径),比方说重定向
  • 给服务器使用:不需要加虚拟目录,比方说请求转发

在实际项目中,虚拟目录的路径不能写死,可以通过request.getContextPath()动态获取虚拟目录

11.png

6.4 服务器输出数据到客户端

获取流对象

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

字符输出流

步骤

  1. 获取字符输出流
  2. 输出数据

注意:乱码问题,客户端的默认编码,与服务端返回消息体的编码不一致

response.getWriter()获取的流的默认编码是ISO-8859-1,我们需要设置该流的默认编码

response获取的流,不需要刷新和关闭。因为response在一次响应结束之后,自动销毁

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@WebServlet("/demo03Response")
public class Demo03Response extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //response获取的流,不需要刷新和关闭。因为response在一次响应结束之后,自动销毁
        //获取流对象之前,设置流的默认编码,但是如果客户端的机器有的是u8,有的是国标,还是会乱码
//        response.setCharacterEncoding("utf-8");
        //设置消息体数据的编码,建议浏览器使用该编码。不区分大写
//        response.setHeader("content-type","text/html;charset=utf-8");
        //当然,javaEE也给提供了更简便的方法
        response.setContentType("text/html;charset=utf-8");

        //1.获取字符输出流
        PrintWriter pw = response.getWriter();
        //2.输出数据
        pw.write("Hello Response");
        pw.write("胡列娜 我女神");
    }

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

字节输出流

步骤

  1. 获取字节输出流:response.getOutputStream()
  2. 输出数据
java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@WebServlet("/demo04Response")
public class Demo04Response extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        //1.获取字节输出流
        ServletOutputStream os = response.getOutputStream();
        //2.输出数据
        os.write("胡列娜 我女神".getBytes("utf-8"));
    }

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

6.5 案例-验证码

本质:图片

目的:防止表单恶意注册

关于Graphics的常用方法

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        int width=500;
        int height=300;
        //1.创建对象,在内存中画图(验证码图片对象)
        BufferedImage image=new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
        //2.美化图片
        //2.1填充背景色
        Graphics g=image.getGraphics();//绘图对象
        g.setColor(Color.PINK);
        g.fillRect(0,0,width,height);
        //2.2画边框
        g.setColor(Color.RED);
        g.drawRect(0,0,width-1,height-1);
        System.out.println("奥利给");
        //2.3写验证码
        String str="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
        Random r=new Random();
        for (int i = 0; i < 4; i++) {
            g.drawString(str.charAt(r.nextInt(str.length()))+"",width/5*(i+1),height/2);
        }
        //2.4 画干扰线
        g.setColor(Color.green);
        for (int i = 0; i < 10; i++) {
            int x1=r.nextInt(width);
            int y1=r.nextInt(height);
            int x2=r.nextInt(width);
            int y2=r.nextInt(height);
            g.drawLine(x1,y1,x2,y2);
        }
        //3.将图片输出到页面展示
        ImageIO.write(image,"jpg",response.getOutputStream());
    }

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

通过html对齐进行访问以及单击修改

html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<img src="/checkCodeServlet">
<a href="javascript:void(0)">看不清换一张</a>
<script>
    let $img=document.querySelector("img");
    $img.onclick = function () {
        let url = "/checkCodeServlet";
        let date=new Date().getTime();
        this.setAttribute("src", url+"?"+date)
    };
    document.querySelector("a").onclick=()=>{
        let url = "/checkCodeServlet";
        let date=new Date().getTime();
        $img.setAttribute("src", url+"?"+date)
    }
</script>
</body>
</html>

七、ServletContext对象

概念:代表整个web应用,可以和程序的容器(服务器)来通信

获取

  1. 通过request获取:request.getServletContext()
  2. 通过HttpServlet获取:getServletContext

其实通过这两个方式获取的都是同一个对象。因为本身HttpServletRequest就是HttpServlet的子类,调用的就是其父类的方法

功能

  1. 获取MIME类型
    • MIME类型:在互联网通信过程中定义的一种文件数据类型
    • 格式:大类型/小类型 如:htm或者html对应text/html、jpg对应image/jpeg
    • 获取:String getMimeType(String filename)
  2. 域对象:共享数据
    • setAttribute(String name,Object value)
    • getAttribute(String name)
    • removeAttribute(String name)
  3. 获取文件的真实路径(服务器完整路径)
    • String getRealPath(String path)

7.1 获取MIME类型

具体的MIME的相关的映射,都在tomcat的web.xml里面配置着。Tomcat的web.xml相当于是所有集成到ide的web.xml的父类。

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@WebServlet("/demo02ServletContext")
public class Demo02ServletContext extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext servletContext = getServletContext();
        //定义文件名称
        String name="a.jpg";
        //获取MIME类型
        String mimeType = servletContext.getMimeType(name);
        System.out.println(mimeType);//image/jpeg
    }

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

12.png

7.2 域对象

ServletContext对象范围:所有用户所有请求的数据(范围最大的)

在一个servlet设置属性

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@WebServlet("/demo03ServletContext")
public class Demo03ServletContext extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext servletContext = getServletContext();
        //设置数据
        servletContext.setAttribute("fairy","胡列娜");

    }

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

在另外的servlet访问属性

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@WebServlet("/demo04ServletContext")
public class Demo04ServletContext extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext servletContext = getServletContext();
        //获取数据
        Object fairy = servletContext.getAttribute("fairy");
        System.out.println(fairy.toString());//胡列娜
    }

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

一般用这个,也比较谨慎,所有用户都是可以共享和操作的。而且,生命周期也非常长,服务器启动,它就创建了,服务器关闭,才会销毁。驻留在内存的时间非常久,造成的内存的压力会很大。

7.3 获取文件真实路径(服务器完整路径)

  • 获取web目录下文件
  • 获取WEB-INF目录下文件
  • 获取src目录下文件
java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@WebServlet("/demo05ServletContext")
public class Demo05ServletContext extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext servletContext = getServletContext();
        String realPath = servletContext.getRealPath("/example/login.html");//获取web下文件
        System.out.println(realPath);//E:\Develop\JavaDev\JavaEE\out\artifacts\JavaEE_war_exploded\example\login.html
        String realPath1 = servletContext.getRealPath("/WEB-INF/web.xml");//获取WEB-INF目录下文件
        System.out.println(realPath1);
        //如果是直接放在src根目录下的,需要去/WEB-INF/classes/下找
        String realPath2 = servletContext.getRealPath("/WEB-INF/classes/a.html");//获取src目录下文件
        System.out.println(realPath2);
    }

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

八、文件下载与上传案例

8.1 文件下载

需求

  1. 页面显示超链接
  2. 点击超链接后弹出下载框
  3. 完成图片文件下载

HTML中超链接指向的资源如果能够被浏览器解析,则在浏览器中展示;若不能解析,就会进行下载到本地。当然,我们也可以通过添加download属性,直接让它所有的都下载。但是这个是没有提示框的。

我们可以使用响应头设置资源打开的方式

content-disposition:attachment;filename=xxx这个表示已附件的形式打开,文件名为xxx

步骤

  1. 定义页面
  2. 定义Servlet
    • 获取文件名称
    • 用字节输入流加载进内存
    • 设置响应头为附件形式打开
    • 将内存写出到响应输出流

中文名称的文件下载时会乱码,改成utf-8的即可,垃圾ie浏览器已经没救了,兼容不了

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@WebServlet("/downloadServlet")
public class DownloadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        //1.获取请求参数,文件名称
        String filename = request.getParameter("filename");
        //2.使用字节输入流加载文件进内存
        //2.1找到文件服务器路径
        ServletContext sc = getServletContext();
        String realPath = sc.getRealPath("/壁纸5.jpg");
        //2.2用字节流关联
        FileInputStream fis = new FileInputStream(realPath);
        //3设置response响应头
        //3.1设置响应格式
        String mimeType = sc.getMimeType(filename);
//        response.setHeader("content-type",mimeType);
        response.setContentType(mimeType);
        //3.2设置响应头打开格式
        //解决中文文件名问题,垃圾ie,去他妈的吧,兼容不了
        filename= URLEncoder.encode(filename,"utf-8");
        response.setHeader("content-disposition","attachment;filename="+filename);
        //4.将输入流数据写出到输出流中
        ServletOutputStream sos = response.getOutputStream();
        byte[] bytes = new byte[1024 * 8];
        int len=0;
        while((len=fis.read(bytes))!=-1){
            sos.write(bytes,0,len);
        }
        //5.释放资源
        fis.close();

    }

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

html中的代码

html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<a href="downloadServlet?filename=壁纸5.jpg">图片下载</a>
</body>
</html>

8.2 文件上传

我一开始以为文件上传跟文件下载一样,获取请求的传过来的数据,然后写。

但是,一直写出数据都是0kb,我就很纳闷了,我的代码是没有问题的。想了一会,搞明白了,request获取的输入流,是获取到的请求体的数据,而不是获取上传文件的内容