【JavaWEB】核心知识

409 阅读38分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情
个人主页:HelloCode.
本文专栏:Java零基础指南
如果您觉得手机APP排版不美观,建议使用浏览器打开
阅读如有问题,欢迎指正,一起进步~~


HTTP协议

概述

  • HTTP(Hyper Text Transfer Protocol):超文本传输协议

  • HTTP协议是基于 TCP/IP 协议的(安全)

  • 超文本:比普通文本更加强大(还有图片、音频等等)

  • 传输协议:客户端和服务端的通信规则(握手规则)

组成部分

  • 请求:客户端发起请求到服务器

  • 响应:服务器收到客户端的请求后发起响应给客户端

除了手动发起的请求外,JavaScript、CSS、图片等资源会自动发起请求

协议的请求

请求的组成部分

  1. 请求行:请求方式 提交路径(提交参数) HTTP/版本号

  2. 请求头

    名称说明
    Accept客户端浏览器所支持的MIME类型
    Accept-Encoding客户端浏览器所支持的压缩编码格式。最常用的就是gzip压缩
    Accept-Language客户端浏览器所支持的语言。一般都是zh_CN或en_US等
    Referer告知服务器,当前请求的来源
    Content-Type请求正文所支持的MIME类型
    Content-Length请求正文的长度
    User-Agent浏览器相关信息
    Connection连接的状态。Keep-Alive保持连接
    If-Modified-Since客户端浏览器缓存文件的最后修改时间
    Cookie会话管理相关,非常的重要
  3. 请求空行:普通换行,用于区分请求头和请求体

  4. 请求体:只有POST提交方式才有请求体,用于显示提交参数

请求的方式

  • GET

get请求方式.png

  • POST

post请求方式.png

只有POST请求方式存在请求体,GET请求方式没有请求体

get提交的参数在请求行中,post提交的参数在请求体中

协议的响应

响应的组成部分

  1. 响应行:请求方式 HTTP/版本号 状态码 状态描述

    常见状态码

    状态码说明
    200一切OK
    302/307请求重定向,两次请求,地址栏发生变化
    304请求资源未发生变化,使用缓存
    404请求资源未找到
    500服务器错误
  2. 响应头

    名称说明
    Location请求重定向的地址,常与302,307配合使用
    Server服务器相关信息
    Content-Type响应正文的MIME类型
    Content-Length响应正文的长度
    Content-Disposition告知客户端浏览器,以下载的方式打开响应正文
    Refresh定时刷新
    Last-Modified服务器资源的最后修改时间
    Set-Cookie会话管理相关,非常的重要
    Expires:-1服务器资源到客户端浏览器后的缓存时间
    Catch-Control:no-catch不要缓存
  3. 响应空行:普通换行,用于区分响应头和响应体

  4. 响应体:将资源文件发送给客户端浏览器进行解析


发布资源案例

发布静态资源

  1. 创建一个Java WEB项目

  2. 将静态页面所需资源导入到项目的web目录下

  3. 修改web.xml配置文件,修改默认主页

    <!--在web.xml中加入-->
    <welcome-file-list>
        <welcome-file>/路径/文件名.html</welcome-file>
    </welcome-file-list>
    
  4. 将项目部署到tomcat中

  5. 启动tomcat服务

  6. 打开浏览器查看页面

Servlet介绍

  • Servlet是运行在Java服务器端的程序,用于接收和响应来自客户端基于HTTP协议的请求。

  • 如果想实现Servlet的功能,可以通过实现==javax.servlet.Servlet==接口或者继承它的实现类。

  • 核心方法:service(),任何客户端的请求都会经过该方法。

发布动态资源

  1. 创建一个JavaWEB项目

  2. 将静态页面所需资源导入到项目的web目录下

  3. 修改web.xml配置文件,修改默认主页

  4. 在项目的src路径下编写一个类,实现Servlet接口

  5. 重写service方法,输出一句话即可

  6. 修改web.xml配置文件,配置servlet相关资源

    <!--Servlet声明-->
    <servlet>
        <servlet-name>自定义名称</servlet-name>
        <servlet-class>java全类名(包名.类名)</servlet-class>
    </servlet>
    
    <!--Servlet映射-->
    <servlet-mapping>
        <servlet-name>和声明中的名称保持一致</servlet-name>
        <url-pattern>/访问路径(浏览器地址栏要输入的访问路径)</url-pattern>
    </servlet-mapping>
    
  7. 将项目部署到tomcat中

  8. 启动tomcat服务

  9. 打开浏览器测试功能

执行流程

  1. 通过浏览器访问到指定的资源路径

  2. 通过url-pattern找到对应的name标签

  3. 通过name找到对应的Servlet声明

  4. 在声明中找到对应的Java实现类

  5. 执行service方法


Servlet

概述

  • Servlet是运行在Java服务器端的程序,用于接收和响应来自客户端基于HTTP协议的请求

  • 是一个接口(javax.servlet.Servlet),常用实现类:GenericServlet、HttpServlet(继承自GenericServlet)

  • 所有的客户端请求都会经过service方法进行处理

  • 初始化会调用init方法,销毁时会调用destroy方法

执行过程

  1. 客户端浏览器向Tomcat服务器发起请求

  2. Tomcat服务器解析请求地址(URL)

  3. Tomcat根据地址找到对应的项目

  4. 找到项目中的web.xml文件

  5. 解析请求资源地址(URI)

  6. 找到项目的资源(对应的Java类)

  7. 执行service方法,响应给客户端浏览器

关系视图

Servlet关系视图.png

实现方式

  1. 实现Servlet接口,实现所有的抽象方法,该方式支持最大程度的自定义

  2. 继承GenericServlet抽象类,必须重写service方法,其他方法可选择重写。该方式让我们开发servlet变得简单。但是这种方式与HTTP协议无关

  3. 继承HttpServlet抽象类,需要重写doGetdoPost方法。该方式表示请求和响应都需要和HTTP协议相关

生命周期

  • 对象的生命周期,就是对象从出生到死亡的过程。即:出生 =》活着 =》死亡。官方说法是对象创建到销毁的过程

  • 出生:请求第一次到达Servlet时,对象就创建出来,并且初始化成功。只出生一次,将对象放到内存中

  • 活着:服务器提供服务的整个过程中,该对象一直存在,每次都是执行service方法

  • 死亡:当服务器停止时,或者服务器宕机时,对象死亡

出生对应的是init方法

活着对应的是service方法(doGetdoPost方法)

死亡对应的是destroy方法

结论: Servlet对象只会创建一次,销毁一次。所以Servlet对象只有一个实例。如果一个对象实例在应用中是唯一的存在,那么就称他为单例模式

线程安全问题

  • 由于Servlet采用的是单例设计模式,也就是整个应用中只有一个实例对象。所以我们需要分析这个唯一的实例对象中的类成员是否线程安全

  • 模拟用户登录功能来查看Servlet线程是否安全

结论: 一个浏览器代表一个线程,多个浏览器代表多个线程。按理说应该是每个浏览器查看的都是自己的信息。但结果浏览器中数据混乱。因此,我们可以认为Servlet是线程不安全的

解决: 定义类成员要谨慎。如果是共用的,并且只会在初始化时赋值,其它时间都是获取的话,那么是没问题的。如果不是共用的,或者每次使用都有可能对其赋值,那就要考虑线程安全的问题了,可以将其定义到doGet或doPost方法内或者使用同步功能即可

