侧边栏壁纸
博主头像
GabrielxD

列車は必ず次の駅へ。では舞台は?私たちは?

  • 累计撰写 471 篇文章
  • 累计创建 108 个标签
  • 累计收到 9 条评论

目 录CONTENT

文章目录

【学习笔记】JavaWeb

GabrielxD
2022-08-21 / 0 评论 / 0 点赞 / 116 阅读 / 19,637 字 / 正在检测是否收录...

JavaWeb 介绍

JavaWeb 概念

所有通过 Java 语言编写可以通过浏览器访问的程序的总称,叫 JavaWeb。
JavaWeb 是基于请求和响应来开发的。

  • 请求:客户端给服务器发送数据的过程被称作请求(Request)
  • 响应:服务器给客户端回传数据的过程被称作响应(Response)
  • 请求和响应的关系:请求和响应是成对出现的,有请求就有响应

Web 资源分类

Web 资源按实现的技术和呈现的效果的不同,分为静态资源和动态资源两种:

  • 静态资源:HTML、CSS、JS、txt 文本、mp4 视频、png 图片、mp3 音频…
  • 动态资源:JSP 页面、Servlet 程序…

Web 网站分类

静态 Web

image-20220831092718149

  • 提供给所有人看数据不会发生变化
  • 仅由静态资源组成
  • 静态网页通过服务器把本地文件原封不动地传给客户机,本身不进行任何处理

动态 Web

image-20220831093802298

  • 不同的人、不同时间、不同地点浏览同一个动态网页,根据代码处理结果不同,会返回不同的内容
  • 一般有用户注册、用户登录等依赖数据交互的更复杂的功能
  • 由静态资源与动态资源组成
  • 动态网页在服务器端动态生成并返回,客户机上看到的只是它的返回结果,一般看不到其源文件
  • 技术栈:ASP、Servlet/JSP、PHP…

常用的 Web 服务器

  • Tomcat:由 Apache 组织提供的一种 Web 服务器,提供对 jsp 和 Servlet 的支持。它是一种轻量级的 javaWeb 容器(服务器),也是当前应用最广的 JavaWeb 服务器(免费)。
  • Jboss:是一个遵从 JavaEE 规范的、开放源代码的、纯 Java 的 EJB 服务器,它支持所有的 JavaEE 规范(免费)。
  • GlassFish:由 Oracle 公司开发的一款 JavaWeb 服务器,是一款强健的商业服务器,达到产品级质量(应用很少)。
  • Resin:是 CAUCHO 公司的产品,是一个非常流行的服务器,对 Servlet 和 JSP 提供了良好的支持,性能也比较优良,resin 自身采用 Java 语言开发(收费,应用比较多)。
  • WebLogic:是 Oracle 公司的产品,是目前应用最广泛的 Web 服务器,支持 JavaEE 规范, 而且不断的完善以适应新的开发要求,适合大型项目(收费,应用不多,适合大公司)。

Tomcat 服务器和 Servlet 版本的对应关系

Servlet 版本 JSP 版本 EL 版本 Apache Tomcat 版本 支持的 Java 版本
6.0 3.1 5.0 10.1.x 11 及以后
5.0 3.0 4.0 10.0.x 8 及以后
4.0 2.3 3.0 9.0.x 8 及以后
3.1 2.3 3.0 8.5.x 7 及以后
3.1 2.3 3.0 8.0.x (废弃) 7 及以后
3.0 2.2 2.2 7.0.x (归档) 6 及以后(7 及以后支持 WebSocket)
2.5 2.1 2.1 6.0.x (归档) 5 及以后
2.4 2.0 N/A 5.5.x (归档) 1.4 及以后
2.3 1.2 N/A 4.1.x (归档) 1.3 及以后
2.2 1.1 N/A 3.3.x (归档) 1.1 及以后

Apache Tomcat® - Which Version Do I Want?

Tomcat

安装

官网:https://tomcat.apache.org/

找到需要的版本下载并解压到安装目录即可。

本文使用 apache-tomcat-8.5.82。

配置环境变量
  • Tomcat 依赖 Java,故需要配置 JAVA_HOME

目录结构