映射方式

  1. 具体名称的方式。访问的资源路径必须和映射配置完全相同 【常用】

    <servlet>
    	<servlet-name>Demo</servlet-name>
    	<servlet-class>study.servlet.Demo</servlet-class>
    </servlet>
    <servlet-mapping>
    	<servlet-name>Demo</servlet-name>
    	<url-pattern>/Demo</url-pattern>
    </servlet-mapping>
    
  2. / 开头 + 通配符的方式。只要符合目录结构即可,不用考虑结尾是什么

    <servlet>
            <servlet-name>Demo2</servlet-name>
            <servlet-class>study.servlet.Demo2</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>Demo2</servlet-name>
            <url-pattern>/Demo2/*</url-pattern>
        </servlet-mapping>
    
  3. 通配符 + 固定格式结尾的方式。只要符合固定结尾格式即可,不用考虑前面的路径

    <servlet>
            <servlet-name>Demo2</servlet-name>
            <servlet-class>study.servlet.Demo2</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>Demo2</servlet-name>
            <url-pattern>*.test</url-pattern>
        </servlet-mapping>
    

注意: 优先级问题。越是具体的优先级越高,越是模糊的 优先级越低。第一种 > 第二种 > 第三种

多路径映射

  • 我们可以给一个Servlet配置多个访问映射,从而根据不同的请求路径来实现不同的功能

  • 场景分析

    如果访问的资源路径是/vip,商品价格打9折

    如果访问的资源路径是/vvip,商品价格打5折

    如果访问的资源路径是其它,商品价格不打折

  • 采用第二种映射方式实现多路径映射(/ + 通配符)

package study.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class Demo3 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取浏览器路径
        String requestURI = req.getRequestURI();
        // 分隔路径
        String path = requestURI.substring(requestURI.lastIndexOf("/"));
        // 路径判断,区分价格
        if(path.equals("/vip")){
            System.out.println("100元");
        }else if(path.equals("/vvip")){
            System.out.println("200元");
        }else System.out.println("300元");
    }

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

创建时机

  1. 第一次访问时创建

    • 优势:减少对服务器内存的浪费。提高了服务器启动的效率

    • 弊端:如果有一些要在应用加载时就做的初始化操作无法完成

  2. 服务器加载时创建

    • 优势:提前创建好对象,提高了首次执行的效率。可以完成一些应用加载时要完成的初始化操作

    • 弊端:对服务器内存占用较多,影响了服务器启动的效率

  3. 修改Servlet创建时机。在<servlet>标签中,添加<load-on-startup>标签

    <load-on-startup>1</load-on-startup>

    正整数代表服务器加载时创建,值越小,优先级越高。负整数或不写代表第一次访问时创建

默认Servlet

  • 默认Servlet是由服务器提供的一个Servlet。它配置在 Tomcat 的 conf 目录中的 web.xml 中

默认Servlet.png

  • 它的映射路径是<url-pattern>/<url-pattern>。我们在发送请求时,首先会在我们项目中的web.xml 中查找映射配置,找到则执行。但是当找不到对应的Servlet 路径时,就去找默认的 Servlet ,由默认Servlet 处理。所以,一切都是Servlet

ServletConfig

  • ServletConfig 是Servlet 的配置参数对象,在Servlet 的规范中,允许为每一个Servlet 都提供一些 初始化的配置。所以,每个Servlet 都有一个自己的ServletConfig

  • 作用:在Servlet 的初始化时,把一些配置信息(键值对的形式)传递给Servlet

  • 生命周期:和Servlet 相同

配置方式

  • <servlet>标签中,通过 <init-param>标签来配置。有两个子标签

  • <param-name>:代表初始化参数的key

  • <param-value>:代表初始化参数的value

    <servlet>
        <servlet-name>servletConfigDemo</servlet-name>
        <servlet-class>study.servlet.servletConfigDemo</servlet-class>
        
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>desc</param-name>
            <param-value>This is ServletConfig</param-value>
        </init-param>
    </servlet>
    

常用方法

返回值方法名说明
StringgetInitParameter(String name)根据参数名称获取参数的值
EnumerationgetInitParameterNames()获取所有参数名称的枚举
StringgetServletName()获取Servlet的名称
ServletContextgetServletContext()获取ServletContext对象
  • 通过init方法,来对ServletConfig对象进行赋值

    private ServletConfig config;
    public void init(ServletConfig config) throws ServletException{
        this.config = config;
    }
    
  • 枚举项遍历

    Enumeration<String> keys = config.getInitParameterNames();
    while(keys.hasMoreElements()){
        String key = keys.nextElement();
        String value = config.getInitParameter(key);
        System.out.println(key + "--" + value);
    }
    
// ServletConfigDemo测试代码
package study.servlet;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;

public class ServletConfigDemo extends HttpServlet {
    private ServletConfig config;
    // 通过init方法对config赋值,获取ServletConfig对象
    @Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 根据name获取value
        String encoding = config.getInitParameter("encoding");
        System.out.println("encoding:" + encoding);

        // 获取所有name并遍历
        Enumeration<String> names = config.getInitParameterNames();
        while(names.hasMoreElements()){
            String name = names.nextElement();
            String value = config.getInitParameter(name);
            System.out.println(name + "---" + value);
        }
        // 获取Servlet-name
        String sname = config.getServletName();
        System.out.println("Servlet-name:" + sname);
        // 获取ServletContext对象
        ServletContext servletContext = config.getServletContext();
        System.out.println(servletContext);
    }

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

ServletContext

  • ServletContext 是应用上下文对象(应用域对象)。每一个应用中只有一个 ServletContext 对象

  • 作用:可以配置和获得应用的全局初始化参数,可以实现Servlet 之间的数据共享

  • 生命周期:应用一加载则创建,应用被停止则销毁


域对象

  • 域对象指的是对象有作用域,也就是作用范围。域对象可以实现数据的共享。不同作用范围的域对象,共享数据的能力也不一样

  • 在Servlet 规范中,一共有4个域对象。ServletContext 就是其中的一个。他也是web 应用中最大的作用域,也叫 application 域。它可以实现整个应用之间的数据共享

配置方式

  • <web-app>标签中,通过<context-param>标签来配置。有两个子标签

  • <param-name>:代表全局初始化参数的key

  • <param-value>:代表全局初始化参数的value

<context-param>
	<param-name>globalencoding</param-name>
	<param-value>UTF-8</param-value>
</context-param>
<context-param>
	<param-name>globaldesc</param-name>
	<param-value>This is ServletContext</param-value>
</context-param>

常用方法

返回值方法名说明
StringgetInitParameter(String name)根据名称获取全局配置的参数
StringgetContextPath()获取当前应用的访问虚拟目录
StringgetRealPath(String path)根据虚拟目录获取应用部署的磁盘绝对路径

HttpServlet类继承自GenericServlet

GenericServlet类中有getServletContext方法,可以直接获取ServletContext对象

返回值方法名说明
voidsetAttribute(String name, Object value)向应用域对象中存储数据
ObjectgetAttribute(String name)通过名称获取应用域对象中的数据
voidremoveAttribute(String name)通过名称移除应用域对象中的数据
package study.servlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

public class ServletContextDemo extends HttpServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext servletContext = req.getServletContext();
        Enumeration<String> names = servletContext.getInitParameterNames();
        while(names.hasMoreElements()){
            String name = names.nextElement();
            String value = servletContext.getInitParameter(name);
            System.out.println(name + "====" + value);

        }
        resp.setContentType("text/html;charset=UTF-8");
        String contextPath = servletContext.getContextPath();
        String realPath = servletContext.getRealPath(contextPath);
        PrintWriter pw = resp.getWriter();
        pw.write("虚拟目录为:" + contextPath + "<br>");
        pw.write("真实目录为:" + realPath);

        servletContext.setAttribute("use","lisi");
    }

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

注解开发

Servlet3.0 规范

  • 我们使用的是Tomcat 9版本。JavaEE规范要求是8.对应的Servlet 版本应该是4.x 版本。但是,在企业开发中,稳定要远比追求新版本重要。所以我们会降低版本使用,用的是Servlet 3.1版本。

  • 其实我们之前的操作全部是基于 Servlet 2.5版本规范的,也就是借助于配置文件的方式。后来随着软件开发逐步的演变,基于注解的配置开始流行。而Servlet 3.0版本也就开始支持注解开发了

  • Servlet 3.0版本既保留了2.5版本的配置方式,同时有支持了全新的注解配置方式。它可以完全不需要 web.xml 配置文件,就能实现 Servlet 的配置,同时还有一些其他的新特性。

自动注解开发

实现步骤

  1. 创建一个 web 项目

  2. 定义一个类,继承HttpServlet

  3. 重写 doGet 和 doPost方法

  4. 在类上使用@WebServlet 注解并配置Servlet

  5. 部署并启动项目

  6. 通过浏览器测试

@WebServlet("/servletDemo")
public class ServletDemo extends HttpServlet{
    
}

注解详解.png

手动创建容器(了解)

  • Servlet 3.0 规范除了使用自动注解的配置方式外,还支持手动创建 Servlet 容器的方式。如果使用必须遵循其编写规范。在3.0 版本加入了一个新的接口:ServletContainerInitializer,需要重写onStartup方法

步骤

  1. 定义一个类,继承HttpServlet

  2. 重写 doGet 和 doPost方法

  3. 定义一个类,实现ServletContainerInitializer接口

  4. 在src 目录下创建一个META-INF的包

  5. 在 META-INF 包下创建一个services 的包

  6. 在 services 包下创建一个 javax.servlet.ServletContainerInitializer 的文件

  7. 文件中的内容为容器实现类的全类名

  8. 在容器实现类中的 onStartup 方法中完成注册 Servlet

    public void onStartup(Set<Class<?>> set, ServletContext servletContext){
        // 1.创建ServletDemo类的对象
        ServletDemo servletDemo = new ServletDemo();
        // 2. 在ServletContext 对象中添加Servlet,并得到Servlet的动态配置对象
        ServletRegistration.Dynamic registration = servletContext.addServlet("ServletDemo", servletDemo);
        // 3. 配置Servlet
        registration.addMapping("/servletDemo");	// 映射访问资源的路径
        registration.setLoadOnStartup(1);	// 设置Servlet加载时机
    }
    
  9. 部署并启动项目

  10. 通过浏览器测试


学生管理系统1

  • 需求:添加学生信息到Java服务器中的本地文件

步骤

  1. 创建一个 web 项目

  2. 创建一个用于保存学生信息的html文件

  3. 创建一个类,继承HttpServlet

  4. 重写doGet 和 doPost 方法

  5. 在web.xml 文件中修改默认主页和配置 Servlet

  6. 在doGet 方法中接收表单数据保存到文件中,并响应给浏览器结果

  7. 部署并启动项目

  8. 通过浏览器测试

获取表单数据

req.getParameter(name值):就可以通过HttpServletRequest 对象中的方法 通过表单的name属性获取到对应的表单数据

响应数据

PrintWriter pw = resp.getWriter():通过 HttpServletResponse 对象中的方法获取输出流对象

pw.println("Save Success"):将指定内容响应到浏览器中

<!--studentAdd.html-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>添加学生</title>
</head>
<body>
    <form action="/add" method="get">
        姓名:<input type="text" name="username"> <br>
        年龄:<input type="number" name="age"> <br>
        成绩:<input type="number" name="score">   <br>
        <button type="submit">添加</button>
    </form>
</body>
</html>
<!--web.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--配置首页-->
    <welcome-file-list>
        <welcome-file>/studentAdd.html</welcome-file>
    </welcome-file-list>
    <!--Servlet声明-->
    <servlet>
        <servlet-name>StudentServlet</servlet-name>
        <servlet-class>studentServlet.add</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>StudentServlet</servlet-name>
        <url-pattern>/add</url-pattern>
    </servlet-mapping>
</web-app>
// add.java
package studentServlet;

import studentServlet.bean.Student;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public class add extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取对应表单内容
        String username = req.getParameter("username");
        String age = req.getParameter("age");
        String score = req.getParameter("score");
        // 封装学生对象
        Student stu = new Student(username, Integer.parseInt(age), Integer.parseInt(score));
        // 保存到本地文件
        BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\Java\\code\\StudentServlet\\stu.txt",true));
        bw.write(stu.getUsername() + "," + stu.getAge() + "," + stu.getScore());
        bw.newLine();
        bw.close();

        // 响应给浏览器
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter pw = resp.getWriter();
        pw.println("添加成功,将在3秒后跳转到首页!!!");
        resp.setHeader("Refresh","3;url=/index.html");
        pw.close();
    }

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

请求对象

请求: 获取资源。在BS架构中,就是客户端浏览器向服务端发出询问。

请求对象: 就是在项目中用于发送请求的对象(ServletRequestHttpServletRequest)

ServletRequest 和 HttpServletRequest 都是接口,但是Tomcat 服务器会帮我们处理好实现类的赋值等工作,我们不需要关心这些

获取各种路径

返回值方法名说明
StringgetContextPath()获取虚拟目录名称
StringgetServletPath()获取Servlet映射路径
StringgetRemoteAddr()获取访问者ip地址
StringgetQueryString()获取请求的消息数据
StringgetRequestURI()获取统一资源标识符
StringBuffergetRequestURL()获取统一资源定位符
package study.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;
import java.io.PrintWriter;

@WebServlet("/request")
public class RequestDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String contextPath = req.getContextPath();
        String servletPath = req.getServletPath();
        String remoteAddr = req.getRemoteAddr();
        String queryString = req.getQueryString();
        String requestURI = req.getRequestURI();
        StringBuffer requestURL = req.getRequestURL();

        PrintWriter pw = resp.getWriter();
        pw.println("contextPath= " + contextPath);
        pw.println("servletPath= " + servletPath);
        pw.println("remoteAddr= " + remoteAddr);
        pw.println("queryString= " + queryString);
        pw.println("requestURI= " + requestURI);
        pw.println("requestURL= " + requestURL);
    }

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

获取请求头

返回值方法名说明
StringgetHeader(String name)根据请求头名称获取一个值
EnumerationgetHeaders(String name)根据请求头名称获取多个值
EnumerationgetHeaderNames()获取所有请求头名称
package study.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;
import java.io.PrintWriter;
import java.util.Enumeration;

@WebServlet("/request2")
public class RequestDemo2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(req.getHeader("host"));
        System.out.println(req.getHeader("user-agent"));
        Enumeration<String> headers = req.getHeaders("user-agent");
        while(headers.hasMoreElements()){
            String s = headers.nextElement();
            System.out.println(s);
        }
        System.out.println("===============");
        Enumeration<String> names = req.getHeaderNames();
        while(names.hasMoreElements()){
            System.out.println(names.nextElement());
        }
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

获取请求参数信息

返回值方法名说明
StringgetParameter(String name)根据名称获取数据
String[]getParameterValues(String name)根据名称获取所有数据
EnumerationgetParameterNames()获取所有名称
Map<String,String[]>getParameterMap()获取所有参数的键值对
package study.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 javax.swing.*;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;

@WebServlet("/request3")
public class RequestDemo3 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(req.getParameter("username"));
        System.out.println("================");
        for (String hobby : req.getParameterValues("hobby")) {
            System.out.println(hobby);
        }
        System.out.println("===================");
        Enumeration<String> parameterNames = req.getParameterNames();
        while(parameterNames.hasMoreElements()){
            String name = parameterNames.nextElement();
            for (String value : req.getParameterValues(name)) {
                System.out.println(value);
            }
        }
        System.out.println("===================");
        Map<String, String[]> parameterMap = req.getParameterMap();
        for (String key : parameterMap.keySet()) {
            String[] value = parameterMap.get(key);
            System.out.println(key + " === " + value);
        }
    }

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

获取请求参数并封装对象

  1. 手动封装方式

    成员变量名称和参数name属性值保持一致

  2. 反射封装方式

    属性描述器:PropertyDescriptor(根据名称获取到对象中对应的get和set方法)

  3. 工具类封装方式

    beanutils工具类,populate方法

    在发布之前,还需要进入File-Project Structure

工具栏封装对象.png

package study.servlet;

import study.servlet.bean.Student;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

/*
*   封装对象------反射方式
* */
public class RequestDemo5 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取所有数据
        Map<String, String[]> map = req.getParameterMap();
        // 封装学生对象
        Student stu = new Student();
        // 遍历集合
        for(String name : map.keySet()){
            String[] value = map.get(name);
            try {
                // 获取Student对象的属性描述器
                PropertyDescriptor pd = new PropertyDescriptor(name, stu.getClass());
                // 获取对应的set方法
                Method writeMethod = pd.getWriteMethod();
                // 执行方法
                writeMethod.invoke(stu,value);
            } catch (IntrospectionException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}
package study.servlet;

import org.apache.commons.beanutils.BeanUtils;
import study.servlet.bean.Student;

/*
*   封装对象------工具类方式
	需要导包:BeanUtils
* */
public class RequestDemo6 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取所有数据
        Map<String, String[]> map = req.getParameterMap();
        // 封装学生对象
        Student stu = new Student();
        try {
            BeanUtils.populate(stu, map);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

流对象获取请求信息

返回值方法名说明
BufferedReadergetReader()获取字符输入流
ServletInputStreamgetInputStream()获取字节输入流

用IO流获取请求信息时,不支持get方式,只支持post提交方式

获得到的流对象都不是自己new出来的,不需要close释放资源,会由请求对象处理并释放

package study.servlet;

import org.apache.commons.beanutils.BeanUtils;
import study.servlet.bean.Student;

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
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;
import java.util.Map;

/*
*   流对象获取数据
* */
@WebServlet("/request7")
public class RequestDemo7 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 字符流(必须是post请求方式)
        BufferedReader br = req.getReader();
        String line;
        while((line = br.readLine()) != null) System.out.println(line);
        
        // 字节流
        ServletInputStream is = req.getInputStream();
        byte[] arr = new byte[1024];
        int len;
        while((len = is.read(arr)) != -1){
            System.out.println(new String(arr, 0, len));
        }
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

中文乱码问题

  • GET方式

    没有乱码问题,在Tomcat 8 版本后已经解决

  • POST方式

    有乱码问题,可以通过 setCharacterEncoding() 方法来解决(编码格式要和页面编码格式一致)

package study.servlet;

/*
*   中文乱码问题
* */
@WebServlet("/request8")
public class RequestDemo8 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        String name = req.getParameter("name");
        System.out.println(name);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

请求域

  • 请求域(request域):可以在一次请求范围内进行共享数据。

  • 一般用于请求转发的多个资源中共享数据

  • 请求对象操作共享数据方法

    返回值方法名说明
    voidsetAttribute(String name, Object value)向请求域对象中存储数据
    ObjectgetAttribute(String name)通过名称获取请求域对象中的数据
    voidremoveAttribute(String name)通过名称移除请求域对象中的数据

请求转发

  • 请求转发:客户端的一次请求到达以后,发现需要借助其他 Servlet 来实现功能(浏览器请求,A发现做不了,转发给B去做)

  • 特点

    • 浏览器地址栏不变

    • 域对象中的数据不丢失

    • 负责转发的Servlet 转发前后的响应正文会丢失

    • 由转发的目的地来响应客户端

返回值方法名说明
RequestDispatchergetRequestDispatcher(String name)获取请求调度对象
voidforward(ServletRequest req, ServletResponse resp)实现转发(用请求调度对象调用)
package study.servlet.request;

/*
*   请求转发
* */
@WebServlet("/request9")
public class RequestDemo9 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置共享数据
        req.setAttribute("name","张三");
        // 获取请求调度对象
        RequestDispatcher rd = req.getRequestDispatcher("/request10");
        // 请求转发
        rd.forward(req, resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}
package study.servlet.request;

/*
*   转发目的
* */
@WebServlet("/request10")
public class RequestDemo10 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取共享数据
        System.out.println(req.getAttribute("name"));
    }

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

请求包含

  • 请求包含:可以合并其他Servlet 中的功能一起响应给客户端(浏览器请求,A只能做一半,另一半让B做)

  • 特点

    • 浏览器地址栏不变

    • 域对象中的数据不丢失

    • 被包含的 Servlet 响应头会丢失

返回值方法名说明
RequestDispatchergetRequestDispatcher(String name)获取请求调度对象
voidinclude(ServletRequest req, ServletResponse resp)实现包含
package study.servlet.request;

/*
*   请求包含
* */
@WebServlet("/request11")
public class RequestDemo11 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置共享数据
        req.setAttribute("name","张三");
        // 获取请求调度对象
        RequestDispatcher rd = req.getRequestDispatcher("/request10");
        // 请求转发
        rd.include(req, resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

响应对象

  • 响应:回馈结果。在 BS 架构中,就是服务器给客户浏览器反馈结果

  • 响应对象:就是在项目中用于发送响应的对象

    ServletResponse(接口)

    HttpServletResponse(继承自ServletResponse,基于http协议的接口)

    和请求对象一样,不需要我们去写实现类,在Tomcat 服务器创建好,在执行 doGet 或者 doPost 方法时,服务器会把相应的实现类对象传递

常见状态码

状态码说明
200成功
302重定向
304请求资源未改变,使用缓存
400请求错误,常见于请求参数错误
404请求资源未找到
405请求方式不支持
500服务器错误

字节流响应消息

返回值方法名说明
ServletOutputStreamgetOutputStream()获取响应字节输出流对象
voidsetContentType("text/html;charset=UTF-8")设置响应内容类型,解决中文乱码问题

步骤:

  1. 获取字节输出流对象

  2. 定义一个消息(一个字符串)

  3. 通过字节流对象输出

获取到的字节输出流对象不需要close释放,会由响应对象处理并释放

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取字节输出流
        ServletOutputStream os = resp.getOutputStream();
        String s = "字节输出流响应消息";
        os.write(s.getBytes());
}

未出现乱码问题:浏览器默认gbk编码,idea默认UTF-8编码;但是getBytes方法在将字符串转为字节数组时,如果不传递参数指定编码,就会根据当前系统平台默认编码进行转换,Windows系统默认编码为gbk,和浏览器一致,故未出现乱码

// 统一编码格式为UTF-8并解决乱码问题
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	// 设置相应内容类型,并设置编码格式(告知浏览器应该采用的编码格式)
	resp.setContentType("text/html;charset=UTF-8");
	// 获取字节输出流
	ServletOutputStream os = resp.getOutputStream();
	String s = "字节输出流响应消息";
	os.write(s.getBytes("UTF-8"));
}