apache-tomcat-8.5.82 ( Directories: 7, Files: 7 )
├─ bin/* – 可执行程序
├─ conf/* – 配置文件
├─ lib/* – 依赖库(jar包)
├─ logs/* – 日志信息
├─ temp/* – 临时文件
├─ webapps/* – 部署的 Web 项目
├─ work/* – 工作目录
├─ BUILDING.txt
├─ CONTRIBUTING.md
├─ LICENSE
├─ NOTICE
├─ README.md
├─ RELEASE-NOTES
└─ RUNNING.txt

启动 Tomcat 服务器

进入 tomcat目录/bin 运行 startup.bat(Windows) / startup.sh(Linux/Mac) 文件即可启动服务器。

打开浏览器,访问 http://localhost:8080 测试启动是否成功。

image-20220831100307056

出现该页即为成功启动。

一些问题

  • 命令行中输出的日志为乱码?
    由于 Windows 默认字符集是 GBK,而日志输出内容是以 UTF-8 编码的,所以会出现乱码。解决方法:在 tomcat安装目录/conf/logging.properties 文件中把 java.util.logging.ConsoleHandler.encoding 的值改为 GBK 即可。
  • 启动 tomcat 服务器闪退?
    一般来说是因为环境变量 JAVA_HOME 没有配置,配置 JAVA_HOME 即可解决。

关闭 Tomcat 服务器

  1. 直接关闭命令行窗口或者按 Ctrl+C 终止命令。
  2. 运行 tomcat目录/bin 下的 shutdown.bat(Windows) / shutdown.sh(Linux/Mac) 即可停止。

配置

在 tomcat目录/conf/server.xml 中可以配置服务器的基本配置,如端口、协议、主机等。

手动部署一个 Web 网站

第一种方法

在 tomcat目录/webapps 下新建一个目录,构建如下结构:

test
├─ test
│ ├─ classes – Java 程序
│ ├─ lib – 依赖库
│ └─ web.xml – 配置文件 *必须
├─ index.html – 默认首页
├─ static/
– 需要引用的静态资源
└─ …

第二种方法

在 tomcat目录/conf/Catalina 下新建一个 xml 文件,文件名与想要使用的路径名相同,比如新建 test.xml,里面的内容:

<Context path="/test" docBase="E:\test">

表示创建一个访问路径为 /test,工程目录为 E:\test 的上下文。

这样如果访问 http://host:port/test 就会访问到 E:\test 目录。

在 IDEA 中添加 Tomcat 运行配置

image-20220831222722307 image-20220831223036224

若没有 Tomcat 模板:

image-20220831222905134

HTTP

详见:【学习笔记】AJAX - HTTP协议

Maven

为什么要学习 Maven

  • 工程中jar包可能出现不统一、不兼容等问题
  • 工程升级维护过程操作繁琐

–> 需要一个管理项目的技术。

简介

  • Maven 的本质是一个项目管理工具,将项目开发与管理过程抽象成 一个项目对象模型(POM)
  • POM(Project Object Model):项目对象模型

image-20220831195028151

作用

  • 项目构建:提供标准的、跨平台的自动化项目构建方式
  • 依赖管理:方便快捷的管理项目依赖的资源(jar包),避免资源间的版本冲突问题
  • 统一开发结构:提供标准的、统一的项目结构

目前仅使用 Maven 方便导入jar包

安装

官网:https://maven.apache.org/

找到需要的版本下载并解压到安装目录即可。

本文使用 apache-maven-3.8.6。

配置环境变量

  • Maven 依赖 Java,故需要配置 JAVA_HOME

  • 设置 Maven 自身的运行环境,需要配置 MAVEN_HOME(Maven 1) 和 M2_HOME(Maven 2)
    image-20220831201122925
    image-20220831201247458

  • 测试配置是否成功:

    mvn -v
    
    image-20220831201617309

配置

在 maven目录/conf/settings.xml 中可以配置。

指定本地仓库

本地仓库默认值:~/.m2/repository,建议修改其位置:

<localRepository>D:/Environments/maven/apache-maven-3.8.6/maven-repo</localRepository>

配置阿里云提供的镜像仓库

Maven 下载jar包默认访问境外的中央仓库,而国外网站速度很慢。改成阿里云提供的镜像仓库,访问国内网站,可以让 Maven 下载jar包的时候速度更快。

Maven 镜像 - 阿里巴巴开源镜像站

<mirror>
    <id>aliyunmaven</id>
    <mirrorOf>*</mirrorOf>
    <name>阿里云公共仓库</name>
    <url>https://maven.aliyun.com/repository/public</url>
</mirror>

在 IDEA 中使用 Maven

设置、配置

首先先修改 IDEA 中的设置:

在设置 > 构建、执行、部署 > 构建工具 > Maven 中修改 Maven 主路径及设置文件和本地仓库路径。

image-20220831214511001

在 Maven > 正在导入 中可以设置自动下载、导入。

image-20220831214545064

创建普通的 Maven 项目

image-20220831220905589

目录结构与 Maven 项目配置文件(pom.xml)的说明:

image-20220901074411791

创建 webapp 模板 Maven 项目

image-20220831221604376

image-20220831224738341

添加 Tomcat 运行配置

[#在 IDEA 中添加 Tomcat 运行配置](#在 IDEA 中添加 Tomcat 运行配置)

成功启动项目

image-20220901075442679

pom.xml 补充说明
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>top.gabrielxd</groupId>
  <artifactId>maven-webapp-test</artifactId>
  <!-- packaging 打包的格式,可以为:pom, jar, maven-plugin, ejb, war, ear, rar, par -->
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <!-- name 声明了一个对用户更为友好的项目名称 -->
  <name>maven-webapp-test Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <!-- dependencies 依赖关系 -->
  <dependencies>
    <dependency>
      <!-- groupId artifactId version 和基本配置中的同名配置意义相同 -->
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <!-- scope 用于限制依赖范围与传播范围,有五个取值: test, compile(默认), runtime, provided, system -->
      <scope>test</scope>
    </dependency>
  </dependencies>
  <!-- 构建配置 -->
  <build>
    <!-- finalName 项目的最终构建名称(不包括文件扩展名) -->
    <finalName>maven-webapp-test</finalName>
  </build>
</project>
一些问题

IDEA 中使用 maven-archetype-webapp 模板生成 webapp 时自动生成的 web.xml 的版本问题

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
</web-app>

自动生成的 web.xml 版本太低,很多特性无法使用,解决方案:

  1. 手动替换为如下的内容

    <?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">
        
    </web-app>
    
  2. 在之前配置的 Maven 本地仓库(localRepository)中找到模板的位置(创建时可以看到,如下):image-20220906151828934 我这里是 %MAVEN_HOME%/maven-repo/org/apache/maven/archetypes/maven-archetype-webapp/1.0/maven-archetype-webapp-1.0.jar
    使用解压软件打开该jar包,在 META-INF 目录中找到 archetype.xml 文件,发现其配置的 web.xml 资源的路径。
    image-20220906152114181

    找到该路径下的 web.xml:
    image-20220906152422373
    修改其内容为:

    <?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">
        
    </web-app>
    

Servlet

简介

  • Servlet 是 JavaWeb 最为核心的内容,它是 Java 提供的一门动态 Web 资源开发技术。
  • Servlet是 JavaEE 规范之一,其实就是一个接口。
  • image-20220901081556181
  • 实现了 Servlet 接口的 Java 程序叫做 Servlet。将来我们需要定义 Servlet 类实现 Servlet 接口,并由 Web 服务器运行 Servlet。

快速入门

需求: 创建 maven-webapp 项目,编写一个 Servlet 类,并使用 IDEA 中 Tomcat 插件进行部署,最终通过浏览器访问所编写的 Servlet 程序。

  1. 创建一个 maven-webapp 项目

  2. 引入 Servlet 依赖坐标
    pom.xml > project

    <dependencies>
        <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    
  3. 在 项目目录/src/main 下新建一个目录 java 并把它标记为源代码根目录
    image-20220901085820518

  4. 在目录下创建包&类:top.gabrielxd.servlet.HelloServlet。
    让该类继承 javax.servlet.http.HttpServlet 并重写 doGet() 方法。
    在方法中从响应对象里获取打印流,通过字符打印流输出一份 HTML 文档。

    package top.gabrielxd.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 HelloServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // 设置响应头中的 Content-Type
            resp.setContentType("text/html");
            resp.setCharacterEncoding("UTF-8");
            // 拿到响应字符打印流
            PrintWriter out = resp.getWriter();
            out.println("<html>");
            out.println("<head>");
            out.println("\t<title>Hello Servlet!</title>");
            out.println("</head>");
            out.println("<body>");
            out.println("\t<h1>Hello Servlet!</h1>");
            out.println("\t<h1>中文测试</h1>");
            out.println("</body>");
            out.println("</html>");
        }
    }
    
    

    image-20220905111349267

    HttpServlet 中已经做好了所有方式的实现,目前简单的使用只需要继承它并重写需要用到的方法即可。

  5. 在 项目目录/src/main/webapp/WEB-INF/web.xml 中配置 Servlet 映射。

    <web-app>
        <display-name>Archetype Created Web Application</display-name>
        <!-- servlet 用于声明 Servlet -->
        <servlet>
         	<!-- servlet-name 用于声明 Servlet 的名字, 仅在 web.xml 内部使用 -->
            <servlet-name>HelloServlet</servlet-name>
            <!-- servlet-class 用于声明 Servlet 所对应的类名 -->
            <servlet-class>top.gabrielxd.servlet.HelloServlet</servlet-class>
        </servlet>
        <!-- servlet-mapping 用于声明进行 Servlet 的 URL 映射 -->
        <servlet-mapping>
            <!-- servlet-name 用于声明 Servlet 的名字, 须与 <servlet> 中的 <servlet-name> 保持一致 -->
            <servlet-name>HelloServlet</servlet-name>
            <!-- url-pattern 用于配置 Servlet 的访问地址, 即 Servlet 的 URL 映射值 -->
            <url-pattern>/helloservlet</url-pattern>
        </servlet-mapping>
    </web-app>
    

    映射的过程:/helloservlet -> HelloServlet -> top.gabrielxd.servlet.HelloServlet

    <url-pattern> 的匹配规则:

    1. 精确路径匹配
      如:url-pattern 为 “/hello/HelloServlet” 时,则只有格式为 “/hello/HelloServlet” 的请求与该 Servlet 匹配。
    2. 最长路径匹配
      如:url-pattern 为 “/hello/*” 时,则格式为 “/hello/HelloServlet” 或者 “/hello/test/TestServlet” 的请求都可该 Servlet 匹配。
    3. 扩展匹配
      如:url-pattern 为 “*.action” 时,则格式为 “/HelloServlet.action” 或者 “/hello/test/Hello.action” 的请求都可该 Servlet 匹配。
    4. 如果前三条规则都没有找到一个可匹配的 Servlet,容器会将请求交给默认 Servlet 来处理。默认 Servlet 就是 url-pattern 配置为 “/” 的 Servlet,在 Tomcat 中,默认 Servlet 是 org.apache.catalina.servlets.DefaultServlet,其配置为 “%Tomcat_Home%\conf\web.xml”。Tomcat 中的 DefaultServlet 主要用于获取服务器静态资源或者显示资源目录列表。当请求交给 DefaultServlet 处理后,DefaultServlet 会根据当前请求 URL 到 Web 应用根目录下寻找对应的资源。若存在,则直接读取并返回给客户端;否则返回 404 错误响应页面。
  6. 配置 Tomcat 运行设置并部署运行。
    image-20220901094336383

    可以看到,访问 /hellosevlet 路径后响应头和响应体均正确返回。

URL 地址到 Servlet 程序的访问

image-20220905122906620.png

ServletContext

  • ServletContext 是一个接口,它表示 Servlet 上下文对象
  • 一个 web 工程,只有一个 ServletContext 对象实例
  • ServletContext 对象是一个域对象(域对象可以像 Map 一样存取数据的对象,这里的域指的是存取数据的操作范围为整个 web 工程)
  • ServletContext 在 web 工程部署启动的时候创建,在 web 工程停止的时候销毁

常用 API

javax.servlet.GenericServlet

方法 说明
ServletContext getServletContext() 返回该 servlet 的 ServletContext 的引用

javax.servlet.ServletContext

方法 说明
void setAttribute(String name, Object object) 将一个对象与此 ServletContext 中的给定属性名称绑定
Object getAttribute(String name) 返回 ServletContext 中有给定名称的属性(对象),如果没有则返回空
String getInitParameter(String name) 返回一个包含指定名称的全局初始化参数的值,若该参数不存在则返回空
RequestDispatcher getRequestDispatcher(String path) 返回一个 RequestDispatcher 对象,作为位于给定路径的资源的包装器
InputStream getResourceAsStream(String path) 将位于指定路径上的资源作为一个对象返回

作用

1、共享数据

FormServlet

package top.gabrielxd.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 FormServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf-8");
        PrintWriter out = resp.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("\t<title>From</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("\t<form action=\"/s2/input.add\" method=\"post\">");
        out.println("\t\tKey: <input type=\"text\" name=\"key\"><br>");
        out.println("\t\tValue: <input type=\"text\" name=\"value\"><br>");
        out.println("\t\t<input type=\"submit\" value=\"存入数据\"><br>");
        out.println("\t</form><hr>");
        out.println("\t<a href=\"/s2/GetAllData\">查看所有数据</a>");
        out.println("</body>");
        out.println("</html>");
    }
}

GetServlet

package top.gabrielxd.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.HashMap;
import java.util.Map;

public class GetServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置内容类型及编码
        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf-8");
        PrintWriter out = resp.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("\t<title>From</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("\t<table>");
        out.println("\t\t<thead>");
        out.println("\t\t\t<tr>");
        out.println("\t\t\t\t<th>键</th>");
        out.println("\t\t\t\t<th>值</th>");
        out.println("\t\t\t</tr>");
        out.println("\t\t</thead>");
        // 拿到 Servlet 上下文对象
        ServletContext context = this.getServletContext();
        if (context.getAttribute("map") != null) {
            // 取上下文对象中的 map 属性
            Map<String, String> map = (HashMap<String, String>) context.getAttribute("map");
            out.println("\t\t<tbody>");
            // 用表格的方式渲染 map 中的键值对
            for (Map.Entry<String, String> entry : map.entrySet()) {
                out.println("\t\t\t<tr>");
                out.println("\t\t\t\t<td>" + entry.getKey() + "</td>");
                out.println("\t\t\t\t<td>" + entry.getValue() + "</td>");
                out.println("\t\t\t</tr>");
            }
            out.println("\t\t</tbody>");
        }
        out.println("\t</table>");
        out.println("</body>");
        out.println("</html>");
    }
}

AddServlet

package top.gabrielxd.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.util.HashMap;
import java.util.Map;

public class AddServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
            IOException {
        // 从请求体中拿到参数
        String key = req.getParameter("key");
        String value = req.getParameter("value");
        // 拿到 Servlet 上下文对象
        ServletContext context = this.getServletContext();
        if (context.getAttribute("map") == null) {
            context.setAttribute("map", new HashMap<String, String>());
        }
        // 取到上下文对象中的 map 属性
        Map<String, String> map = (HashMap<String, String>) context.getAttribute("map");
        map.put(key, value); // 放入键值
        // 重定向到原来的地址
        resp.setStatus(resp.SC_MOVED_TEMPORARILY);
        resp.setHeader("Location", req.getHeader("Referer"));
    }
}

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"
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
    <servlet-name>FormServlet</servlet-name>
    <servlet-class>top.gabrielxd.servlet.FormServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>FormServlet</servlet-name>
    <url-pattern>/add</url-pattern>
  </servlet-mapping>
  <servlet>
    <servlet-name>AddServlet</servlet-name>
    <servlet-class>top.gabrielxd.servlet.AddServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>AddServlet</servlet-name>
    <url-pattern>*.add</url-pattern>
  </servlet-mapping>
  <servlet>
    <servlet-name>GetServlet</servlet-name>
    <servlet-class>top.gabrielxd.servlet.GetServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>GetServlet</servlet-name>
    <url-pattern>/GetAllData</url-pattern>
  </servlet-mapping>
</web-app>

test.gif

2、获取初始化参数

TestServlet

package top.gabrielxd.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;

public class TestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        ServletContext context = this.getServletContext();
        String url = context.getInitParameter("homepage");
        resp.getWriter().println(url);
    }
}

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"
  <display-name>Archetype Created Web Application</display-name>
  <context-param>
    <param-name>homepage</param-name>
    <param-value>https://gabrielxd.top</param-value>
  </context-param>
  <servlet>
    <servlet-name>TestServlet</servlet-name>
    <servlet-class>top.gabrielxd.servlet.TestServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>TestServlet</servlet-name>
    <url-pattern>/test</url-pattern>
  </servlet-mapping>
</web-app>

image-20220905182832037

3、请求转发
请求转发的特点
  • 浏览器地址栏的 URL 地址不会改变
  • 是一次请求
  • 共享 Request 域中的数据
  • 可以转发到 WEB-INF 目录下
  • 不能访问工程以外的资源

DispatchServlet

package top.gabrielxd.servlet;

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

public class DispatchServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        // 转发的请求路径
        RequestDispatcher dispatcher = context.getRequestDispatcher("/test");
        dispatcher.forward(req, resp); // 调用 forward 实现请求转发
    }
}

javax.servlet.RequestDispatcher

方法 说明
void forward(ServletRequest request, ServletResponse response) 将一个请求从一个 Servlet 转发到服务器上的另一个资源(Servlet、JSP 文件或 HTML 文件)
void include(ServletRequest request, ServletResponse response) 在响应中包括一个资源(Servlet、JSP 页面、 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"
  <display-name>Archetype Created Web Application</display-name>
  <context-param>
    <param-name>homepage</param-name>
    <param-value>https://gabrielxd.top</param-value>
  </context-param>
  <servlet>
    <servlet-name>TestServlet</servlet-name>
    <servlet-class>top.gabrielxd.servlet.TestServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>TestServlet</servlet-name>
    <url-pattern>/test</url-pattern>
  </servlet-mapping>
  <servlet>
    <servlet-name>DispatchServlet</servlet-name>
    <servlet-class>top.gabrielxd.servlet.DispatchServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>DispatchServlet</servlet-name>
    <url-pattern>/dispatch</url-pattern>
  </servlet-mapping>
</web-app>

image-20220905182904611

4、读取资源文件

在 /工程目录/src/main/resources 目录下创建 db.properties 文件用于读取。

ResourceServlet

package top.gabrielxd.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.InputStream;
import java.util.Properties;

public class ResourceServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        ServletContext context = this.getServletContext();
        // 获取资源的输入流
        InputStream is = context.getResourceAsStream("/WEB-INF/classes/db.properties");
        // 新建并读取属性
        Properties prop = new Properties();
        prop.load(is);
        String username = prop.getProperty("username");
        String password = prop.getProperty("password");
        resp.getWriter().println(username + ": " + password);
    }
}

在 工程目录/src/main/java 和 工程目录/src/main/resources 里创建的文件被打包到了同一个路径下:/WEB-INF/classes,俗称这个路径为 classpath。

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"
         metadata-complete="true">
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
    <servlet-name>ResourceServlet</servlet-name>
    <servlet-class>top.gabrielxd.servlet.ResourceServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>ResourceServlet</servlet-name>
    <url-pattern>/resource</url-pattern>
  </servlet-mapping>
</web-app>

HttpServletRequest

作用

每次只要有请求进入 Tomcat 服务器,Tomcat 服务器就会把请求过来的 HTTP 协议信息解析好封装到 Request 对象中。然后传递到 service 方法(doGet() 与 doPost())中给我们使用。我们可以通过 HttpServletRequest 对象,获取到所有请求的信息。

常用 API

javax.servlet.ServletRequest

方法 说明
String getRemoteHost() 返回发起该请求的客户端的主机地址
String getParameter(String name) 返回该请求携带的给定名称对应的参数
String[] getParameterValues(String name) 返回该请求携带的给定名称对应的参数(参数有多个值时使用)
String[] getParameterMaps() 返回该请求携带的所有参数的映射
void setAttribute(String name, Object o) 将一个对象与该请求域中的给定属性名称绑定
Object getAttribute(String name) 返回该请求域中有给定名称的属性(对象),如果没有则返回 null
RequestDispatcher getRequestDispatcher(String path) 返回一个 RequestDispatcher 对象,作为位于给定路径的资源的包装器(与 ServletContext 中相似)

javax.servlet.http.HttpServletRequest

方法 说明
String getRequestURI() 返回该请求的资源路径
StringBuffer getRequestURL() 返回该请求的统一资源定位符(绝对路径)
String getHeader(String name) 返回该请求的请求头
String getMethod() 返回该请求的请求方式(例如 GET, POST, PUT…)

实例

测试常用 API

RequestApiServlet

package top.gabrielxd.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 RequestApiServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = resp.getWriter();
        out.println("<b>URI</b>: " + req.getRequestURI() + "<br>");
        out.println("<b>URL</b>: " + req.getRequestURL() + "<br>");
        out.println("<b>RemoteHost</b>: " + req.getRemoteHost() + "<br>");
        out.println("<b>Header(User-Agent)</b>: " + req.getHeader("User-Agent") + "<br>");
        out.println("<b>Method</b>: " + req.getMethod() + "<br>");
    }
}
image-20220906161624698
测试获取参数

ParamServlet

package top.gabrielxd.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 ParamServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = resp.getWriter();
        req.getParameterMap().forEach((k, v) -> {
            out.println(k);
            for (String s : v) {
                out.println("- " + s);
            }
            out.println();
        });
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}
image-20220906163534717
解决请求参数为中文时乱码的问题

GET 请求
tomcat 8 之前请求发送中文数据需要转码:

String username = req.getParameter("username");
// 先以 iso-8859-1 编码再以 utf-8 解码
username = new String(username.getBytes("iso-8859-1"), "utf-8");

POST 请求

// 设置请求体字符集为 utf-8
req.setCharacterEncoding("utf-8");
请求转发

ServletContext - 3、请求转发 相同

HttpServletResponse

作用

HttpServletResponse 和 HttpServletRequest 一样。每次请求进来,Tomcat 服务器都会创建一个 Response 对象传递给 Servlet 程序去使用。HttpServletRequest 表示请求过来的信息,HttpServletResponse 表示所有响应的信息,如果需要设置返回给客户端的信息,都可以通过 HttpServletResponse 对象来进行设置。

常用 API

javax.servlet.ServletResponse

方法 说明
ServletOutputStream getOutputStream() throws IOException 获取字节输出流
PrintWriter getWriter() throws IOException 获取字符打印流
void setCharacterEncoding(String charset) 设置响应字符集
void setContentLength(int len) 设置响应体长度
void setContentLengthLong(long len) 设置响应体长度
void setContentType(String type) 设置响应体类型

javax.servlet.ServletResponse

方法 说明
void setDateHeader(String name, long date) 设置一个具有给定名称和日期值的响应头
void addDateHeader(String name, long date) 添加一个具有给定名称和日期值的响应头
void setHeader(String name, String value) 设置一个具有给定名称和值的响应头
void addHeader(String name, String value) 添加一个具有给定名称和值的响应头
void setIntHeader(String name, int value) 设置一个具有给定名称和整数值的响应头
void addIntHeader(String name, int value) 添加一个具有给定名称和整数值的响应头
void setStatus(int sc) 设置响应状态码

实例

实现文件下载

DownloadServlet

package top.gabrielxd.servlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;

public class DownloadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String path = "/imgs/amamiya.jpg"; // 文件路径
        // 获取文件名
        String fileName = path.substring(path.lastIndexOf('/') + 1);
        // 设置响应头让浏览器能够支持下载该文件 文件名用 utf-8 编码防止中文文件名乱码
        resp.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName
                ,"UTF-8"));
        ServletContext context = getServletContext(); // Servlet 上下文对象
        // 获取文件在项目部署完成后的真实路径
        String realPath = context.getRealPath("/WEB-INF/classes" + path);
        // try-with-resources 的方法创建文件输入流
        try (FileInputStream in = new FileInputStream(realPath)) {
            int len;
            byte[] buffer = new byte[1024]; // 缓冲区
            ServletOutputStream out = resp.getOutputStream(); // 响应字节输出流
            // 写入到缓存区后输出给客户端
            while ((len = in.read(buffer)) > 0) {
                out.write(buffer, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
请求重定向
请求重定向的特点
  • 浏览器地址栏的 URL 地址会改变
  • 两次请求
  • 不共享 Request 域中的数据
  • 不能访问到 WEB-INF 目录下的资源
  • 可以访问工程以外的资源
请求转发与请求重定向的区别
  • 请求转发是一种服务器的行为,客户端只有一次请求,服务器端转发后会将请求对象保存,地址栏中的 URL 地址不会改变,得到响应后服务器端再将响应发给客户端
  • 请求重定向是一种客户端行文,从本质上讲等同于两次请求,前一次请求对象不会保存,地址栏的 URL 地址会改变
image-20220907005751552
实现方式 1
// 设置响应状态码 302 Found(Moved Temporarily)
resp.setStatus(302);
// 设置响应头 给出新地址
resp.setHeader("Location", "https://http.cat/200");
实现方式 2

直接使用实现好的 API(推荐)

resp.sendRedirect("https://http.cat/200");

会话

概述

会话:用户打开浏览器,访问web服务器的资源,会话建立;直到有一方断开连接,会话结束。
在一次会话中可以包含多次请求和响应。

  • 从浏览器发出请求到服务端响应数据给前端之后,一次(在浏览器和服务器之间的)会话就被建立了
  • 会话被建立后,如果浏览器或服务端都没有被关闭,则会话就会持续建立着
  • 浏览器和服务器就可以继续使用该会话进行请求发送和响应,上述的整个过程就被称之为会话

用实际场景来理解会话:比如在访问京东的时,当打开浏览器进入京东首页后,浏览器和京东的服务器之间就建立了一次会话,后面的搜索商品、查看商品的详情、加入购物车等操作都在这一次会话中完成。

会话跟踪

概述

会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据

  • 服务器会收到多个请求,这多个请求可能来自多个浏览器
  • 服务器需要用来识别请求是否来自同一个浏览器
  • 服务器用来识别浏览器的过程就是会话跟踪
  • 服务器识别浏览器后就可以在同一个会话中多次请求之间来共享数据

为什么

  1. 一个会话中的多次请求为什么要共享数据?有了数据共享功能后能实现哪些功能呢?
    • 页面展示用户登录信息:很多网站在登录后访问多个功能发送多次请求后,浏览器上都会有当前登录用户的信息,例如:用户名、头像…
    • 页面展示用户登录信息:很多网站在登录后访问多个功能发送多次请求后,浏览器上都会有当前登录用户的信息,例如:用户名、头像…
    • 网站登录页面的记住我功能:当用户登录成功后,勾选记住我按钮后下次再次访问该网站时就不需要再次登录,这样可以简化操作优化用户体验,而多次登录就会有多次请求,所以这个功能也涉及到共享数据
    • 登录页面的验证码功能:生成验证码和输入验证码点击注册这也是两次请求,这两次请求的数据之间要进行对比,相同则允许注册,不同则拒绝注册,该功能的实现也需要在同一次会话中共享数据
  2. 为什么现在浏览器和服务器不支持数据共享?
    • 浏览器和服务器之间使用 HTTP 请求来进行数据传输,而HTTP协议是无状态的,这也就导致每次浏览器向服务器请求时,服务器都会将该请求视为新的请求
    • HTTP 协议设计成无状态的目的是让每次请求之间相互独立,互不影响
    • 但请求与请求之间独立后,就无法实现多次请求之间的数据共享

如何实现

  1. 客户端会话跟踪技术:Cookie
  2. 服务端会话跟踪技术:Session

简介

客户端会话技术,将数据保存到客户端,以后每次请求都携带 Cookie 数据进行访问。

工作流程

image-20220907140126532
  • 服务端提供了两个 Servlet,分别是 ServletA 和 ServletB
  • 浏览器发送 HTTP请求1 给服务端,服务端 ServletA 接收请求并进行业务处理
  • 服务端 ServletA 在处理的过程中可以创建一个Cookie对象并将 name=gabrielxd 的数据存入 Cookie
  • 服务端 ServletA 在响应数据的时候,会把 Cookie 对象响应给浏览器
  • 浏览器接收到响应数据,会把 Cookie 对象中的数据存储在浏览器内存中,此时浏览器和服务端就建立了一次会话
  • 在同一次会话中浏览器再次发送 HTTP请求2 给服务端 ServletB,浏览器会携带 Cookie 对象中的所有数据
  • ServletB 接收到请求和数据后,就可以获取到存储在 Cookie 对象中的数据,这样同一个会话中的多次请求之间就实现了数据共享

基本使用

image-20220907142716148
  • 创建 Cookie 对象

    Cookie cookie = new Cookie("key1", "value1");
    
  • 设置cookie的有效期

    cookie.setMaxAge(24 * 60 * 60);
    
  • 通知客户端保存 Cookie

    resp.addCookie(cookie);
    
  • 拿到 Cookie 数组并遍历
    PrintWriter out = resp.getWriter();
    for (Cookie cookie : req.getCookies()) {
        out.println("<p>" + cookie.getName() + ": " + cookie.getValue() + "</p>");
    }
    
image-20220907144916608

一些细节

  • Cookie 一般会保存在本地的 用户目录下 appdata
  • 一个 Cookie 只能保存一个键值对
  • 一个 Web 站点可以给浏览器发送多个 Cookie,最多存放 20 个 Cookie
  • Cookie 大小有限制:4kb
  • 浏览器上限:300 个 Cookie

javax.servlet.http.Cookie

方法 说明
void setMaxAge(int expiry) 设置该Cookie的存活时间(秒)
  • 参数为正值表示该 cookie 将在这么多秒后过期
  • 参数为负值意味着该 cookie 不会被持久地存储,并将在网络浏览器退出时被删除(默认为 -1)
  • 参数为零,表示马上删除该 cookie

Session

简介

服务端会话跟踪技术:将数据保存到服务端。

  • Session 是存储在服务端而 Cookie 是存储在客户端
  • 存储在客户端的数据容易被窃取和截获,存在很多不安全的因素
  • 存储在服务端的数据相比于客户端来说就更安全
  • Session 是基于 Cookie 的

工作流程

image-20220907165736536
  • 在服务端的 ServletA 获取一个 Session 对象,把数据存入其中
  • 在服务端的 ServletB 获取到相同的 Session 对象,从中取出数据
如何保证在一次会话中获取的 Session 对象是同一个
image-20220907172349069

getSession() 方法在新建 Session 时做了什么?

// 创建记录 Session ID 的 Cookie 并发给浏览器
Cookie cookie = new Cookie("JSESSIONID",sessionId);
resp.addCookie(cookie);

基本使用

javax.servlet.http.HttpServletRequest

方法 说明
HttpSession getSession() 返回与该请求相关的 HttpSession,如果没有则新建一个
HttpSession getSession(boolean create) 返回与该请求相关的 HttpSession,如果没有且 create 为 true,则创建一个,否则返回空

javax.servlet.http.HttpSession

方法 说明
void setAttribute(String name, Object value) 使用指定的名称,将一个对象绑定到此会话(Session)上
Object getAttribute(String name) 返回在此会话中与指定名称绑定的对象,如果该名称下没有绑定对象,则返回空
void removeAttribute(String name) 从这个会话中删除与指定名称绑定的对象
boolean isNew() 返回该会话是否是新创建出来的
void invalidate() 使这个会话无效,然后解除与之绑定的任何对象
实例

SessionServlet

package top.gabrielxd.cs;

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

public class SessionServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        // 获取 / 创建 Session 对象
        HttpSession session = req.getSession();
        // 在 Session 中存数据
        session.setAttribute("username", "gabrielxd");
        session.setAttribute("numArr", new int[]{1, 2, 3, 4, 5});
        // 获取 Session ID
        String sessionId = session.getId();
        PrintWriter out = resp.getWriter();
        // 判断 Session 是否是新创建出来的
        if (session.isNew()) out.write("new session, id: " + sessionId);
        else out.write("existing session, id: " + sessionId);
    }

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

第一次响应:

image-20220907173005938

第二次请求:

image-20220907173029226

显然符合#如何保证在一次会话中获取的 Session 对象是同一个

Session 钝化与活化

  • 钝化:在服务器正常关闭后,Tomcat 会自动将 Session 数据写入硬盘的文件(SESSIONS.ser)中
  • 活化:再次启动服务器后,从文件中加载数据到 Session 中
    • 数据加载到服务器内存中后,路径中的 SESSIONS.ser 文件会被删除掉
演示
  1. 首先要正常的启动和关闭服务器:
    在项目目录(pom.xml 所在的目录)下打开命令行,执行启动命令:

    mvn tomcat:run
    
  2. 构建部署完成后会在命令行输出 URL:
    image-20220907174950300

    我们访问之前在 web.xml 中配好的路径,在这里是:http://localhost:8080/cs-02-session/session

    会发现 session 正常工作。

  3. 接下来正常关闭服务器:直接按快捷键 Ctrl+C
    然后就可以在 工程目录/target/tomcat/work/localEngine/localhost/cs-02-session (Tomcat 版本不同路径可能不同)下找到 SESSIONS.ser 文件,这就是服务器保存下来的离线的Session 文件。

  4. 再次打开服务器会发现 SESSIONS.ser 文件确实被删除了,取而代之的是第一次访问页面发现 Session 是已经存在的了。自此就演示了 Session 的钝化与活化。

Session 钝化后生成的文件:工程目录/target/tomcat/work/localEngine/localhost/cs-02-session/SESSIONS.ser

注意:

  • Session 数据存储在服务端,服务器重启后,Session 数据会被保存
  • 浏览器被关闭重新启动后,重新建立的连接就已经是一个全新的会话,获取的 Session 数据也是一个新的对象,所以 Session 不能长期保存数据

Session 销毁

  1. 默认情况下,无操作 30 分钟自动销毁

    • 失效时间可以通过在项目中的 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">
          
          <session-config>
              <session-timeout>60</session-timeout>
          </session-config>
      </web-app>
      
    • 默认在 tomcat 安装目录下 conf/web.xml 中是 30 分钟

  2. 调用 Session 对象的 invalidate() 方法进行销毁:

    session.invalidate();
    

JSP

简介

JSP(Java Server Pages):Java 服务端页面。是一种动态的网页技术,其中既可以定义 HTML、JS、CSS等静态内容,还可以定义 Java代码的动态内容,也就是说:JSP = HTML + Java。

作用

  • JSP 的主要作用是代替 Servlet 程序回传 HTML 页面的数据。
  • 因为 Servlet 程序回传 HTML 页面数据是一件非常繁锁的事情,开发成本和维护成本都极高。

基本使用

  1. 首先在 pom.xml 中引入 JSP 依赖

    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.2</version>
        <scope>provided</scope>
    </dependency>
    
  2. 在项目的 webapp 下创建 jsp 页面并编写 HTML 和 Java 代码:

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
        <h1>Hello JSP!</h1>
        <% System.out.println("Hello JSP!"); %>
    </body>
    </html>
    
    
  3. 配置并开启 Tomcat 服务器。

  4. 访问 jsp 对应路径:
    image-20220908115527028

原理

像之前一样找到 工程目录/target/tomcat/work 目录。

我这里使用命令行开启 tomcat 服务器后无法访问 jsp 页面,所以直接使用 IDEA 启动 tomcat 服务,而 IDEA 会生成文件在 用户目录/AppData/Local/JetBrains/IntelliJIdea2022.2/tomcat/ 中(IDEA 版本不同目录名中的版本号也不同),然后可以看到一个以 UUID 格式命名的目录,在里面就有 work 目录,再向下找到 work/Catalina/localhost/jsp1/org/apache/jsp。

我这里的文件路径是:C:\Users\GabrielxD\AppData\Local\JetBrains\IntelliJIdea2022.2\tomcat\f0618c67-3cd3-4b9d-9ffd-f5d105c9f609\work\Catalina\localhost\jsp1\org\apache\jsp。

这里就存放了之前编写的 JSP 页面转换成的结果,发现是 Java 源代码与编译好的 class 文件。
image-20220908122029731

打开 hello_jsp.java

/* ... */
package org.apache.jsp;