字符流响应消息

返回值方法名说明
PrintWritergetWriter()获取响应字符输出流对象
voidsetContentType("text/html;charset=UTF-8")设置响应内容类型,解决中文乱码问题

步骤和上面字节流一样,同样不需要自己close释放资源

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	// 设置相应内容类型,并设置编码格式(告知浏览器应该采用的编码格式)
	resp.setContentType("text/html;charset=UTF-8");
	// 获取字符输出流对象
	PrintWriter pw = resp.getWriter();
	pw.write("字符输出流响应消息");
}

响应图片

  1. 通过文件的相对路径获取绝对路径(getRealPath)

  2. 创建字节输入流对象,关联读取的图片路径

  3. 通过响应对象获取字节输出流对象

  4. 循环读取和写出图片

@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String realPath = getServletContext().getRealPath("/img/tx.png");
        // 创建字节输入流对象,关联图片
        BufferedInputStream is = new BufferedInputStream(new FileInputStream(realPath));
        // 获取字节输出流对象,响应图片
        ServletOutputStream os = resp.getOutputStream();
        // 循环读写
        byte[] arr = new byte[1024];
        int len;
        while((len = is.read(arr)) != -1){
            os.write(arr, 0, len);
        }
        // 释放资源
        is.close();
    }

设置缓存

  • 缓存:对于不经常变化的数据,我们可以设置合理的缓存时间,以避免浏览器频繁请求服务器。以此来提高效率