import ...

public final class hello_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent,
                 org.apache.jasper.runtime.JspSourceImports {
	// ...
}

发现该类继承了 org.apache.jasper.runtime.HttpJspBase

在 Tomcat安装目录/lib/jasper.jar/org/apache/jasper/runtime 中找到 HttpJspBase.class 字节码文件,用 IDEA 反编译打开:

// ...

package org.apache.jasper.runtime;

import ...

public abstract class HttpJspBase extends HttpServlet implements HttpJspPage {
    // ...
}

发现 HttpJspBase 直接继承了 HttpServlet,也就是说 JSP 翻译出来的 Java 类,它间接了继承了 HttpServlet 类。

于是得出结论:JSP 本质上就是一个 Servlet。

访问 JSP 时的流程

image-20220908123750439
  1. 浏览器第一次访问 hello.jsp 页面,Tomcat 会将 hello.jsp 转换为名为 hello_jsp.java 的一个 Servlet。
  2. Tomcat 再将转换的 Servlet 编译成字节码文件 hello_jsp.class
  3. Tomcat 会执行该字节码文件,向外提供服务

JSP 脚本

声明脚本

<%! ... %>
作用

可以给 JSP 翻译出来的 Java 类定义属性和方法甚至是静态代码块、内部类等。

实例

test.jsp

<%@ page import="java.util.Map" %>
<%@ page import="java.util.HashMap" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%-- 声明类属性 --%>
<%!
    private Integer id;
    private String name;
    private static Map<String, Object> map;
%>
<%-- 声明静态代码块 --%>
<%!
    static {
        map = new HashMap<String,Object>();
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");
    }
%>
<%-- 声明类方法 --%>
<%!
    public int func(){
        return 0;
    }
%>
<%-- 声明内部类 --%>
<%!
    public static class A {
        private Integer id = 1;
        private String name = "inner";
    }
%>
</body>
</html>

生成的 test_jsp.java

/* ... */
package org.apache.jsp;

import ...
import java.util.Map;
import java.util.HashMap;

public final class test_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent,
                 org.apache.jasper.runtime.JspSourceImports {

    private Integer id;
    private String name;
    private static Map<String, Object> map;

    static {
        map = new HashMap<String,Object>();
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");
    }

    public int func(){
        return 0;
    }

    public static class A {
        private Integer id = 1;
        private String name = "inner";
    }

  	// ....
}

表达式脚本

<%= ... %>
作用

在 JSP 页面上输出数据。

特点
  • 所有的表达式脚本都会被翻译到 _jspService() 方法中
  • 表达式脚本都会被翻译成为 out.print(...) 输出到页面上
  • 由于表达式脚本翻译的内容都在 _jspService() 方法中,所以 _jspService() 方法中的对象都可以直接使用
  • 表达式脚本中的表达式不能以分号结束