返回值方法名说明
voidsetDateHeader(String name,long time)设置消息头添加缓存

例:resp.setDateHeader("Expires",System.currentTimeMillis() + 1*60*60*1000); 设置一个小时缓存时间

Expires 就是过期的意思

时间单位为毫秒,1秒等于1000毫秒

定时刷新

  • 定时刷新:过了指定时间后,页面自动进行跳转
返回值方法名说明
voidsetHeader(String name,String value)设置消息头定时刷新

例:resp.setHeader("Refresh","3;URL=要跳转的路径")

单位为秒

请求重定向

  • 请求重定向:客户端的一次请求到达后,发现需要借助其他Servlet 来实现功能

  • 特点:浏览器地址栏会发生改变,两次请求,请求域对象中不能共享数据,可以重定向到其他服务器

  • 重定向实现原理

    1. 设置响应状态码为302:resp.setStatus(302);

    2. 设置响应的资源路径(响应到哪里去,通过响应消息头 location 来指定):resp.setHeader("location","/response/responseDemo")

  • 响应对象重定向方法

    返回值方法名说明
    voidsendRedirect(String name)设置重定向

文件下载

  1. 创建字节输入流,关联读取的文件

  2. 设置响应消息头支持的类型:resp.setHeader("Content-Type","application/octet-stream")

    Content-Type:消息头名称,代表所支持的类型

    application/octet-stream:消息头参数,代表应用的类型为字节流

  3. 设置响应消息头以下载方式打开资源:resp.setHeader("Content-Disposition","attachment;filename=下载的文件名称")

    Content-Disposition:消息头名称,代表处理形式

    attachment;filename=xxx:消息头参数,代表附件的形式进行处理,filename代表指定下载文件的名称

  4. 通过响应对象获取字节输出流对象

  5. 循环读写

  6. 释放资源

@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 创建字节输入流,关联读取的文件
        String realPath = req.getServletContext().getRealPath("/img/tx.png");
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(realPath));
        // 设置响应消息头支持的类型
        resp.setHeader("Content-Type", "application/octet-stream");
        // 设置响应消息头以下载方式打开资源
        resp.setHeader("Content-Disposition","attachment;filename=file.png");
        // 通过响应对象获取字节输出流对象
        ServletOutputStream os = resp.getOutputStream();
        int len;
        byte[] arr = new byte[1024];
        while((len = bis.read(arr)) != -1){
            os.write(arr, 0, len);
        }
        // 释放资源
        bis.close();
    }

学生管理系统2

  • 资源准备

    1. 创建一个 web 项目

    2. 在web 目录下创建一个 index.html,包含两个超链接标签(添加学生、查看学生)

    3. 在 web目录下创建一个 addStudent.html,用于实现添加功能的表单页面

    4. 在 src 下创建一个 Student 类,用于封装学生信息

// list.java
package studentServlet;

import studentServlet.bean.Student;

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.*;
import java.util.ArrayList;
@WebServlet("/list")
public class list extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        BufferedReader br = new BufferedReader(new FileReader("E:\\Java\\code\\StudentServlet\\stu.txt"));
        ArrayList<Student> list = new ArrayList<>();
        String str;
        while((str = br.readLine()) != null){
            String[] split = str.split(",");
            Student stu = new Student(split[0], Integer.parseInt(split[1]), Integer.parseInt(split[2]));
            list.add(stu);
        }
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter pw = resp.getWriter();
        for(Student student : list){
            pw.write(student.getUsername() + "," + student.getAge() + "," + student.getScore());
            pw.write("<br>");
        }
        br.close();
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}
<!--index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>学生信息管理系统</title>
</head>
<body>
    <h1>学生信息管理系统</h1>
    <hr>
    <a href="/studentAdd.html">添加学生信息</a>
    <a href="/list">查看学生信息</a>
</body>
</html>

Cookie

会话

  • 会话:浏览器和服务器之间的多次请求和响应

  • 为了实现一些功能,浏览器和服务器之间可能会产生多次的请求和响应,从浏览器访问服务器开始,到访问服务器结束(关闭浏览器、到了过期时间)。这期间产生的多次请求和响应加在一起就称之为浏览器和服务器之间的一次会话

  • 会话过程所产生的一些数据,可以通过会话技术(Cookie 和 Session)保存。