实例

test.jsp

<%@ page import="java.util.Map" %>
<%@ page import="java.util.HashMap" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%-- ... --%>

<%= 12 %><br>
<%= 12.34 %><br>
<%= "字符串" %><br>
<%= map %><br>
<%= request.getParameter("username") %><br>
</body>
</html>

生成的 test.java

/* ... */
package org.apache.jsp;

import ...

public final class test_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent,
                 org.apache.jasper.runtime.JspSourceImports {

  // ...

  public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
      throws java.io.IOException, javax.servlet.ServletException {

    final java.lang.String _jspx_method = request.getMethod();
    if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method) && !javax.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) {
      response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSP 只允许 GET、POST 或 HEAD。Jasper 还允许 OPTIONS");
      return;
    }

    final javax.servlet.jsp.PageContext pageContext;
    javax.servlet.http.HttpSession session = null;
    final javax.servlet.ServletContext application;
    final javax.servlet.ServletConfig config;
    javax.servlet.jsp.JspWriter out = null;
    final java.lang.Object page = this;
    javax.servlet.jsp.JspWriter _jspx_out = null;
    javax.servlet.jsp.PageContext _jspx_page_context = null;


    try {
      response.setContentType("text/html;charset=UTF-8");
      pageContext = _jspxFactory.getPageContext(this, request, response,
      			null, true, 8192, true);
      _jspx_page_context = pageContext;
      application = pageContext.getServletContext();
      config = pageContext.getServletConfig();
      session = pageContext.getSession();
      out = pageContext.getOut();
      _jspx_out = out;

      out.write("\r\n");
      out.write("\r\n");
      out.write("\r\n");
      out.write("<html>\r\n");
      out.write("<head>\r\n");
      out.write("    <title>Title</title>\r\n");
      out.write("</head>\r\n");
      out.write("<body>\r\n");
      out.write('\r');
      out.write('\n');
      out.write('\r');
      out.write('\n');
      out.write('\r');
      out.write('\n');
      out.write('\r');
      out.write('\n');
      out.write('\r');
      out.write('\n');
      out.write('\r');
      out.write('\n');
      out.write('\r');
      out.write('\n');
      out.write("\r\n");
      out.write("\r\n");
      out.print( 12 );
      out.write("<br>\r\n");
      out.print( 12.34 );
      out.write("<br>\r\n");
      out.print( "字符串" );
      out.write("<br>\r\n");
      out.print( map );
      out.write("<br>\r\n");
      out.print( request.getParameter("username") );
      out.write("<br>\r\n");
      out.write("</body>\r\n");
      out.write("</html>\r\n");
    } catch (java.lang.Throwable t) {
      if (!(t instanceof javax.servlet.jsp.SkipPageException)){
        out = _jspx_out;
        if (out != null && out.getBufferSize() != 0)
          try {
            if (response.isCommitted()) {
              out.flush();
            } else {
              out.clearBuffer();
            }
          } catch (java.io.IOException e) {}
        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
        else throw new ServletException(t);
      }
    } finally {
      _jspxFactory.releasePageContext(_jspx_page_context);
    }
  }
}
image-20220908131334204

代码脚本

<% ... %>
作用

可以在 JSP 页面中,使用 Java 语句编写自己需要的功能。

特点
  • 代码脚本翻译之后都在 _jspService 方法中

    代码脚本由于翻译到 _jspService() 方法中,所以在 _jspService() 方法中的现有对象都可以直接使用

  • 可以由多个代码脚本块组合完成一个完整的 Java 语句

  • 代码脚本还可以和表达式脚本一起组合使用,在 JSP 页面上输出数据

实例

test.jsp

<%@ page import="java.util.Map" %>
<%@ page import="java.util.HashMap" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%-- ... %-->

<%-- if 语句 --%>
<%
    boolean flag = false;
    if (flag) {
%>
        <h1>True</h1>
<%
    } else {
%>
        <h1>False</h1>
<%
    }