概述

  • Cookie: 客户端会话管理技术

    把要共享的数据保存到客户端

    每次请求时,把会话信息带到服务器端,从而实现多次请求的数据共享

  • 作用:可以保存客户端访问网站的相关内容,从而保证每次访问时先从本地缓存中获取,以此提高效率

  • 特点:

    是一个普通的Java类

    两个必须属性:name 和value

    发送:HttpServletResponse.addCookie(Cookie对象)

    每个网站最多20个Cookie,浏览器存储的Cookie总数不大于300个,每个Cookie大小限制在4kb

    获取所有Cookie对象:HttpServletRequest.getCookie()

Cookie属性

属性名作用是否重要
nameCookie的名称必须属性
valueCookie的值(不支持中文)必须属性
pathCookie的路径重要
domainCookie的域名重要
maxAgeCookie的存活时间(s)重要
versionCookie的版本号不重要
commentCookie的描述不重要

方法

方法名作用
Cookie(String name, String value)构造方法创建对象
属性对应的set和get方法赋值和获取值(name有final修饰,无set方法)
  • 向客户端添加Cookie:void HttpServletResponse.addCookie(Cookie cookie)

  • 获取所有的Cookie:Cookie[] HttpServletRequest.getCookies()

练习

  • 需求说明:通过Cookie记录最后访问时间,并在浏览器上显示出来

  • 最终目的:掌握Cookie的基本使用,从创建到添加客户端,再到从服务器端获取

  • 实现步骤

    1. 通过响应对象写出一个提示信息

    2. 创建Cookie对象,指定name和value

    3. 设置Cookie最大存活时间

    4. 通过响应对象将Cookie对象添加到客户端

    5. 通过请求对象获取Cookie对象

    6. 将Cookie对象中的访问时间写出

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	// 通过响应对象写出一个提示信息
	resp.setContentType("text/html;charset=UTF-8");
	PrintWriter pw = resp.getWriter();
	pw.write("您的最后访问时间为:");
	pw.write("<br>");
	// 创建Cookie对象,指定name和value
	Cookie cookie = new Cookie("time", System.currentTimeMillis()+"");
	// 设置Cookie最大存活时间
	cookie.setMaxAge(3600);
	// 通过响应对象将Cookie对象添加到客户端
	resp.addCookie(cookie);
	// 通过请求对象获取Cookie对象
	Cookie[] cookies = req.getCookies();
	// 将Cookie对象中的访问时间写出
	for(Cookie ck : cookies){
		if("time".equals(ck.getName())){
			String value = ck.getValue();
			// 格式化时间
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			String lastTime = sdf.format(new Date(Long.parseLong(value)));
			pw.write(lastTime);
		}
	}
}

注意事项

  • 数量限制

    每个网站最多只能用20个Cookie,且大小不能超过4kb,所有网站的Cookie总数不超过300个

  • 名称限制

    Cookie的名称只能包含ASCII码表中的字母、数字字符。不能包含逗号、分号、空格,不能以$开头。

    Cookie的值不支持中文

  • 存活时间限制setMaxAge()方法接收数字

    负整数:当前会话有效,浏览器关闭则清除(默认)

    0:立即清除

    正整数:以秒为单位设置存活时间

  • 访问路径限制

    取自第一次访问的资源路径前缀

    只要以这个前缀开头(包括子级路径)就能获取到,反之获取不到

    设置路径:setPath()方法设置指定路径


Session

  • HttpSession:服务器端会话管理技术

    本质也是采用客户端会话管理技术

    只不过在客户端保存的是一个特殊标识,而共享的数据保存到了服务器端的内存对象中。

    每次请求时,会将特殊标识带到服务器端,根据这个标识来找到对应的内存空间,从而实现数据共享

    是Servlet规范中四大域对象之一的会话域对象

  • 作用:可以实现数据共享

    域对象功能作用
    ServletContext应用域在整个应用之间实现数据共享
    ServletRequest请求域在当前的请求或请求转发之间实现数据共享
    HttpSession会话域在当前会话范围之间实现数据共享

常用方法

返回值方法名说明
voidsetAttribute(String name, Object value)设置共享数据
ObjectgetAttribute(String name)获取共享数据
voidremoveAttribute(String name)移除共享数据
StringgetId()获取唯一标识名称
voidInvalidate()让session立即失效

对象获取

  • HttpSession 是一个接口,对应的实现类对象是通过HttpServletRequest 对象来获取

    返回值方法名说明
    HttpSessiongetSession()获取HttpSession对象
    HttpSessiongetSession(boolean create)获取HttpSession对象,未获取到是否自动创建(默认true)

练习

  • 需求说明:通过第一个Servlet 设置共享数据用户名,并在第二个Servlet 获取到

  • 最终目的:掌握HttpSession 的基本使用,如何获取和使用

  • 实现步骤

    1. 在第一个 Servlet 中获取请求的用户名

    2. 获取 HttpSession 对象

    3. 将用户名设置到共享数据中

    4. 在第二个 Servlet 中获取 HttpSession 对象

    5. 获取共享数据用户名

    6. 将获取到的用户名响应给客户端浏览器

// Session01
@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 在第一个 Servlet 中获取请求的用户名
        String username = req.getParameter("username");
        // 获取 HttpSession 对象
        HttpSession session = req.getSession();
        // 将用户名设置到共享数据中
        session.setAttribute("username",username);
    }
// session02
@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 在第二个 Servlet 中获取 HttpSession 对象
        HttpSession session = req.getSession();
        // 获取共享数据用户名
        Object username = session.getAttribute("username");
        // 将获取到的用户名响应给客户端浏览器
        PrintWriter pw = resp.getWriter();
        pw.write(username+"");
    }

注意事项

  • 唯一标识的查看:借助浏览器开发者工具

  • 浏览器禁用Cookie

    方式一:通过提示信息告知用户,大部分网站采用的解决方式【推荐】

    方式二:通过resp.enconeURL方法实现url重写(地址栏拼接jsessionid)【了解】

  • 钝化和活化

    • 钝化:序列化。把长时间不用,但还不到过期时间的HttpSession进行序列化,写到磁盘上

    • 活化:相反的状态

    • 钝化时间

      第一种情况:当访问量很大时,服务器会根据getLastAccessTime 来进行排序,对长时间不用,但还没到过期时间的HttpSession进行序列化

      第二种情况:当服务器进行重启的时候,为了保持客户HttpSession 中的数据,也要对其进行序列化

    HttpSession 的序列化由服务器自动完成,我们无需关心


JSP

  • JSP(Java Server Pages):是一种动态网页技术标准

  • JSP 部署在服务器上,可以处理客户端发送的请求,并根据请求内容动态的生成 HTML、XML 或其他格式文档的 Web网页,然后再响应给客户端。

  • Jsp 是基于Java 语言的,它的本质就是 Servlet

类别使用场景
HTML开发静态资源,无法添加动态资源
CSS美化页面
JavaScript给网页添加一些动态效果
Servlet编写Java 代码,实现后台功能处理
JSP包含了显示页面技术,也具备Java代码功能

快速入门

  1. 创建一个web项目

  2. 在web 目录下创建一个 index.jsp 文件

  3. 在文件中写一句内容为:这是我的第一个jsp

  4. 部署并启动项目

  5. 通过浏览器测试

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    第一个jsp
</body>
</html>

执行过程

  1. 客户端浏览器发送请求到Tomcat 服务器

  2. Tomcat 服务器解析请求地址

  3. 找到具体的应用

  4. 找到对应的jsp文件

  5. 将jsp文件翻译为.java的文件

  6. 编译Java文件

  7. 响应给客户端

文件内容介绍

  • 生成的Java 文件目录

    继承自 HttpJspBase类,而HttpJspBase继承自 HttpServlet(JSP本质上就是一个 Servlet)

    jsp显示页面本质上就是通过获取输出流对象并通过write写出

语法

  • JSP注释:<%-- 注释的内容 --%>

  • Java代码块:<% Java代码 %>

  • JSP表达式:<%=表达式%>

  • JSP声明:<%! 声明变量或方法 %>

System.out.println():普通输出语句,输出在控制台上

out.println():JspWriter 对象,将内容输出在浏览器页面上,不会自动换行

<%="要输出的内容"%>就相当于out.println("要输出的内容")

在声明中,如果加!,代表声明的是成员变量;如果不加!,代表声明的是局部变量;如果是声明方法,就必须加!