%>
<%-- for 语句 --%>
<table border="1" cellspacing="0">
    <%
        for (int i = 0; i < 10; ++i) {
    %>
            <tr>
                <td>第 <%= i %> 行</td>
            </tr>
    <%
        }
    %>
</table>
<%-- _jspService() 方法内的代码都可以写 --%>
<%
    String username = request.getParameter("username");
    System.out.println("参数:username=" + username);
%>
</body>
</html>

生成的 test.java

/* .... */
package org.apache.jsp;

import ...

public final class test_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent,
                 org.apache.jasper.runtime.JspSourceImports {

  // ...
                     
  public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
      throws java.io.IOException, javax.servlet.ServletException {

    final java.lang.String _jspx_method = request.getMethod();
    if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method) && !javax.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) {
      response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSP 只允许 GET、POST 或 HEAD。Jasper 还允许 OPTIONS");
      return;
    }

    final javax.servlet.jsp.PageContext pageContext;
    javax.servlet.http.HttpSession session = null;
    final javax.servlet.ServletContext application;
    final javax.servlet.ServletConfig config;
    javax.servlet.jsp.JspWriter out = null;
    final java.lang.Object page = this;
    javax.servlet.jsp.JspWriter _jspx_out = null;
    javax.servlet.jsp.PageContext _jspx_page_context = null;


    try {
      response.setContentType("text/html;charset=UTF-8");
      pageContext = _jspxFactory.getPageContext(this, request, response,
      			null, true, 8192, true);
      _jspx_page_context = pageContext;
      application = pageContext.getServletContext();
      config = pageContext.getServletConfig();
      session = pageContext.getSession();
      out = pageContext.getOut();
      _jspx_out = out;

      out.write("\r\n");
      out.write("\r\n");
      out.write("\r\n");
      out.write("<html>\r\n");
      out.write("<head>\r\n");
      out.write("    <title>Title</title>\r\n");
      out.write("</head>\r\n");
      out.write("<body>\r\n");
      out.write('\r');
      out.write('\n');
      out.write('\r');
      out.write('\n');
      out.write('\r');
      out.write('\n');
      out.write('\r');
      out.write('\n');
      out.write('\r');
      out.write('\n');
      out.write('\r');
      out.write('\n');
      out.write('\r');
      out.write('\n');
      out.write("\r\n");
      out.write("\r\n");
      out.print( 12 );
      out.write("<br>\r\n");
      out.print( 12.34 );
      out.write("<br>\r\n");
      out.print( "字符串" );
      out.write("<br>\r\n");
      out.print( map );
      out.write("<br>\r\n");
      out.print( request.getParameter("username") );
      out.write("<br>\r\n");
      out.write("\r\n");
      out.write('\r');
      out.write('\n');

    boolean flag = false;
    if (flag) {

      out.write("\r\n");
      out.write("        <h1>True</h1>\r\n");

    } else {

      out.write("\r\n");
      out.write("        <h1>False</h1>\r\n");

    }

      out.write('\r');
      out.write('\n');
      out.write("\r\n");
      out.write("<table border=\"1\" cellspacing=\"0\">\r\n");
      out.write("    ");

        for (int i = 0; i < 10; ++i) {
    
      out.write("\r\n");
      out.write("            <tr>\r\n");
      out.write("                <td>第 ");
      out.print( i );
      out.write(" 行</td>\r\n");
      out.write("            </tr>\r\n");
      out.write("    ");

        }
    
      out.write("\r\n");
      out.write("</table>\r\n");
      out.write('\r');
      out.write('\n');

    String username = request.getParameter("username");
    System.out.println("参数:username=" + username);

      out.write("\r\n");
      out.write("</body>\r\n");
      out.write("</html>\r\n");
    } catch (java.lang.Throwable t) {
      if (!(t instanceof javax.servlet.jsp.SkipPageException)){
        out = _jspx_out;
        if (out != null && out.getBufferSize() != 0)
          try {
            if (response.isCommitted()) {
              out.flush();
            } else {
              out.clearBuffer();
            }
          } catch (java.io.IOException e) {}
        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
        else throw new ServletException(t);
      }
    } finally {
      _jspxFactory.releasePageContext(_jspx_page_context);
    }
  }
}
image-20220908132158902

JSP 指令

page 指令

<%@ page 属性="值" %>

JSP 中的 page 指令可以修改 JSP 页面中一些重要的属性或行为。

属性 说明
language JSP 的语言类型,暂时只支持 Java
contentType JSP 返回的数据类型,也就是源码中 response.setContentType() 的参数
pageEncoding 当前 JSP 页面文件本身的字符集
import 用于导包,导类,与 Java 源代码中一样
autoFlush 设置当 out 输出流缓冲区满了之后,是否自动刷新冲级区,默认为 true
buffer 设置 out 缓冲区的大小,默认是 8kb
errorPage 设置当 JSP 页面运行时出错,自动跳转去的错误页面路径
isErrorPage 显式声明 JSP 页面是否是错误信息页面,默认为 false,如果是 true 可以获取异常信息
session 设置访问当前 JSP 页面时是否会自动创建 HttpSession 对象,默认为 true
extends 设置类继承

include 指令

<% @include file="文件路径" %>

静态包含。file 属性指定要包含的 JSP 页面的路径(路径中第一个斜杠 / 表示为 http://ip:port/工程路径/ 映射到代码的 web 目录)

静态包含的特点:

  • 静态包含不会翻译被包含的 JSP 页面
  • 静态包含其实是把被包含的 JSP 页面的代码拷贝到包含的位置执行输出

实例

webapp/index.jsp

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

<%@ include file="common/header.jsp" %>
<h1>Main</h1>
<%@ include file="common/footer.jsp" %>

</body>
</html>

webapp/common/header.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<h1>Header</h1>

webapp/common/footer.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<h1>Footer</h1>

JSP 内置对象

Tomcat 在翻译 JSP 页面成为 Servlet 源代码后,内部提供了九个内置对象:

  • request – 请求对象
  • response – 响应对象
  • pageContext – JSP 的上下文对象
  • session – 会话对象
  • application – ServletContext 对象
  • config – ServletConfig 对象
  • out – 输出流对象
  • page – 指向当前 JSP 的对象
  • exception – 异常对象

域对象

内置对象中的 pageContext、request、session、application 都可以用来在对应的作用域中保存、获取数据(对象),被称作域对象(域对象是可以像 Map 一样存取数据的对象),作用域由小到大 page < request < session < application:

  • pageContext 作用域:page,保存的数据只在一个页面中有效
  • request 作用域:request,保存的数据只在一次请求中有效,请求转发会携带这个数据
  • session 作用域:session,保存的数据只在一次会话中有效(从打开浏览器到关闭浏览器)
  • application 作用域:application,保存的数据在服务器中有效(从打开服务器到关闭服务器)

pageContext 也可以在不同的作用域中设置属性,以及按照作用域从小到大的顺序查找属性:

javax.servlet.jsp.JspContext

方法 说明
void setAttribute(String name, Object value) 将一个对象与 page 作用域中的给定属性名称绑定
void setAttribute(String name, Object value, int scope) 将一个对象与指定作用域中的给定属性名称绑定
Object getAttribute(String name) 返回 page 作用域中有给定名称的属性(对象),如果没有则返回空
Object getAttribute(String name, int scope) 返回指定作用域中有给定名称的属性(对象),如果没有则返回空
Object findAttribute(String name) 在 page、request、session(如果存在)和 application 作用域中依次搜索指定的属性,并返回相关的值或空值

javax.servlet.jsp.PageContext

属性 默认值 说明
static int PAGE_SCOPE 1 page 作用域
static int REQUEST_SCOPE 2 request 作用域
static int SESSION_SCOPE 3 session 作用域
static int APPLICATION_SCOPE 4 application 作用域

一些问题

JSP 中的 out 输出和 response.getWriter() 输出的区别

image-20220908223511360

当 JSP 页面所有代码执行完成后

  1. 首先执行 out.flush(),把 out 缓冲区中的数据全部追加写入到 response writer 缓冲区末尾。
  2. 然后执行 response.getWriter().flush() ,把全部数据写给客户端。

由于 JSP 翻译之后,底层源代码都是使用 out 来进行输出,所以一般情况下。我们在 JSP 页面中应该统一使用 out 来进行输出,以避免打乱页面输出内容的顺序。

  • out.write() 输出字符串没有问题
  • out.print() 输出任意数据都没有问题(都会转换成字符串后调用的 out.write() 输出)

结论:在 JSP 页面中,可以统一使用 out.print() 来进行输出。

JSP 常用标签

jsp:include

<jsp:include page="路径"></jsp:include>

动态包含。page 属性指定要包含的 JSP 页面的路径,动态包含也可以像静态包含一样,把被包含的内容执行输出到包含位置。

特点
  • 动态包含会把包含的 JSP 页面也翻译成为 Java 代码

  • 动态包含底层代码使用如下代码去调用被包含的 JSP 页面执行输出:

    JspRuntimeLibrary.include(request, response, "路径", out, false);
    
  • 动态包含还可以传递参数

实例
<jsp:include page="/include/footer.jsp">
    <jsp:param name="username" value="root"/>
    <jsp:param name="password" value="root"/>
</jsp:include

jsp:forward

<jsp:forward page="路径"></jsp:forward>

请求转发标签,它的功能就是请求转发,page 属性设置请求转发的路径。同样可以传递参数。

实例
<jsp:forward page="/test.jsp">
    <jsp:param name="username" value="gabrielxd"/>
    <jsp:param name="count" value="5"/>
</jsp:forward>

EL

简介

EL 的全称是:Expression Language(表达式语言)。

EL 表达式主要作用是代替 JSP 页面中的表达式脚本在 JSP 页面中进行数据的输出,因为 EL 表达式在输出数据的时候,要比 JSP 表达式脚本简洁很多。

语法

${ 表达式 }
取值

与 JSP 表达式脚本相似,EL 表达式获取数据会依次从 page > request > session > application 这4个域中寻找。

普通输出

EL 在输出 null 值时输出的是空串,而 JSP 表达式脚本输出 null 值的时候,输出的是 null 字符串。

<%@ page import="java.util.List" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.HashMap" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%
  int i = 10;
  boolean b = true;
  String str = "string";
  List<Integer> list = new ArrayList<>();
  list.add(2);
  list.add(3);
  list.add(5);
  Map<String, String> map = new HashMap<>();
  map.put("abc", "xyz");
  map.put("123", "789");
  pageContext.setAttribute("i", i);
  pageContext.setAttribute("b", b);
  pageContext.setAttribute("str", str);
  pageContext.setAttribute("list", list);
  pageContext.setAttribute("map", map);
%>
${ i } <br>
${ b } <br>
${ str } <br>
${ list } <br>
${ list[1] } <br>
${ map } <br>
${ map.abc } <br>

</body>
</html>

输出结果:

10
true
string
[2, 3, 5]
3
{123=789, abc=xyz}
xyz
运算
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%-- 关系运算 --%>
相等: ${ 1 == 1 } | ${ 1 eq 1 }<br>
不等于: ${ 1 != 1 } | ${ 1 ne 1 }<br>
小于: ${ 1 < 2 } | ${ 1 lt 2 }<br>
大于: ${ 1 > 2 } | ${ 1 gt 2 }<br>
小于等于: ${ 1 <= 2 } | ${ 1 le 2 }<br>
大于等于: ${ 1 >= 2 } | ${ 1 ge 2 }<br>
<%-- 逻辑运算 --%>
与运算: ${ 1 == 1 && 1 < 2 } | ${ 1 eq 1 and 1 lt 2 }<br>
或运算: ${ 1 == 1 || 1 > 2 } | ${ 1 eq 1 or 1 gt 2 }<br>
取反运算: ${ !true } | ${ not true }<br>
<%-- 算术运算 --%>
加法: ${ 1 + 2 }<br>
减法: ${ 1 - 2 }<br>
乘法: ${ 1 * 2 }<br>
除法: ${ 10 / 3 } | ${ 10 div 3 }<br>
取模: ${ 10 % 3 } | ${ 10 mod 3 }<br>
</body>
</html>

输出结果:

相等: true | true
不等于: false | false
小于: true | true
大于: false | false
小于等于: true | true
大于等于: false | false
与运算: true | true
或运算: true | true
取反运算: false | false
加法: 3
减法: -1
乘法: 2
除法: 3.3333333333333335 | 3.3333333333333335
取模: 1 | 1
empty 运算

empty 运算可以判断一个数据是否为空,如果为空则输出 true 否则输出 false。

以下几种情况为空:

  • 值为 null 时
  • 值为空串时
  • 值是 Object 类型数组,长度为零时
  • 单列集合元素个数为零
  • 双列集合元素个数为零
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.HashMap" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%
    request.setAttribute("emptyNull", null);
    request.setAttribute("emptyStr", "");
    request.setAttribute("emptyArr", new Object[0]);
    request.setAttribute("emptyList", new ArrayList<Integer>());
    request.setAttribute("emptyMap", new HashMap<Integer, String>());
%>
${ empty emptyNull }<br>
${ empty emptyStr }<br>
${ empty emptyArr }<br>
${ empty emptyList }<br>
${ empty emptyMap }<br>
</body>
</html>

输出结果:

true
true
true
true
true
三元运算
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
${ 1 + 1 == 2 ? "1 + 1 = 2" : "1 + 1 ≠ 2" }
</body>
</html>

输出结果:

1 + 1 = 2

11 个隐含对象

变量名 类型 作用
pageContext pageContextImpl 可以用来获取 JSP 中的内置对象
pageScope Map<String, Object> 可以用来获取 page 域中的数据
requestScope Map<String, Object> 可以用来获取 request 域中的数据
sessionScope Map<String, Object> 可以用来获取 session 域中的数据
applicationScope Map<String, Object> 可以用来获取 application 域中的数据
param Map<String, String> 可以用来获取请求参数
paramValues Map<String, String[]> 可以用来获取请求参数,值为多个时使用
header Map<String, String> 可以用来获取请求头信息
headerValues Map<String, String[]> 可以用来获取请求头信息,值为多个时使用
cookie Map<String, Cookie> 可以用来获取当前请求的 Cookie 信息
initParam Map<String, String> 可以用来获取在 web.xml 中配置的 <context-param> 上下文参数

JSTL

简介

JSP 标准标签库(JSP Standarded Tag Library),是一个不断完善的开放源代码的 JSP 标签库

EL 表达式主要是为了替换 JSP 中的表达式脚本,而标签库则是为了替换代码脚本,以使得 JSP 的代码编写变得更简洁。

JSTL 由五个不同功能的标签库组成:

功能范围 URI 前缀
核心标签库 http://java.sun.com/jsp/jstl/core c
格式化 http://java.sun.com/jsp/jstl/fmt fmt
函数 http://java.sun.com/jsp/jstl/functions fn
数据库 http://java.sun.com/jsp/jstl/sql sql
XML http://java.sun.com/jsp/jstl/xml x

基本使用

  1. 引入依赖

    <!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/taglibs/standard -->
    <dependency>
        <groupId>taglibs</groupId>
        <artifactId>standard</artifactId>
        <version>1.1.2</version>
    </dependency>
    
  2. 在 JSP 页面上引入 JSTL 标签库

    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 
    
  3. 使用标签

    <h1><c:out value="Hello JSLT!" /></h1>
    

核心标签库常用标签

<c:out>

<c:out> 标签用来显示一个表达式的结果,与 <%= %> 作用相似,它们的区别就是 <c:out> 标签可以直接通过点操作符来访问属性。

语法
<c:out value="<string>" default="<string>" escapeXml="<true|false>"/>
<c:out value="<string>" escapeXml="<true|false>"><string></c:out>
属性
属性 描述 是否必要 默认值
value 要输出的内容
default 输出的默认值 主体中的内容
escapeXml 是否忽略 XML 特殊字符 true
实例
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<html>
<head>
  <title>Title</title>
</head>
<body>
<c:out value="&lt要显示的数据对象(未使用转义字符)&gt" escapeXml="true" default="默认值" /><br>
<c:out value="&lt要显示的数据对象(使用转义字符)&gt" escapeXml="false" default="默认值" /><br>
<c:out value="${ null }" default="使用的表达式结果为null,则输出该默认值" escapeXml="false" /><br>
<c:out value="${ null }" escapeXml="false">使用的表达式结果为null,则输出该默认值</c:out><br>
<%--<c:out value="${ null }" default="default 属性中的内容" escapeXml="false">标签体中内容</c:out><br>--%>
<c:out value="非 null" default="使用的表达式结果为null,则输出该默认值" escapeXml="false" /><br>
<c:out value="非 null" escapeXml="false">使用的表达式结果为null,则输出该默认值</c:out><br>
</body>
</html>

输出结果:

&lt要显示的数据对象(未使用转义字符)&gt
<要显示的数据对象(使用转义字符)>
使用的表达式结果为null,则输出该默认值
使用的表达式结果为null,则输出该默认值
非 null
非 null

注意<c:out> 标签要么写成自闭和形式带 default 属性,要么写成双标签形式不带 default 属性,标签体内容和 default 属性不能同时出现,否则引发异常 java.lang.ClassNotFoundException

<c:set>

<c:set>标签用于设置变量值和对象属性,类似 <jsp:setProperty> 行为标签。

语法
<c:set
   var="<string>"
   value="<string>"
   target="<string>"
   property="<string>"
   scope="<string>"/>
属性
属性 描述 是否必要 默认值
value 要存储的值 主体的内容
target 要修改的属性所属的对象
property 要修改的属性
var 存储信息的变量
scope var属性的作用域 Page
实例
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<html>
<head>
    <title>Title</title>
</head>
<body>
<c:out value="${ result }" default="不存在 result" /><br>
<c:set var="result" scope="session" value="${ 1 + 1 }" />
<c:out value="${ result }" default="不存在 result" /><br>
</body>
</html>

输出结果:

第一次访问:

不存在 result
2

再次访问:

2
2
<c:if>

<c:if> 标签判断表达式的值,如果表达式的值为 true 则执行其主体内容。

语法
<c:if test="<boolean>" var="<string>" scope="<string>">
   ...
</c:if>
属性
属性 描述 是否必要 默认值
test 条件
var 用于存储条件结果的变量
scope var属性的作用域 page
实例
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<html>
<head>
  <title>Title</title>
</head>
<body>
<c:if test="${ 1 + 1 == 2 }">
  1 + 1 = 2
</c:if>
</body>
</html>

输出结果:

1 + 1 = 2
<c:choose> <c:when> <c:otherwise>

这三个标签类似 Java swtich 语句与 if-elseif-else 语句的结合体。

语法
<c:choose>
    <c:when test="<boolean>">
        ...
    </c:when>
    <c:when test="<boolean>">
        ...
    </c:when>
    
    ...
    
    <c:otherwise>
        ...
    </c:otherwise>
</c:choose>
属性
  • <c:choose> 标签没有属性。
  • <c:when> 标签只有一个属性。
  • <c:otherwise> 标签没有属性。

<c:when> 标签的属性如下:

属性 描述 是否必要 默认值
test 条件
实例
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<html>
<head>
  <title>Title</title>
</head>
<body>
<%
  pageContext.setAttribute("scope", 1);
%>
<c:choose>
  <c:when test="${ scope == 1 }">
    page
  </c:when>
  <c:when test="${ scope == 2 }">
    request
  </c:when>
  <c:when test="${ scope == 3 }">
    session
  </c:when>
  <c:when test="${ scope == 4 }">
    application
  </c:when>
  <c:otherwise>
    bad scope
  </c:otherwise>
</c:choose>
</body>
</html>

输出结果:

page
<c:forEach>

<c:forEach> 标签封装了 Java 中的for,while,do-while循环,它用于迭代一个集合中的对象。

语法
<c:forEach
    items="<object>"
    begin="<int>"
    end="<int>"
    step="<int>"
    var="<string>"
    varStatus="<string>">
    
    ...
    
</c:forEach>
属性
属性 描述 是否必要 默认值
items 要被循环的信息
begin 开始的元素 0
end 最后一个元素 Last element
step 每一次迭代的步长 1
var 代表当前条目的变量名称
varStatus 代表循环状态的变量名称
实例
<%@ page import="java.util.HashMap" %>
<%@ page import="java.util.Map" %>
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<html>
<head>
  <title>Title</title>
</head>
<body>
<c:forEach var="i" begin="0" end="6" step="2">
  <c:out value="i: ${ i }"/><br>
</c:forEach>
<hr>
<%
  Map<String, String> map = new HashMap<String, String>() {{
    this.put("nickname", "GabrielxD");
    this.put("contact", "gabrielxd@outlook.com");
    this.put("link", "https://gabrielxd.top");
  }};
  pageContext.setAttribute("map", map);
%>
<c:forEach items="${ pageScope.map }" var="entry" varStatus="status">
  ${ status.index } - ${ entry.key }: ${ entry.value }<br>
</c:forEach>
</body>
</html>

输出结果:

i: 0
i: 2
i: 4
i: 6

------

0 - contact: gabrielxd@outlook.com
1 - nickname: GabrielxD
2 - link: https://gabrielxd.top

JavaBeans

简介

JavaBeans 是 Java 中一种特殊的类,可以将多个对象封装到一个对象(bean)中。特点是可序列化,提供无参构造器,提供 getter 和 setter 方法访问对象的属性。名称中的“Bean”是用于 Java 的可重用软件组件的惯用叫法。

JavaBeans 规范

  • 有一个 public 的无参数构造函数
  • 属性可通过 getsetis(可替代get,用在布尔型属性上) 方法或遵循特定命名规则的其他方法访问
  • 可序列化

作用

JavaBeans 一般用来和数据库的字段做 ORM(Object Relational Mapping / 对象关系映射)。

  • 表 -> 类
  • 字段 -> 属性
  • 行记录 -> 对象

实例

数据库中的 Person 表:

id name age deceased
1 A 18 false
2 B 32 false
3 C 57 true

top.gabrielxd.pojo.PersonBean

package top.gabrielxd.pojo;

public class PersonBean {
    private String name;
    private int age;
    private boolean deceased;

    public PersonBean() { }

    public PersonBean(String name, int age, boolean deceased) {
        this.name = name;
        this.age = age;
        this.deceased = deceased;
    }

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }

    public int getAge() { return age; }

    public void setAge(int age) { this.age = age; }

    public boolean isDeceased() { return deceased; }

    public void setDeceased(boolean deceased) { this.deceased = deceased; }
}