指令

  • page 指令:<%@ page 属性名=属性值 属性名=属性值... %>

    属性名作用
    contentType响应正文支持的类型和设置编码格式
    language使用的语言,默认是Java
    errorPage当前页面出现异常后跳转的页面
    isErrorPage是否抓住异常,如果为true则页面中就可以使用异常对象,默认是false
    import导包 import= "java.util.ArrayList"
    session是否创建 HttpSession 对象,默认是true
    buffer设定 JspWriter 输出jsp内容缓存的大小,默认8kb
    pageEncoding翻译jsp时所用的编码格式
    isEIgnored是否忽略EL表达式,默认是false
  • include 指令:可以包含其他页面

    <%@ include file=包含的页面 %>

  • taglib 指令:可以引入外部标签库

    <%@ taglib uri=标签库的地址 prefix=前缀名称 %>

注意事项

  • 九大隐式对象(不用创建,可以直接使用)

    隐式对象名称代表实际对象
    requestjavax.servlet.http.HttpServletRequest
    responsejavax.servlet.http.HttpServletResponse
    sessionjavax.servlet.http.HttpSession
    applicationjavax.servlet.ServletContext
    pagejava.lang.Object
    configjavax.servlet.ServletConfig
    exceptionjava.lang.Throwable
    outjavax.servlet.jsp.JspWriter
    pageContextjavax.servlet.jsp.PageContext
  • PageContext 对象

    是 JSP 独有的, Servlet 中没有

    是四大域对象之一的页面域对象,还可以操作其他三个域对象中的属性

    还可以获取其他八个隐式对象

    生命周期是随着 JSP 的创建而存在,随着 JSP 的结束而消失。每个JSP 页面都有一个 PageContext 对象

四大域对象

域对象名称范围级别备注
PageContext页面范围最小,只能在当前页面使用因范围太小,开发中用的很少
ServletRequest请求范围一次请求或当前请求转发用请求转发之后,再次转发时请求域丢失
HttpSession会话范围多次请求数据共享时使用多次请求共享数据,但不同的客户端不能共享
ServletContext应用范围最大,整个应用都可以使用尽量少用,如果对数据有修改需要做同步处理

MVC模型

  • *M(Model):模型。*用于封装数据,封装的是数据模型

  • *V(View):视图。*用于显示数据,动态资源用 JSP页面,静态资源用 HTML 页面

  • *C(Controller):控制器。*用于处理请求和响应,例如 Servlet

MVC.png


学生管理系统3

登录功能

  1. 创建一个web目录

  2. 在web目录下创建一个index.jsp

  3. 在页面中获取会话域中的用户名,获取到了就显示添加和查看功能的超链接,没获取到就显示登录功能的超链接

  4. 在web目录下创建一个login.jsp。实现登录页面

  5. 创建 LoginServlet,获取用户名和密码

  6. 如果用户名为空,则重定向到登录页面

  7. 如果不为空,将用户名添加到会话域中,再重定向到首页

<%-- login.jsp --%>
<%--
  Created by IntelliJ IDEA.
  User: lihao
  Date: 2022/2/24
  Time: 20:25
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<h1>学生信息管理系统--登录</h1>
<hr>
<form action="/login" method="post">
    用户名:<input type="text" name="username">
    密码:<input type="password" name="password">
    <button type="submit">登录</button>
</form>
</body>
</html>
// login.java
package studentServlet;

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 javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/login")
public class login extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        resp.setContentType("text/html;charset=UTF-8");
        if(null == username || "".equals(username)){
            resp.getWriter().write("账号不能为空,请重新输入(2s后返回...)");
            resp.setHeader("Refresh","2;URL=/login.jsp");
        }else{
            if("admin".equals(username) && "abc123".equals(password)){
                session.setAttribute("username",username);
                resp.sendRedirect("/index.jsp");
            }else{
                resp.getWriter().write("账号密码不正确,请重新输入(2s后返回...)");
                resp.setHeader("Refresh","2;URL=/login.jsp");
            }
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}
<%-- index.jsp --%>
<%--
  Created by IntelliJ IDEA.
  User: lihao
  Date: 2022/2/24
  Time: 19:58
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>学生信息管理系统</title>
</head>
<body>
    <h1>学生信息管理系统</h1>
    <hr>
    <% if(session.getAttribute("username") == null){ %>
    <a href="/login.jsp">请登录</a>
    <% } else { %>
    <a href="/add.jsp">添加学生信息</a>
    <a href="/list">查看学生信息</a>
    <% } %>
</body>
</html>
<%-- list.jsp --%>
<%@ page import="java.util.ArrayList" %>
<%@ page import="studentServlet.bean.Student" %>
<%--
  Created by IntelliJ IDEA.
  User: lihao
  Date: 2022/2/24
  Time: 20:30
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>查看学生</title>
</head>
<body>
    <table width="600px" border="1px" align="center">
        <tr>
            <th>姓名</th>
            <th>年龄</th>
            <th>成绩</th>
        </tr>
        <%
            ArrayList<Student> students = (ArrayList<Student>) session.getAttribute("students");
            for(Student stu : students){
        %>
            <tr>
                <td><%=stu.getUsername()%></td>
                <td><%=stu.getAge()%></td>
                <td><%=stu.getScore()%></td>
            </tr>
        <% } %>
    </table>
</body>
</html>
// list.java
package studentServlet;

import studentServlet.bean.Student;

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.*;
import java.util.ArrayList;
@WebServlet("/list")
public class list extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        BufferedReader br = new BufferedReader(new FileReader("E:\\Java\\code\\StudentServlet\\stu.txt"));
        ArrayList<Student> list = new ArrayList<>();
        String str;
        while((str = br.readLine()) != null){
            String[] split = str.split(",");
            Student stu = new Student(split[0], Integer.parseInt(split[1]), Integer.parseInt(split[2]));
            list.add(stu);
        }
        req.getSession().setAttribute("students",list);
        resp.sendRedirect("/list.jsp");
    }

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

EL表达式

  • EL(Expression Language):表达式语言

  • 在 JSP 2.0 规范中加入的内容,也是 Servlet 规范的一部分

  • 作用:在 JSP 页面中获取数据,让我们的 JSP 脱离 Java代码块和 JSP 表达式

  • 语法:${表达式内容}

快速入门

  1. 创建一个web 项目

  2. 在web 目录下创建 jsp文件

  3. 在文件中向域对象添加数据

  4. 使用三种方式获取域对象中的数据(Java代码块、JSP表达式、EL表达式)

  5. 部署并启动项目

  6. 通过浏览器测试

<%--
  Created by IntelliJ IDEA.
  User: lihao
  Date: 2022/2/25
  Time: 10:04
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>EL表达式快速入门</title>
</head>
<body>
    <% request.setAttribute("username","zhangsan"); %>

    <%--java代码块--%>
    <% out.println(request.getAttribute("username")); %><br>
    <%-- jsp表达式 --%>
    <%=request.getAttribute("username")%><br>
    <%--EL表达式--%>
    ${username}
</body>
</html>

获取数据

  1. 获取基本数据类型的数据

    ${数据名}

  2. 获取自定义对象类型的数据

    ${对象名.属性名}

    这里获取到对象的成员变量的原理是通过调用get方法获取,所以不必担心private私有问题

  3. 获取数组类型的数据

    ${数组名[索引]}

  4. 获取List 集合类型的数据

    ${集合[索引]}

  5. 获取 Map 集合类型的数据

    ${集合.key值}:获取key对应的value

<%@ page import="study.servlet.bean.Student" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.HashMap" %><%--
  Created by IntelliJ IDEA.
  User: lihao
  Date: 2022/2/25
  Time: 10:10
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>EL表达式获取数据</title>
</head>
<body>
    <%
        Student stu = new Student("张三",23);
        int[] arr = {1,2,3,4,5};
        ArrayList<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        HashMap<String,String> map = new HashMap<>();
        map.put("user","zhangsan");
        map.put("age","28");
        pageContext.setAttribute("stu",stu);
        pageContext.setAttribute("arr",arr);
        pageContext.setAttribute("list",list);
        pageContext.setAttribute("map",map);
    %>
    <%--EL表达式获取数据--%>
    ${stu.name}<br>     <%--获取自定义对象类型的数据--%>
    ${arr[2]}<br>       <%--获取数组类型的数据--%>
    ${list[1]}<br>      <%--获取List 集合类型的数据--%>
    ${map.user}         <%--获取 Map 集合类型的数据--%>
</body>
</html>

注意事项

  • EL 表达式没有空指针异常

  • EL 表达式没有索引越界异常

  • EL 表达式没有字符串的拼接

使用细节

  • EL 表达式能够获取到四大域对象的数据,根据名称从小到大在域对象中查找

  • 还可以获取 JSP 其他八个隐式对象,并调用对象中的方法

运算符

  • 关系运算符

    运算符作用示例结果
    == 或 eq等于${5 == 5} 或 ${5 eq 5}true
    != 或 ne不等于${5 != 5} 或 ${5 ne 5}false
    < 或 lt小于${3 < 5} 或 ${3 lt 5}true
    > 或 gt大于${3 > 5} 或 ${3 gt 5}false
    <= 或 le小于等于${3 <= 5} 或 ${3 le 5}true
    >= 或 ge大于等于${3 >= 5} 或 ${3 ge 5}false
  • 逻辑运算符

    运算符作用示例结果
    && 或 and并且${A && B} 或 ${A and B}true / false
    || 或 or或者${A || B} 或 ${A or B}true / false
    ! 或 not取反${!A} 或 ${not A}true / false
  • 其他运算符

    运算符作用
    empty1. 判断对象是否为null
    2. 判断字符串是否为空字符串
    3. 判断容器元素是否为0
    条件 ? 表达式1 : 表达式2三元运算符

隐式对象

隐式对象名称对应JSP隐式对象说明
pageContextpageContext功能完全相同
applicationScope操作应用域对象数据
sessionScope操作会话域对象数据
requestScope操作请求域对象数据
pageScope操作页面域对象数据
header获取请求头数据
headerValues获取请求头数据(多个值)
param获取请求参数数据
paramValues获取请求参数数据(多个值)
initParam获取全局配置参数数据
cookie获取Cookie对象
<%--
  Created by IntelliJ IDEA.
  User: lihao
  Date: 2022/2/25
  Time: 10:21
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>隐式对象</title>
</head>
<body>
    <%--pageContext对象,可以获取其他三个域对象和JSP中八个隐式对象--%>
    ${pageContext.request.requestURL}<br>

    <%--applicationScope sessionScope requestScope pageScope 操作四大域对象中的数据--%>
    ${pageContext.setAttribute("username","zhangsan")}
    ${pageScope.username}<br>

    <%--header headerValues 获取请求头数据--%>
    ${header["connection"]}
    ${headerValues["connection"][0]}
    ${header.connection}<br>

    <%--param paramValues 获取请求参数数据--%>
    ${param.username}
    ${paramValues.hobby[0]}<br>

    <%--initParam 获取全局配置参数--%>
    ${initParam.globaldesc}<br>

    <%--cookie 获取cookie信息--%>
    ${cookie}   <%--直接写cookie获取到的是一个map集合--%>
    <br>
    ${cookie.JSESSIONID.value}
</body>
</html>

JSTL

  • JSTL(Java Server Pages Standarded Tag Library):JSP 标准标签库

  • 主要提供给开发人员一个标准通用的标签库

  • 开发人员可以利用这些标签来取代 JSP 页面上的Java代码,从而提高程序的可读性,降低程序的维护难度

组成作用说明
core核心标签库通用的逻辑处理
fmt国际化不同地域显示不同语言
functionsEL 函数EL 表达式可以使用的方法
sql操作数据库了解
xml操作XML了解

核心标签

标签名称功能分类属性作用
<标签名:if>流程控制核心标签库用于条件判断
<标签名:choose>
<标签名:when>
<标签名:otherwise>
流程控制核心标签库用于多条件判断
<标签名:forEach>迭代遍历核心标签库用于循环遍历

使用步骤

  1. 创建一个 web 项目

  2. 在 web目录下创建一个 WEB-INF 目录

  3. 在 WEB-INF 目录下创建一个 libs 目录,将 JSTL 的 jar 包导入

  4. 创建 JSP 文件,通过 taglib 导入 JSTL 标签库

  5. 对流程控制和迭代遍历的标签进行使用

  6. 部署并启动项目

  7. 通过浏览器查看

<%@ page import="java.util.ArrayList" %><%--
  Created by IntelliJ IDEA.
  User: lihao
  Date: 2022/2/25
  Time: 10:45
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%--导入核心库并起标签名--%>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
<html>
<head>
    <title>JSTL【JSP标准标签库】</title>
</head>
<body>
    ${pageContext.setAttribute("score","A")}

    <%--对成绩进行判断--%>
    <c:if test="${score eq 'A'}">
        优秀
    </c:if>
    <hr>

    <%--多条件判断--%>
    <c:choose>
        <c:when test="${score eq 'A'}">优秀</c:when>
        <c:when test="${score eq 'B'}">良好</c:when>
        <c:when test="${score eq 'C'}">及格</c:when>
        <c:when test="${score eq 'D'}">较差</c:when>
        <c:otherwise>成绩非法</c:otherwise>
    </c:choose>
    <hr>

    <%--循环遍历--%>
    <%
        ArrayList<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");
        pageContext.setAttribute("list",list);
    %>
    <c:forEach items="${list}" var="str">
        ${str}<br>
    </c:forEach>
</body>
</html>

Filter

  • 在程序中访问服务器资源时,当一个请求到来,服务器首先判断是否有过滤器与请求资源相关联,如果有,过滤器可以将请求拦截下来,完成一些特定的功能,再由过滤器决定是否交给请求资源。如果没有相关联的过滤器则像之前那样直接请求资源了。响应也是类似的

  • 过滤器一般完成用于通用的操作,例如:登录验证、统一编码处理、敏感字符过滤等等......

概述

  • 是一个接口。如果想实现过滤器的功能,必须实现该接口

  • 核心方法

    返回值方法名作用
    voidinit(FilterConfig config)初始化方法
    voiddoFilter(ServletRequest request, ServletResponse response, FilterChain chain)对请求资源和响应资源过滤
    voiddestroy()销毁方法
  • 配置方式

    方式一:配置文件(web.xml)

    方式二:注解方式

FilterChain

  • FilterChain 是一个接口,代表过滤器链对象。由Servlet 容器提供实现类对象。直接使用即可

  • 过滤器可以定义多个,就会组成过滤器链

  • 核心方法

    返回值方法名说明
    voiddoFilter(ServletRequest request, ServletResponse response)放行方法

    如果有多个过滤器,在第一个过滤器中调用下一个过滤器,依此类推。直到到达最终访问资源。

    如果只有一个过滤器,放行时,就会直接到达最终访问资源

过滤器使用

  • 需求说明:通过 Filter 过滤器解决多个资源写出中文乱码的问题

  • 实现步骤

    1. 创建一个 web 项目

    2. 创建两个 Servlet 功能类,都向客户端写出中文数据

    3. 创建一个 Filter 过滤器实现类,重写 doFilter 核心方法

    4. 在方法内解决中文乱码,并放行

    5. 部署并启动项目

    6. 通过浏览器测试

package study.servlet.filter;


import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/filter01")
public class filter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("filter执行了");
        // 处理乱码
        servletResponse.setContentType("text/html;charset=UTF-8");
        // 放行
        filterChain.doFilter(servletRequest,servletResponse);
    }
    @Override
    public void destroy() {}
}

使用细节

  • 配置方式

    注解方式:@WebFilter(拦截路径)

    配置文件方式

    <!--声明-->
    <filter>
    	<filter-name>demo</filter-name>
        <filter-class>全类名</filter-class>
    </filter>
    <!--映射-->
    <filter-mapping>
    	<filter-name>demo</filter-name>
        <url-pattern>/拦截路径</url-pattern>
    </filter-mapping>
    
  • 多个过滤器使用顺序

    如果有多个过滤器,取决于过滤器映射的顺序

生命周期

  • 创建

    当应用加载时实例化对象并执行init初始化方法

  • 服务

    对象提供服务的过程,执行 doFilter 方法

  • 销毁

    当应用卸载时或服务器停止时对象销毁。执行 destroy 方法

FilterConfig

  • FilterConfig 是一个接口。代表过滤器的配置对象,可以加载一些初始化参数

  • 核心方法

    返回值方法名作用
    StringgetFilterName()获取过滤器对象名称
    StringgetInitParameter(String name)根据name获取 value
    Enumeration<String>getInitParameterNames()获取所有参数的key
    ServletContextgetServletContext()获取应用上下文对象
    <filter>
        <filter-name>demo</filter-name>
        <filter-class>全类名</filter-class>
        
        <init-param>
            <param-name>username</param-name>
            <param-value>zhangsan</param-value>
        </init-param>
    </filter>
    

五种拦截行为

  • Filter 过滤器默认拦截的是请求,但是在实际开发中,我们还有请求转发和请求包含,以及由服务器触发调用的全局错误页面。默认情况下过滤器是不参与过滤的,要想使用,就需要我们配置

  • 拦截方式

    <!--声明-->
    <filter>
    	<filter-name>demo</filter-name>
        <filter-class>全类名</filter-class>
    </filter>
    <!--映射-->
    <filter-mapping>
    	<filter-name>demo</filter-name>
        <url-pattern>/拦截路径</url-pattern>
        
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>INCLUDE</dispatcher>
        <dispatcher>ERROR</dispatcher>
        <dispatcher>ASYNC</dispatcher>
    </filter-mapping>
    

    REQUEST:默认值,浏览器直接请求的资源会被过滤器拦截

    FORWARD:转发访问资源会被过滤器拦截

    INCLUDE:包含访问资源

    ERROR:全局错误跳转资源

    ASYNC:异步访问资源

全局错误页面配置