top.gabrielxd.pojo.TestPersonBean

package top.gabrielxd.pojo;

public class TestPersonBean {
    public static void main(String[] args) {
        PersonBean personA = new PersonBean("A", 18, false);
        PersonBean personB = new PersonBean("B", 32, false);
        PersonBean personC = new PersonBean("C", 57, true);
    }
}

testPersonBean.jsp

<%@ page import="top.gabrielxd.pojo.PersonBean" %>
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

<jsp:useBean id="person" class="top.gabrielxd.pojo.PersonBean" scope="page"/>
<jsp:setProperty name="person" property="name" value="A"/>
<jsp:setProperty name="person" property="age" value="18"/>
<jsp:setProperty name="person" property="deceased" value="true"/>

name: <jsp:getProperty name="person" property="name"/><br>
age: <jsp:getProperty name="person" property="age"/><br>
deceased: <jsp:getProperty name="person" property="deceased"/><br>

</body>
</html>

Filter

简介

Filter 表示过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。

过滤器可以把对资源的请求拦截下来,统一进行一些操作,比如:处理中文乱码、登陆验证…

image-20220909155138161

基本使用

  1. 定义过滤器类实现 javax.servlet.Filter 接口,然后实现三个其中的三个方法 init()doFilter()destory(),在 doFilter() 方法中编写过滤器需要实现的逻辑,这里我们让请求和响应都以 UTF-8 编码以解决中文乱码问题。
    CharacterEncodingFilter

    package top.gabrielxd.filter;
    
    import javax.servlet.*;
    import java.io.IOException;
    
    public class CharacterEncodingFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("CharacterEncodingFilter 初始化");
        }
    
        @Override
        public void doFilter(ServletRequest req, ServletResponse resp,
                             FilterChain chain) throws IOException, ServletException {
            System.out.println("进入 CharacterEncodingFilter");
            req.setCharacterEncoding("utf-8");
            resp.setCharacterEncoding("utf-8");
            // 让程序继续往下访问用户的目标资源
            chain.doFilter(req, resp);
            System.out.println("继续执行");
        }
    
        @Override
        public void destroy() {
            System.out.println("CharacterEncodingFilter 销毁");
        }
    }
    
  2. web.xml 中配置 <filter><filter-mapping>

    <!-- 配置过滤器 -->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>top.gabrielxd.filter.CharacterEncodingFilter</filter-class>
    </filter>
    <!-- 路径以 /servlet 开头的资源被过滤 -->
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/servlet/*</url-pattern>
    </filter-mapping>
    
  3. 定义测试用的 Servlet 类,并在 web.xml 中配置其映射。

    HelloServlet

    package top.gabrielxd.servlet;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.*;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    public class HelloServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            resp.setContentType("text/html");
            PrintWriter out = resp.getWriter();
            out.write("\t<h1>Hello Servlet!</h1>");
            out.write("\t<h1>中文测试</h1>");
        }
    }
    
    
    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>top.gabrielxd.servlet.HelloServlet</servlet-class>
    </servlet>
    <!-- 把 HelloServlet 同时映射在 /servlet/hello 与 /hello -->
    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/servlet/hello</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
    
  4. 可以看到访问 /hello 时未经过过滤器设置编码格式,中文为乱码状态。
    而访问 /servlet/hello 时经过了过滤器设置编码格式为 UTF-8,中文显示正常。

    image-20220909163322451

Listener

简介

Listener 表示监听器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。

监听器可以监听就是在 applicationsessionrequest 三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件。

  • applicationServletContext 类型的对象。
  • ServletContext 代表整个web应用,在服务器启动的时候,tomcat会自动创建该对象。在服务器关闭时会自动销毁该对象。

JavaWeb 提供了 8 个监听器:

监听器分类 监听器名称 作用
ServletContext 监听 ServletContextListener 对 ServletContext 对象进行监听(创建、销毁)
ServletContextAttributeListener 对 ServletContext 对象中的属性进行监听(增删改属性)
Session 监听 HttpSessionListener 对 Session 对象进行监听(创建、销毁)
HttpSessionAttributeListener 对 Session 对象中的属性进行监听(增删改属性)
HttpSessionBindingListener 监听对象于 Session 的绑定与接触
HttpSessionActivationListener 对 Session 数据的钝化和活化进行监听
Request 监听 ServletRequestListener 对 Request 对象进行监听(创建、销毁)
ServletRequestAttributeListener 对 Request 对象中的属性进行监听(增删改属性)

基本使用

  1. 定义监听器类实现需要用到的监听器接口(上面的八种监听器之一),然后实现其提供的方法。
    这里我们实现了 ServletRequestListener 类,它有两个方法 requestInitialized()requestDestroyed() 分别在请求创建和销毁时触发。我们重写请求创建时触发的逻辑:
    requestCountListener

    package top.gabrielxd.listener;
    
    import javax.servlet.ServletContext;
    import javax.servlet.ServletRequestEvent;
    import javax.servlet.ServletRequestListener;
    
    public class requestCountListener implements ServletRequestListener {
        @Override
        public void requestInitialized(ServletRequestEvent evt) {
            ServletContext context = evt.getServletContext();
            Integer requestCount = (Integer) context.getAttribute("requestCount");
            if (requestCount == null) requestCount = 1;
            else ++requestCount;
            context.setAttribute("requestCount", requestCount);
        }
    
        @Override
        public void requestDestroyed(ServletRequestEvent evt) { }
    }
    
  2. web.xml 中配置监听器(<listener> 标签):

    <listener>
        <listener-class>top.gabrielxd.listener.requestCountListener</listener-class>
    </listener>
    
  3. 在 JSP 中展示请求(监听器触发)的次数:

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    <h1>已收到请求 <span style="color: #6cf;">${ requestCount }</span> 次</h1>
    </body>
    </html>
    

参考

0

评论区