<error-page>
    <!--根据异常类型配置-->
    <exception-type>java.lang.exception</exception-type>
    <location>/error.jsp</location>
</error-page>
<error-page>
    <!--根据状态码配置-->
	<error-code>404</error-code>
    <location>/error.jsp</location>
</error-page>

Listener

  • 观察者设计模式,所有的监听器都是基于观察者设计模式的

  • 三个组成部分

    1. 事件源:触发事件的对象

    2. 事件:触发的动作,封装了事件源

    3. 监听器:当事件源触发事件后,可以完成功能

  • 在程序当中,我们可以对:对象的创建销毁、域对象中属性的变化、会话相关内容进行监听

  • Servlet 规范中共计 8 个监听器,监听器都是以接口形式提供,具体功能需要我们自己来完成

监听器

监听对象的创建和销毁的监听器

  • ServletContextListener:用于监听 ServletContext 对象的创建和销毁

  • 核心方法

    返回值方法名作用
    voidcontextInitialized(ServletContextEvent sce)对象创建时执行该方法
    voidcontextDestroyed(ServletContextEvent sce)对象销毁时执行该方法

    参数:ServletContextEvent 代表事件对象

    事件对象中封装了事件源,也就是 ServletContext

    真正的事件指的是创建或销毁 ServletContext 对象的操作

  • HttpSessionListener:用于监听 HttpSession 对象的创建和销毁

  • 核心方法

    返回值方法名作用
    voidsessionCreated(HttpSessionEvent se)对象创建时执行该方法
    voidsessionDestroyed(HttpSessionEvent se)对象销毁时执行该方法

    参数:HttpSessionEvent 代表事件对象

    事件对象中封装了事件源,也就是 HttpSession

    真正的事件指的是创建或销毁 HttpSession 对象的操作

  • ServletRequestListener:用于监听 ServletRequest 对象的创建和销毁

  • 核心方法

    返回值方法名作用
    voidrequestInitialized(ServletRequestEvent sre)对象创建时执行该方法
    voidrequestDestroyed(ServletRequestEvent sre)对象销毁时执行该方法

    参数:ServletRequestEvent 代表事件对象

    事件对象中封装了事件源,也就是 ServletRequest

    真正的事件指的是创建或销毁 ServletRequest 对象的操作

监听域对象属性变化的监听器

  • ServletContextAttributeListener:用于监听 ServletContext 应用域中属性的变化

  • 核心方法

    返回值方法名作用
    voidattributeAdded(ServletContextAttributeEvent scae)域中添加属性时执行该方法
    voidattributeRemoved(ServletContextAttributeEvent scae)域中移除属性时执行该方法
    voidattributeReplaced(ServletContextAttributeEvent scae)域中替换属性时执行该方法

    参数:ServletContextAttributeEvent 代表事件对象

    事件对象中封装了事件源,也就是 ServletContext

    真正的事件指的是添加、移除、替换应用域中属性的操作

  • HttpSessionAttributeListener:用于监听 HttpSession 会话域中属性的变化

  • 核心方法

    返回值方法名作用
    voidattributeAdded(HttpSessionBindingEvent se)域中添加属性时执行该方法
    voidattributeRemoved(HttpSessionBindingEvent se)域中移除属性时执行该方法
    voidattributeReplaced(HttpSessionBindingEvent se)域中替换属性时执行该方法

    参数:HttpSessionBindingEvent 代表事件对象

    事件对象中封装了事件源,也就是 HttpSession

    真正的事件指的是添加、移除、替换会话域中属性的操作

  • ServletRequestAttributeListener:用于监听 ServletRequest 请求域中属性的变化

  • 核心方法

    返回值方法名作用
    voidattributeAdded(ServletRequestAttributeEvent srae)域中添加属性时执行该方法
    voidattributeRemoved(ServletRequestAttributeEvent srae)域中移除属性时执行该方法
    voidattributeReplaced(ServletRequestAttributeEvent srae)域中替换属性时执行该方法

    参数:ServletRequestAttributeEvent 代表事件对象

    事件对象中封装了事件源,也就是 ServletRequest

    真正的事件指的是添加、移除、替换请求域中属性的操作

监听会话相关的感知型监听器

感知型监听器:在定义好之后就可以直接使用,不需要再通过注解或xml文件进行配置

  • HttpSessionBindingListener:用于感知对象和会话域绑定的监听器

  • 核心方法

    返回值方法名作用
    voidvalueBound(HttpSessionBindingEvent event)数据添加到会话域中(绑定)时执行该方法
    voidvalueUnbound(HttpSessionBindingEvent event)数据从会话域中移除(解绑)时执行该方法

    参数:HttpSessionBindingEvent 代表事件对象

    事件对象中封装了事件源,也就是 HttpSession

    真正的事件指的是添加、移除会话域中数据的操作

  • HttpSessionActivationListener:用于感知会话域对象钝化和活化的监听器

  • 核心方法

    返回值方法名作用
    voidsessionWillPassivate(HttpSessionEvent se)会话域中数据钝化时执行该方法
    voidsessionDidActivate(HttpSessionEvent se)会话域中数据活化时执行该方法

    参数:HttpSessionEvent 代表事件对象

    事件对象中封装了事件源,也就是 HttpSession

    真正的事件指的是会话域中数据钝化、活化的操作

监听器的使用

  • 监听对象的

    ServletContextListener

    HttpSessionListener

    ServletRequestListener

package study.servlet.listener;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.annotation.WebServlet;

@WebServlet("/listener01")
@WebListener
public class listener01 implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("监听到了对象的创建");
        ServletContext servletContext = servletContextEvent.getServletContext();
        // 添加属性
        servletContext.setAttribute("username","张三");
        // 替换属性
        servletContext.setAttribute("username","李四");
        // 移除属性
        servletContext.removeAttribute("username");
        
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("监听到了对象的销毁");
    }
}
  • 监听属性变化的

    ServletContextAttributeListener

    HttpSessionAttributeListener

    ServletRequestAttributeListener

package study.servlet.listener;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;

@WebServlet("/listener02")

public class listener02 implements ServletContextAttributeListener {
    @Override
    public void attributeAdded(ServletContextAttributeEvent servletContextAttributeEvent) {
        System.out.println("监听到了属性的添加");
        ServletContext servletContext = servletContextAttributeEvent.getServletContext();
        Object username = servletContext.getAttribute("username");
        System.out.println(username);
    }

    @Override
    public void attributeRemoved(ServletContextAttributeEvent servletContextAttributeEvent) {
        System.out.println("监听到了属性的移除");
        ServletContext servletContext = servletContextAttributeEvent.getServletContext();
        Object username = servletContext.getAttribute("username");
        System.out.println(username);
    }

    @Override
    public void attributeReplaced(ServletContextAttributeEvent servletContextAttributeEvent) {
        System.out.println("监听到了属性的替换");
        ServletContext servletContext = servletContextAttributeEvent.getServletContext();
        Object username = servletContext.getAttribute("username");
        System.out.println(username);
    }
}
  • 会话相关的感知型

    HttpSessionBindingListener

    HttpSessionActivationListener

配置监听器

  • 注解方式:@WebListener

  • xml文档方式

    <listener>
    	<listener-class>监听器对象实现类的全路径</listener-class>
    </listener>
    

学生管理系统优化

解决乱码

使用过滤器实现所有资源的编码统一

  1. 将请求和响应对象转换为和HTTP相关的HttpServletRequest和HttpServletResponse

  2. 设置编码格式

  3. 放行

package studentSystem.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter("/*")
public class EncodingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 将请求和响应对象转换为和HTTP相关的HttpServletRequest和HttpServletResponse
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        // 设置编码格式
        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        // 放行
        filterChain.doFilter(request, response);
    }
}

检查登录

使用过滤器解决登录检查

  1. 将请求和响应对象转换为和HTTP相关的HttpServletRequest和HttpServletResponse

  2. 获取会话域对象中的数据

  3. 判断用户名

  4. 重定向(或定时刷新)到登录页面或放行

注解配置过滤器时指定多个拦截路径

@WebFilter(value = {"/拦截路径一", "/拦截路径二", ...})

package studentSystem.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebFilter(value = {"/add.jsp", "/list.jsp"})
public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 将请求和响应对象转换为和HTTP相关的HttpServletRequest和HttpServletResponse
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        // 获取会话域对象中的数据
        HttpSession session = request.getSession();
        Object username = session.getAttribute("username");
        Object password = session.getAttribute("password");
        // 判断用户名
        if("admin".equals(username) && "abc123".equals(password)){
            filterChain.doFilter(request, response);
        }else{
            // 输出提示信息并设置定时刷新到登录页面
            response.getWriter().write("您还未登录,请登录后再试。(2s后为您跳转到登录页面)");
            response.setHeader("Refresh","2:URL=/login.jsp");
        }
    }
}

优化JSP页面

通过EL表达式和JSTL替换之前的Java代码块和JSP表达式

完整代码github.com/HelloCode66…