JavaWeb学习笔记
JavaWeb 学习笔记
1 基本概念
1.1 什么是 Web 开发
概念:Web 就是网页的意思,顾名思义,Web 开发指的就是网页开发
1.2 Web 开发分类
静态 Web:所有人可以看到的,始终不发生变化的数据,例如 HTML、CSS...
动态 Web:提供给所有人看,会发生变化的数据,每个人在不同时间、不同地点看到的是数据都是各不相同的
那么,在 Java 中,将开发动态 Web 的技术统称为 JavaWeb
1.3 Web 应用程序
概念:Web 应用程序,就是可以提供浏览器访问的程序
一个 Web 应用程序有多个部分组成:
-
HTML、CSS、JS
-
jsp、servelt
-
Java 程序
-
jar 包
-
配置文件
一个 Web 应用程序编写完成后,若想提供给外界访问,就需要一个服务器来统一管理
1.4 静态 Web
以客户端打开 index.html 页面为例,客户端向服务器发起访问请求,Web Service 去服务器中找该页面,若服务器中若存在静态资源 index.html,则服务器会发送响应给客户端,这样客户端就可以打开该网页
静态 Web 缺点
-
Web 页面无法实时更新,所有用户看到的都是同一个页面
-
无法实现和数据库交互,数据无法持久化,用户无法交互
1.5 动态 Web
概念:动态 Web 是指,Web 数据显示的结果因人而异,不同的用户对应不同的数据显示结果
动态 Web 缺点:如果加入服务器的动态 Web 资源出错,就需要重新编写后台程序,重新发布(网站停机维护)
动态 Web 优点
-
Web 页面可以实时动态刷新,所有用户看到的都是各不相同的页面及数据
-
可以实现与数据库的交互(JDBC),可以实现数据的持久化
2 Web 服务器
2.1 什么是 Web 服务器
Web 服务器是一种被动的操作,用来处理用户的一些请求和给用户响应一些信息
2.2 Tomcat
-
Tomcat 是 Apache 软件基金会的 Jakarta 项目中的一个核心项目,最新的 Servlet 和 JSP 规范总是能在 Tomcat 中得到体现
-
技术先进、性能稳定,而且免费
-
Tomcat 属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试 JSP 程序的首选
-
Tomcat 实际上运行 JSP 页面和 Servlet
-
Tomcat 最新版本为 10.0.14
3 Tomcat
3.1 安装 Tomcat
首先,点击进入 Tomcat官网:Apache Tomcat® - Welcome!
选择 Tomcat 9,下载 windows 环境下的 64 位版本
最后,将下载的压缩解压
3.2 Tomcat 启动和配置
3.2.1 启动和关闭
双击安装目录下的 startup.bat
文件,启动后,去浏览器打开 8080
端口,发现已经成功启动
双击运行安装目录下的 shutdown.bat
关闭应用
3.2.2 配置
修改端口号
-
Tomcat 的默认端口号为 8080端口
-
安装路径下的 conf 目录下,可以通过修改
server.xml
文件来改变
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
修改主机名
-
Tomcat 默认主机名为
localhost
-
同样可以通过修改 conf 目录下的
server.xml
来改变 -
若修改此处的主机名之后,还要修改系统文件
C:/System32/drivers/etc/hosts
文件中的映射关系
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
阿里高难度面试题:请你谈一谈网站是如何进行访问的?
客户端输入一个域名后,点击搜索按钮
首先会检查本机的
C:/System32/drivers/etc/hosts
文件是否有这个域名的映射
有,直接返回对应的 ip 地址,在这个地址中,有我们需要访问的 Web 程序,可以直接进行访问
没有,去 DNS 服务器中找,找到的话就返回,找不到就返回 404
3.3 发布一个网站
自己写的网站,放入 Tomcat 服务器中指定的 Web 应用文件夹(webapps)之下,就可以进行访问了
|webapps // Tomcat 服务器的 web 目录
|———— ROOT
|———— MyWeb // 自己网站的目录名
|———— index.html // 网站默认首页
|———— WEB-INF
|———— class // java 应用程序
|———— lib // web 应用所依赖的 jar 包
|———— web.xml // 网站配置文件
|———— static //静态资源文件
|———— css //css 文件
|———— js //js 文件
|———— img //图片文件
|———— ... //其他资源
4 HTTP
4.1 什么是 http
-
http:超文本传输协议(Hyper Text Transfer Protocol,HTTP)是一个简单的请求 - 响应协议,它通常运行在 TCP 之上
-
文本:字符串、HTML...
-
超文本:图片、视频、音频、定位、地图...
-
默认端口:80
-
-
https:全称(Hyper Text Transfer Protocol over SecureSocket Layer),是以安全为目标的 HTTP 通道,在 HTTP 的基础上通过传输加密和身份认证保证了传输过程的安全性
- 默认端口:443
4.2 http 的两个时代
-
http 1.0 时代:客户端与 Web 服务器建立连接之后,只能获得一个 Web 资源,然后会断开链接
-
http 1.1 时代:客户端与 Web 服务器建立连接之后,可以获得多个 Web 资源
4.3 http 请求
以访问百度为例
- 客户端 → 请求(Request)→ 服务器
General:
Request URL: https://www.baidu.com/ //请求地址
Request Method: GET //请求方式(GET/POST)
Status Code: 200 OK //状态码
Remote Address: 36.152.44.95:443 //远程地址(server + port)
...
Request Headers:
Accept: text/html
Accept-Encoding: gzip, deflate, br //编码
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 //语言
Cache-Control: max-age=0 //缓存控制
Connection: keep-alive //链接保持
Host: www.baidu.com //主机名
...
4.3.1 请求行
-
请求行中的请求方式:GET
-
请求方式:Get、Post 、HEAD、GELETE、PUT、TRACT...
-
Get:请求可以携带的参数较少,大小有限制,会在 url 中显示数据内容,不安全但高效
-
Post:请求可以携带的参数无限制,大小无限制,不会在 url 中显示数据内容,安全但不高效
-
4.3.2 消息头
Accept-Encoding //告诉浏览器,它所支持的数据类型
Accept-Language //支持的编码格式(UTF-8、GN2312、GBK、ISO8859-1...)
Cache-Control //缓存控制
Connection //告诉浏览器,请求完成后是断开还是继续保持链接
Host //主机名称
4.4 http 响应
以访问百度为例
- 服务器 → 响应(Response)→ 客户端
General:
Request URL: https://www.baidu.com/ //请求地址
Request Method: GET //请求方式(GET/POST)
Status Code: 200 OK //状态码
Remote Address: 36.152.44.95:443 //远程地址(server + port)
...
Response Headers:
Cache-Control: private //缓存控制
Connection: keep-alive //链接保持
Content-Encoding: gzip //编码
Content-Type: text/html;charset=utf-8 //类型
...
4.4.1 响应体
Accept-Encoding //告诉浏览器,它所支持的数据类型
Accept-Language //支持的编码格式(UTF-8、GN2312、GBK、ISO8859-1...)
Cache-Control //缓存控制
Connection //告诉浏览器,请求完成后是断开还是继续保持链接
Host //主机名称
Refresh //告诉客户端,多久刷新一次
Location //让网页重新定位
4.4.2 响应状态码
-
200:请求响应成功
-
3xx:请求重定向
-
4xx:找不到资源
-
5xx:服务器出错
面试题:当在浏览器地址栏中输入地址并回车的一瞬间,直到页面被展示出来,经历了什么?
5 Maven
5.1 为什么使用 Maven
在 JavaWeb 开发中,需要使用大量的 jar 包,难道要去手动一个个导入吗?那效率也太低了,而且也很容易出错
那么与没有一种可以自动帮助导入 jar 包并且自动配置的工具呢?Maven 应运而生!
5.2 Maven 项目架构管理工具
Maven 的核心思想:约定大于配置! (有约束,就不要去违反)
Maven 会规定好我们应该如何去编写 Java 代码,必须按照这个规范来执行!
5.3 Maven 的下载
首先进入 Maven 官网 :Maven – Welcome to Apache Maven
按照自己的电脑系统,下载对应的压缩包
下载完成后,解压得到如下目录
5.4 配置环境变量
我的 Maven 安装路径为:
E:\PROGRAM_PATH\MAVEN\apache-maven-3.8.5
总共需要配置以下路径:
-
M2_HOME
-
MAVEN_HOME
-
然后在 Path 中添加引用
%MAVEN_HOME\bin%
-
在 cmd 命令行输入
mvn -version
,出现如下界面,表示添加成功
5.5 配置阿里镜像
为了加速下载,需要配置国内镜像,这里使用阿里云的镜像
在配置文件 E:\PROGRAM_PATH\MAVEN\apache-maven-3.8.5\conf\setting.xml
文件中找到 <mirrors>
标签,添加阿里云镜像
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>aliyun public repository</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
5.6 创建本地仓库
这里我选择在 maven 安装路径的同级目录下创建一个 Maven 本地仓库
同样,在上一步的配置文件中找到标签 <localRepository>
,添加本地仓库配置
<localRepository>E:/PROGRAM_PATH/MAVEN/localRepository/</localRepository>
5.7 项目配置 Maven
创建一个 Maven 项目有两种方式:采用默认的 Maven 模板和不使用模板,这是选择手动配置,不使用模板进行创建
在 IDEA 中左上角【File】→ 【New Project】,弹出创建项目界面
左边选择创建一个 Maven 项目,右边选择好 jdk 版本,然后点击【Next】
填写项目名称,点击右下角的【Finish】
出现如下界面,一个很清爽的 Maven 项目初步创建完成
然后去 Maven Repository 找到自己需要的依赖粘贴复制进 pom 文件中, Maven 会帮助我们导入所有的依赖
5.8 创建并启动 JavaWeb 项目
首先需要使用 Maven 模板创建项目,步骤与 5.7 类似,唯一不同是需要勾选【create from archtype】,下方的模板选择【webapp】结尾的那个,然后点击【Next】
接下来填写项目名称以及项目存放地址,点击【Next】
点击右边的小三角,将 Maven 从 IDEA 自带的切换为我们自己的
Maven 默认会选择电脑 C 盘目录下的 setting.xml 文件和本地仓库,这里应该更换为我们自己设置的,先点击右边的【Override】按钮,然后分别选择我们的 Maven 自定义安装目录下的 setting.xml 文件和本地仓库,然后点击【Finish】,等待项目创建成功
然后点击主界面最右边的【Maven 工具栏】,依次点击【clean】、【compile】、【install】按钮,等待执行完成
可以发现,模板默认为我们生成了一个 index.jsp 文件
那么,怎么启动项目呢?因为是一个 JavaWeb 应用,所以需要 Tomcat 来充当服务器的角色,接下来需要配置 Tomcat
开始配置 Tomcat 服务器,点击主界面右上角的【Add Configuration】,添加 Tomcat 配置
在弹出的界面中,选择左上角的【+】号,然后点击 Tomcat Server 栏目下的【Local】按钮
在弹出的界面中,先给本项目的 Tomcat 命名,如 Tomcat 9,然后点击下方的【Configure】按钮选择 Tomcat 的安装路径以导入 Tomcat(一般会自动识别出来),然后检查一下 Tomcat 的启动端口是否正确(我设置的为 8088)
在这里需要检查一下该页面的【URL】是否正确,观察项目结构可知,此处的 URL 应该定位到 webapp 文件夹,需要重新配置
因此,手动添加 URL 路径
接下来点击第二项【Deployment】,在该界面中,点击右边的【+】号,然后选择下方的【Exteral Source】按钮
在弹出的界面中选择 webapp 所在的文件夹,然后点击【OK】
回到原来的页面,依次点击【Aplly】、【OK】
点击主界面右上角的启动按钮
出现如下显示,说明项目成功启动
成功启动后,IDEA 会自动打开浏览器(若没有自动打开,可以浏览器手动输入项目的 URL 地址进行访问),可以看到,一个最简单的 JavaWeb 项目创建并启动成功!
5.9 pom 文件
创建好一个 Web 项目之后,会自动生成一个 pom 配置文件,该文件时 Maven 的核心配置文件,文件具体内容如下
<?xml version="1.0" encoding="UTF-8"?>
<!--Maven 版本和头文件-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--创建项目时配置的 GAV-->
<groupId>org.example</groupId>
<artifactId>com.jiuxiao.webapp</artifactId>
<version>1.0-SNAPSHOT</version>
<!--项目打包方式 jar 包: Java 应用 / war 包: JavaWeb 应用-->
<packaging>war</packaging>
<!--无关紧要的信息,删除也行-->
<name>com.jiuxiao.webapp Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<!--配置-->
<properties>
<!--项目默认构建编码-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!--编码的版本-->
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<!--项目依赖-->
<dependencies>
<!--具体依赖的 jar 包配置文件-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<!--项目构建用的东西-->
<build>
<finalName>com.jiuxiao.webapp</finalName>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
5.10 Maven 的小问题
由于 Maven 强调 “约定大于配置”,所以有可能会遇到我们自己写的配置文件,无法被导出或者失效的问题,因此我们需要去 bulid 中配置 resources:
<!--配置 resources,防止资源导入失败的问题-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>**/*.properties</exclude>
<exclude>**/*.xml</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
5.11 简单的 Servlet 程序
5.11.1 什么是 Servlet
Servlet(Server Applet)是 Java Servlet 的简称,称为小服务程序或服务连接器,用 Java 编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态 Web 内容
5.11.2 初识 Servlet
创建一个新的 JavaWeb 项目,新建测试类 HelloServlet
,该类需要继承 HttpServlet
,所以首先需要导入 Servlet
依赖
<!--Servlet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
按照 Tomcat 自带的例子,重写 doGet
和 doPost
方法
package com.jiuxiao.servlet;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet测试
*
* @author WuDaoJiuXiao
* @since 1.0.0
*/
public class HelloServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet测试</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Servlet测试</h1>");
out.println("</body>");
out.println("</html>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
然后需要在 web.xml
中注册 Servlet 并建立映射
<display-name>Archetype Created Web Application</display-name>
<!--注册 Servlet-->
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.jiuxiao.servlet.HelloServlet</servlet-class>
</servlet>
<!--一个Servlet对应一个 mapping-->
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<!-- 设置请求路径-->
<url-pattern>/jiuxiao</url-pattern>
</servlet-mapping>
新建一个测试用的静态网页文件 header.html
启动 Tomcat,自动打开的仍然是原来的 index.jsp
页面,那么怎么打开我们自己创建的测试页面呢?只需要在网址后面加上 header.html
即可
那么,怎么访问刚刚我们写的测试类 HelloServlet
类呢?
分析一下,在 web.xml
中我们已经将测试类 HelloServlet
进行了注册,然后给了它一个映射,映射路径 /jiuxiao
,所以,在地址栏中加上 /jiuxiao
后是否就能访问了呢?重启项目后访问,发现果然如我们所想,可以访问
但是,出现了问题,为什么测试类中的 "测试" 两个字乱码呢?一般应该考虑编码的问题,网页右键检查元素,我们发现,响应头的默认编码格式为 Java
的默认编码格式 ISO-8859-1
,而中文一般都是 UTF-8
编码,所以,是不是改变编码格式就行了呢?
回到测试类 HelloServlet
中,设置编码格式为 UTF-8
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8"); //设置编码为 UTF-8
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet测试</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Servlet测试</h1>");
out.println("</body>");
out.println("</html>");
}
然后重启 Tomcat,再次访问,发现已经可以正常访问,果然是编码问题
6 Servlet
6.1 Servlet 简介
Servlet 就是 sun 公司开发的一种用来开发动态 Web 的技术
Sun 在这些 API 中提供一个接口叫做 Servlet,如果想开发一个 Servlet 程序,有以下两步骤
-
编写一个类,实现 Servlet 接口
-
把开发好的 Java 类部署到 Web 服务器中
其本质还是 Java 程序,只不过是实现了 Servlet 接口的 Java 程序
6.2 HelloServlet
创建一个普通的 Maven 项目(不适用模板),创建好之后删除 src
目录,只留下 .idea
文件夹、pom.xml
依赖文件以及 IDEA 识别项目类型的 servlet-text.iml
文件,此项目作为父工程,以后在里面创建子工程 Module
,只需要用父工程的 pom
文件进行管理即可,尽可能的将要使用的依赖导入到主工程中
6.2.1 父子工程
在刚才的主工程 servlet-test
里面新建一个 Module
子工程 servlet-01
,那么父与子项目的 pom 文件中会有如下区别
- 父工程
<modules>
<!-- servlet-01 就是子项目名称-->
<module>servlet-01</module>
</modules>
- 子工程
<parent>
<!-- 子项目的 pom 文件中需要加入父工程的坐标-->
<artifactId>javaweb-servlet</artifactId>
<groupId>com.jiuxiao</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
父项目中 jar 包子项目可以直接使用,反之,子项目中的 jar 包父项目不能使用
6.2.2 Maven 环境优化
- 将子项目中的
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">
</web-app>
-
按照 Maven 的约束,将项目创建完整
6.2.3 创建 Servlet 程序
在子项目中创建测试类 Servlet
测试类,继承 HttpServlet
类
思考:前文 6.1 中已经提到,要编写
Servlet
程序,就需要实现Servlet
接口,那么问题来了,为什么这里我们编写的测试类HelloServlet
继承的是HttpServlet
类,却不是前面提到的Servlet
呢?我们点进
HttpServlet
类,查看源码,发现HttpServlet
继承了GenericServlet
类再点进
GenericServlet
源码,发现它继承了Servlet
接口再点进
Servlet
源码,发现他只有以下五个方法:其中初始化
init()
、获取配置getServeltConfig()、getServletInfo()
、销毁destory()
什么的都不重要,重要的是service
接口,他有两个参数ServletRequset
和ServletResponse
两个参数,这不正是我们测试类继承HttpServlet
后重写doGet()
和doPost()
两个主要方法时传进去的两个参数吗?那么,我们追根溯源,逆着往回走,返回
GenericServlet
类,找到service()
方法,发现他并没有具体去实现该方法继续往回走,回到
HttpServlet
类,我们发现,直到这里才具体实现了service()
方法并且可以发现,该类已经具体实现了最重要的
doGet()、doPost()
方法但是,它实现的并不是我们需要的,因此需要我们继承之后对其进行重写!
继续往回走,回到我们的测试类
HelloServlet
,所以我们重写了doGet()、doPost()
方法经过以上分析,我们明白了为什么要继承
HttpServlet
类,而不是Servlet
类的原因,具体继承关系如下所示
最终测试用的程序如下:
package com.jiuxiao.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;
/**
* 测试Servlet
*
* @author WuDaoJiuXiao
* @since 1.0.0
*/
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter();
writer.print("Hello, Servlet!");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
6.2.4 编写 Servlet 映射
我们编写的测试类,本质上仍然是 Java 程序,但是浏览器是不能直接访问 Java 程序的,浏览器只能访问 Web 服务器,所以需要将测试类注册到 Web 中,并且为其建立一个对应的路径来让浏览器可以访问它!
在 web.xml 中进行注册并且添加映射
<!--注册 Servlet-->
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.jiuxiao.servlet.HelloServlet</servlet-class>
</servlet>
<!--映射 Servlet 路径-->
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
6.2.5 配置 Tomcat
按照步骤 5.8 配置好 Tomcat 以及项目发布路径 URL
6.2.6 启动项目
按照步骤 5.8 启动项目,发现可以成功访问,那么证明一个最简单的 Servlet 项目成功创建并运行
这只是默认的 index.jsp
首页,想要访问我们的测试类 HelloServlet
,就需要加上我们当初配置好的映射路径,我这里为 /hello,加上后访问,发现也成功访问
6.3 Servlet 原理
6.4 Mapping 问题
- 一个 Servlet 可以映射一个路径
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
- 一个 Servlet 可以映射多个路径
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello2</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello3</url-pattern>
</servlet-mapping>
- 一个 Servlet 可以映射通用路径
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello/*</url-pattern>
</servlet-mapping>
- 默认请求路径
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
直接进入了我们的测试类,没有进入默认的 index.jsp,所以不推荐这样映射
- 可以指定前缀、后缀等
<!--可以自定义后缀实现请求映射 注意:* 前边不能加任何醒目映射的路径-->
<servlet-mapping>
<servlet-name>hello</servlet-name>
<!--报错,* 前边加了东西-->
<url-pattern>/*.skg</url-pattern>
<!--不会出错-->
<url-pattern>*.skg</url-pattern>
</servlet-mapping>
6.5 映射路径的优先级
假设,我们有一个类 ErrorServlet
专门处理错误请求,如下:
package com.jiuxiao.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;
/**
* Servlet 错误处理
*
* @author WuDaoJiuXiao
* @since 1.0.0
*/
public class ErrorServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html");
PrintWriter writer = resp.getWriter();
writer.print("<h2 align=\"center\">页面走丢了...</h2>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
然后去 web.xml 中注册
<!--Hello-->
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.jiuxiao.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
<!--404-->
<servlet>
<servlet-name>error</servlet-name>
<servlet-class>com.jiuxiao.servlet.ErrorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>error</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
因为在 error 处理中,我们注册的路径是 /*
,那么,我们配置的另一个 hello 映射的路径是 /hello
,它也属于 /*
的一种,会不会被直接映射到 error 呢?
启动项目,发现初始页面为我们自定义的 error 映射,输入不存在的请求,也是返回 error 映射,说明 error 映射生效了
然后改变请求路径为 /hello
,发现可以正常访问,/hello
路径并没有被 /*
拦截
因此,我们得出一个结论:如果指定了固有的映射路径,那么他的优先级最高,除非找不到,才会去走默认的处理请求
6.6 ServletContext
6.6.1 共享数据
Web 容器在启动的时候,它会为每个 Web 程序都创建一个对应的 ServletContext 对象
它代表当前所有的 Web 对象所对应的 Web 程序,共用一个 ServletContext 对象,也就是说,程序之间的数据可以相互之间共享
创建设置信息类 HelloServlet
和获取信息类 GetServlet
package com.jiuxiao.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;
/**
* Servlet 设置信息
*
* @author WuDaoJiuXiao
* @since 1.0.0
*/
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
String username = "荒天帝";
//将信息添加进 ServletContext 中
context.setAttribute("username", username);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
package com.jiuxiao.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;
/**
* 获取 Servlet 信息
*
* @author WuDaoJiuXiao
* @since 1.0.0
*/
public class GetServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
//从 ServletContext 中获取添加的信息
String username = (String) context.getAttribute("username");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html");
resp.getWriter().print("姓名:" + username);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
配置好两个类的映射路径
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.jiuxiao.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>getc</servlet-name>
<servlet-class>com.jiuxiao.servlet.GetServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>getc</servlet-name>
<url-pattern>/getc</url-pattern>
</servlet-mapping>
启动项目,默认来到了 index.jsp
页面
输入获取类的映射路径 /getc
,访问,我们发现,获取到的 username 值为 null
为什么呢?这是因为,此时,程序还没有走 HelloServlet
方法,那么 ServletContext
的总对象中也就没有数据,所以获取到的值为空,因此,要先设置属性值(先访问 /hello
),然后再 /getc
获取数据
可以发现,获取到了数据,这样就实现了不同 Web 程序间的数据的共享
程序执行的原理如下所示:
6.6.2 获取初始化参数
创建一个获取初始化参数的测试类 GetParam
package com.jiuxiao.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;
/**
* 获取 ServletContext 初始化参数
*
* @author WuDaoJiuXiao
* @Date 2022/4/12 15:58
* @since 1.0.0
*/
public class GetParam extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
String url = context.getInitParameter("mysql-url");
resp.getWriter().print(url);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
配置对应的 Servlet 映射
<!--GetParam-->
<servlet>
<servlet-name>gp</servlet-name>
<servlet-class>com.jiuxiao.servlet.GetParam</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>gp</servlet-name>
<url-pattern>/gp</url-pattern>
</servlet-mapping>
配置初始化参数,比如数据库的链接配置
<!--数据库连接配置-->
<context-param>
<param-name>mysql-url</param-name>
<param-value>jdbc:mysql://localhost:3306</param-value>
</context-param>
启动项目,初始化参数读取成功
6.6.3 请求转发
创建一个请求转发的测试类 ServletDispatcher
,并注册路径,转发请求到 6.6.2 中的 /gp
页面中去
package com.jiuxiao.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;
/**
* 请求转发测试
*
* @author WuDaoJiuXiao
* @Date 2022/4/12 16:41
* @since 1.0.0
*/
public class ServletDispatcher extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
//context.getRequestDispatcher() 只是设置了请求转发的路径
//调用 forward() 才会转发
context.getRequestDispatcher("/gp").forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
<!--ServletDispatcher-->
<servlet>
<servlet-name>sd</servlet-name>
<servlet-class>com.jiuxiao.servlet.ServletDispatcher</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>sd</servlet-name>
<url-pattern>/sd</url-pattern>
</servlet-mapping>
启动项目之后,我们访问 /sd
页面,发现经过他转发之后返回的页面仍然是 /gp
,也就是说,只是转发了请求,本质上还是请求的 /gp
页面(与重定向不同,重定向路径会发生改变,请求转发不改变路径)
请求转发的原理如下:
6.6.4 读取资源文件
在项目的 java/com/jiuxiao/servlet
目录下新建 db.properties
资源配置文件
username=skg
password=123456
注册 Servelt 路径映射
<!--ServletProperties-->
<servlet>
<servlet-name>sp</servlet-name>
<servlet-class>com.jiuxiao.servlet.ServletProperties</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>sp</servlet-name>
<url-pattern>/sp</url-pattern>
</servlet-mapping>
使用 Maven 重新清理项目,再次启动项后后发现,两个配置文件都被生成在了统一目录下,即 classes
目录,我们俗称该路径为 classpath
既然知道了配置资源的路径,那么就可以通 ServletContext 来读取资源文件的具体内容
新建读取资源测试类 ServletProperties
package com.jiuxiao.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.InputStream;
import java.util.Properties;
/**
* 读取资源文件
*
* @author WuDaoJiuXiao
* @Date 2022/4/12 17:22
* @since 1.0.0
*/
public class ServletProperties extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
InputStream is = this.getServletContext().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().print("username :" + username + " " + "password : " + password);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
启动项目,成功读取到了资源文件得配置
6.7 HttpServletResponse
Web 服务器接收到客户端的 http 请求,针对这个请求,分别创建一个代表该请求的 HttpServletRequest 对象,代表响应的一个 HttpServletResponse
- 如果要获取客户端请求过来的参数,找 HttpServletRequest
- 如果要响应给客户端一些数据,找 HttpServletResponse
6.7.1 方法分类
- 负责向浏览器发送数据的方法
ServletOutputStream getOutputStream() throws IOException;
PrintWriter getWriter() throws IOException;
- 负责向浏览器发送响应头的方法
void setCharacterEncoding(String var1);
void setContentLength(int var1);
void setContentLengthLong(long var1);
void setContentType(String var1);
void setBufferSize(int var1);
void setDateHeader(String var1, long var2);
void setHeader(String var1, String var2);
void setIntHeader(String var1, int var2);
void setStatus(int var1);
- 响应的一些状态码
int SC_CONTINUE = 100;
int SC_SWITCHING_PROTOCOLS = 101;
int SC_OK = 200;
int SC_CREATED = 201;
int SC_ACCEPTED = 202;
int SC_NON_AUTHORITATIVE_INFORMATION = 203;
int SC_NO_CONTENT = 204;
int SC_RESET_CONTENT = 205;
int SC_PARTIAL_CONTENT = 206;
int SC_MULTIPLE_CHOICES = 300;
int SC_MOVED_PERMANENTLY = 301;
int SC_MOVED_TEMPORARILY = 302;
int SC_FOUND = 302;
int SC_SEE_OTHER = 303;
int SC_NOT_MODIFIED = 304;
int SC_USE_PROXY = 305;
int SC_TEMPORARY_REDIRECT = 307;
int SC_BAD_REQUEST = 400;
int SC_UNAUTHORIZED = 401;
int SC_PAYMENT_REQUIRED = 402;
int SC_FORBIDDEN = 403;
int SC_NOT_FOUND = 404;
int SC_METHOD_NOT_ALLOWED = 405;
int SC_NOT_ACCEPTABLE = 406;
int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
int SC_REQUEST_TIMEOUT = 408;
int SC_CONFLICT = 409;
int SC_GONE = 410;
int SC_LENGTH_REQUIRED = 411;
int SC_PRECONDITION_FAILED = 412;
int SC_REQUEST_ENTITY_TOO_LARGE = 413;
int SC_REQUEST_URI_TOO_LONG = 414;
int SC_UNSUPPORTED_MEDIA_TYPE = 415;
int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
int SC_EXPECTATION_FAILED = 417;
int SC_INTERNAL_SERVER_ERROR = 500;
int SC_NOT_IMPLEMENTED = 501;
int SC_BAD_GATEWAY = 502;
int SC_SERVICE_UNAVAILABLE = 503;
int SC_GATEWAY_TIMEOUT = 504;
int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
6.7.2 实现重定向(重要)
- 重定向原理:一个 Web 资源收到客户端索要资源的请求后,会通知客户端去访问另外一个 Web 资源,本质上改变了请求的路径,这个过程叫做重定向
建立重定向测试类 RedirectServlet
,并在 Servlet 中注册,注册路径位 /red
package com.jiuxiao.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 重定向测试
*
* @author WuDaoJiuXiao
* @Date 2022/4/13 17:28
* @since 1.0.0
*/
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.sendRedirect("http://www.bilibili.com");
//重定向本质就是程序帮我们设置了响应状态码和响应头
//resp.setHeader("Location", "https://www.bilibili.com");
//resp.setStatus(302);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
<!--RedirectServlet-->
<servlet>
<servlet-name>redirect</servlet-name>
<servlet-class>com.jiuxiao.servlet.RedirectServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>redirect</servlet-name>
<url-pattern>/red</url-pattern>
</servlet-mapping>
启动项目,在地址栏的输入测试类的注册路径 /red
然后点击回车,可以发现浏览器开始加载新页面,说明重定向有效果
在调试窗口,找到响应头,检查其状态码发现是 302
,代表重定向状态
等待页面加载成功,成功来到了小破站,重定向成功
6.7.3 重定向小例子
尝试实现如下功能
每次启动项目之后,打开的页面为默认的
index.jsp
页面,我们修改该页面为简单的用户登录界面,点击提交之后,使用重定向,将页面跳转到success.jsp
页面,表示用户登录成功
创建登录测试类 RequestTest
,注册路径为 /login
package com.jiuxiao.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 登录请求测试
*
* @author WuDaoJiuXiao
* @Date 2022/4/13 20:02
* @since 1.0.0
*/
public class RequestTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println("用户登录成功");
System.out.println(username + " : " + password);
//重定向到 success.jsp 页面
//这里的路径要注意,要加上项目启动的默认路径,比如本项目就是 /res
resp.sendRedirect("/res/success.jsp");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
<!--RequestTest-->
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>com.jiuxiao.servlet.RequestTest</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
修改默认的项目启动页面 index.jsp
如下
<%--这里要设置浏览器的默认编码,否则登录页面会乱码--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<body>
<h2>用户登录</h2>
<%--这里的提交路径,需要寻找到项目的路径--%>
<%--${pageContext.request.contextPath} 代表当前路径--%>
<form action="${pageContext.request.contextPath}/login" method="post">
用户名: <input type="text" name="username"><br>
密 码: <input type="password" name="password"><br>
<input type="submit">
</form>
</body>
</html>
人为模仿一个登录成功的页面 success.jsp
,用户点击登录之后就要重定向到该页面
<%--
Created by IntelliJ IDEA.
User: YYH
Date: 2022/4/13
Time: 19:59
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Success</title>
</head>
<body>
<h2>Submit Success!</h2>
</body>
</html>
启动项目,默认启动界面为我们修改后的 index.jsp
页面
我们输入用户名和密码,点击提交,成功跳转到我们设置的重定向页面 success.jsp
返回控制台,控制台也成功打印了我们提交的用户信息,简单的重定向模拟登录的例子成功
6.7.4 下载文件
新建下载文件测试类 FileServlet
,并且注册到 Servlet 中,注册路径为 /down
package com.jiuxiao.servlet;
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;
/**
* 下载文件
*
* @author WuDaoJiuXiao
* @Date 2022/4/13 14:43
* @since 1.0.0
*/
public class FileServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取要下载的文件链接
String filePath = "E:\\MyCode\\test\\servlet-test\\response-01\\src\\main\\resources\\测试.png";
System.out.println("下载文件的路径为 : " + filePath);
//下载文件的文件名
String fileName = filePath.substring(filePath.lastIndexOf("\\") + 1);
//告诉浏览器我们需要下载的东西,中文文件名需要使用 URLEncoder.encode 编码
resp.setHeader("Content-Disposition", "attachment;file=" + URLEncoder.encode(fileName, "UTF-8"));
//获取下载文件的输入流
FileInputStream in = new FileInputStream(filePath);
//创建缓冲区
int len = 0;
byte[] buffer = new byte[1024];
//获取 OutputStream 对象
ServletOutputStream out = resp.getOutputStream();
//将 FileOutputStream 流写入到缓冲区,使用 OutputStream 将缓冲区数据输出到客户端
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
in.close();
out.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
<!--FileServlet-->
<servlet>
<servlet-name>fileDown</servlet-name>
<servlet-class>com.jiuxiao.servlet.FileServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>fileDown</servlet-name>
<url-pattern>/down</url-pattern>
</servlet-mapping>
启动项目,成功下载文件
6.7.5 验证码实现
新建验证码测试类 ImageServlet
,并且注册 Servlet 路径,注册路径为 /code
package com.jiuxiao.servlet;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
/**
* 验证码测试
*
* @author WuDaoJiuXiao
* @Date 2022/4/13 16:34
* @since 1.0.0
*/
public class ImageServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//让浏览器 3 秒刷新一次
resp.setHeader("refresh", "3");
//在内存中生成一张图片
BufferedImage image = new BufferedImage(55, 20, BufferedImage.TYPE_INT_BGR);
//创建画笔
Graphics2D pen = (Graphics2D) image.getGraphics();
pen.setColor(Color.WHITE);
//图片填充背景色
pen.fillRect(0, 0, 55, 20);
//给图片写数据
pen.setColor(Color.BLUE);
pen.setFont(new Font(null, Font.BOLD, 20));
pen.drawString(randomNums(), 0, 20);
//告诉浏览器,该请求使用图片方式打开
resp.setContentType("image/jpg");
//网站存在缓存,禁用浏览器缓存
resp.setDateHeader("expires", -1);
resp.setHeader("Cache-Control", "no-cache");
resp.setHeader("Pragma", "no-cache");
//将图片写给浏览器
ImageIO.write(image, "jpg", resp.getOutputStream());
}
//生成随机数
private String randomNums() {
Random random = new Random();
String num = random.nextInt(9999) + "";
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 4 - num.length(); i++) {
sb.append("0");
}
num += sb.toString();
return num;
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
<!--ImageServlet-->
<servlet>
<servlet-name>code</servlet-name>
<servlet-class>com.jiuxiao.servlet.ImageServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>code</servlet-name>
<url-pattern>/code</url-pattern>
</servlet-mapping>
启动项目,在浏览器中访问 /code
注册路径,发现验证阿妈生成成功,且每过 3 秒刷新一次
为什么能正确显示图片呢?检查响应头的信息,我们发现,在代码里设置的 Content-Type: image/jpg
生效了,所以浏览器才会将该请求识别为图片
6.8 HttpServletRequest
HttpServletRequest 的方法几乎与 HttpServletResponse 相比差不多,只不过都是相对的,这不是很重要,不多赘述,HttpServletRequest 重要的应用一般有两种:获取前端参数和请求转发
6.8.1 获取参数与请求转发
HttpServletRequest 中常用的方法有以下三个:
- 获取前端参数:
getParameter()
和getParameterValues()
- 请求转发:
getRequestDispatcher().forward()
新建测试类 LoginServlet
,注册 Servlet 路径为 /login
package com.jiuxiao.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.util.Arrays;
/**
* 获取前端参数
*
* @author WuDaoJiuXiao
* @Date 2022/4/13 20:47
* @since 1.0.0
*/
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
String username = req.getParameter("username");
String password = req.getParameter("password");
String[] hobbies = req.getParameterValues("hobbies");
System.out.println("--------------");
System.out.println(username + " : " + password);
System.out.println("hobbies : " + Arrays.toString(hobbies));
System.out.println("--------------");
//请求转发到页面 success.jsp
//这里若要把请求转发路径直接写死,需要注意 "/" 代表的已经是当前项目的默认路径
//不能写成 /req/success.jsp,应该是 /success.jsp
req.getRequestDispatcher("/success.jsp").forward(req, resp);
}
}
<!--LoginServlet-->
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>com.jiuxiao.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
修改默认 index.jsp
为我们自定义的登录页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录页面</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/login" method="post">
用户名: <input type="text" name="username"><br>
密 码: <input type="password" name="password"><br>
爱 好:
<input type="checkbox" name="hobbies" value="girl">女孩
<input type="checkbox" name="hobbies" value="code">代码
<input type="checkbox" name="hobbies" value="game">游戏
<input type="checkbox" name="hobbies" value="movie">电影
<br>
<input type="submit">
</form>
</body>
</html>
模拟一个登陆成功页面 success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登陆成功</title>
</head>
<body>
<h2> Login Success!</h2>
</body>
</html>
启动项目,打开了我们自定义的主页
填写信息,点击提交,成功跳转,实现了请求转发
进入后台,后台已经打印出了前端的 HttpServletRequest 的相关信息,这就是获取前端的参数和请求转发
6.8.2 转发与重定向区别(重要)
相同点:都会实现页面的跳转
不同点:
- 请求转发, URL 不会产生变化,状态码为 307(参照 6.8.1)
- 重定向,URL 会产生变化,状态码为 302(参照 6.7.3)
【重定向与请求转发辨析,重点!!!】
- 对于 6.7.3 的例子,实现的是重定向, URL 会发生改变
先访问了 index.jsp
页面,点击提交后,再访问了 success.jsp
页面,整个过程中,客户端一共和两个页面发生了直接资源交换 ,所以本质上 URL 发生了改变
- 对于 6.8.1 的例子,实现的是请求转发,URL 不会发生改变
先访问了 index.jsp
页面,点击提交后,登录请求被 HttpServletRequest
通过 getRequestDispatcher().forward()
方法进行了转发,转发到了 success.jsp
页面,然后 success.jsp
页面将它自己页面的资源返回给了 index.jsp
页面,整个过程中客户端只与第一个页面 index.jsp
发生了直接资源交换 ,所以本质上 URL 没有发生改变
7 Session 和 Cookie
7.1 什么是会话
会话:用户打开一个浏览器,点击了很多超链接,访问了多个 Web 资源,然后关闭了浏览器,这个过程可以称之为会话
思考:一个网站,怎么证明你来过?
- 服务端给客户端一个许可证,客户端下次访问服务端的时候带上许可证就可以了(Cookie)
- 服务端登记你曾经来过的信息,下次你来的时候,服务端匹配你的信息就可以了(Session)
有状态会话:一个同学来过教室,下次他再来这个教室,我们知道他曾经来过,这就称之为有状态会话
7.2 Cookie
实现一个小 Demo:用户登录后,服务端保存用户的 Cookie 数据,关闭浏览器重新打开之后,用户仍然可以自动登录
新建测试类 CookieDemo01
,注册路径为 cd1
package com.jiuxiao.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
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.Date;
/**
* Cookie 测试
*
* @author WuDaoJiuXiao
* @Date 2022/4/14 11:20
* @since 1.0.0
*/
public class CookieDemo01 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
Cookie[] cookies = req.getCookies();
//判断 Cookie 是否存在
if (cookies != null) {
out.write("你上次登录本站的时间为:");
//获取 Cookie 内容
for (int i = 0; i < cookies.length; i++) {
Cookie cookie = cookies[i];
if (cookie.getName().equals("lastLoginTime")) {
//获取 Cookie 中的值
long lastLoginTime = Long.parseLong(cookie.getValue());
Date date = new Date(lastLoginTime);
out.write(date.toLocaleString());
}
}
} else {
out.write("这是你第一次登录本站!");
}
//服务端给客户端响应一个 Cookie
Cookie cookie = new Cookie("lastLoginTime", System.currentTimeMillis() + "");
//设置 Cookie 有效期为一天
cookie.setMaxAge(24 * 60 * 60);
//响应头添加 Cookie
resp.addCookie(cookie);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
<!--CookieDemo01-->
<servlet>
<servlet-name>CookieDemo01</servlet-name>
<servlet-class>com.jiuxiao.servlet.CookieDemo01</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CookieDemo01</servlet-name>
<url-pattern>/cd1</url-pattern>
</servlet-mapping>
启动项目,第一次登录时,登录时间为空,(因为是第一次登录,所以 Cookie 无数据)
我们刷新一次网页,会显示上次登录的时间,在响应信息里我们可以看到,里面记录了上次登录时间、 Cookie 的保存时间等信息
关闭浏览器,然后再次访问,可以发现仍然保留着上次登录的时间
7.3 Cookie 常用方法
-
从请求中拿到 Cookie 信息
-
服务器端给客户端响应 Cookie
Cookie[] cookies = req.getCookies(); //获取 Cookie 数组
cookie.getName(); //获取 Cookie 中的 Key
cookie.getValue(); //获取 Cookie 中的 Value
new Cookie(); //新建一个 Cookie
cookie.setMaxAge(); //设置 Cookie 的有效期
resp.addCookie(); //响应给客户端一个 Cookie
7.4 Session(重点)
什么是 Session:
- 服务器会给每个用户创建一个 Session 对象
- 一个 Session 独占一个浏览器,只要浏览器没有关闭,这个 Session 就存在
- 登陆之后,整个网站的内容都可以访问
为什么浏览器一打开就会创建一个 Session 呢?
创建测试类 SessionDemo01
,注册路径为 /sd1
package com.jiuxiao.servlet;
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;
/**
* Session 测试
*
* @author WuDaoJiuXiao
* @Date 2022/4/15 14:07
* @since 1.0.0
*/
public class SessionDemo01 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=utf-8");
HttpSession session = req.getSession();
session.setAttribute("username", "荒天帝");
String sessionId = session.getId();
if (session.isNew()) {
resp.getWriter().write("Session 创建成功, ID 为 " + sessionId);
} else {
resp.getWriter().write("Session 已经在服务器中, ID 为 " + sessionId);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
<!--SessionDemo01-->
<servlet>
<servlet-name>SessionDemo01</servlet-name>
<servlet-class>com.jiuxiao.servlet.SessionDemo01</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SessionDemo01</servlet-name>
<url-pattern>/sd1</url-pattern>
</servlet-mapping>
启动项目,发现 Session 已经存在
前边说过,只要浏览器一打开,Session 就已经创建成功,直至关闭浏览器,那么,系统到底进行了什么操作?
在浏览器中查看响应,我们给 Cookie 中并没有传参数,但是这里默认会有一个名为 JSESSIONID
的参数,而且该键对应的值刚好是我们所得到的 SessionId,两者之间是否有联系?
我们很容易得到结论:在系统创建一个 Session 时,应该是帮我们做了以下的事情
// 创建了一个名为 JSESSIONID 的 Cookie
Cookie cookie = new Cookie("JSESSIONID", sessionId);
// 将创建的 Cookie 添加到响应体中
resp.addCookie(cookie);
我们发现,Session 对象设置的参数可以是一个对象,那么,设置一个对象之后,我们能否再通过 session 取到这个对象的值呢?
创建一个测试对象 Student
类
package com.jiuxiao.pojo;
/**
* Student 测试类
*
* @author WuDaoJiuXiao
* @Date 2022/4/15 14:48
* @since 1.0.0
*/
public class Student {
private String name;
private int age;
private String city;
public Student(String name, int age, String city) {
this.name = name;
this.age = age;
this.city = city;
}
public Student() {
}
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 String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", city='" + city + '\'' +
'}';
}
}
将上面的 SessionDemo01
中的 setAttribute()
方法的入参由原来的字符串修改为 Student 对象
session.setAttribute("student", new Student("荒天帝", 500, "ShangHai"));
创建获取 Session 对象信息的测试类 GetSessionInfo
,注册路径为 /gsi
package com.jiuxiao.servlet;
import com.jiuxiao.pojo.Student;
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;
/**
* 获取 Session 信息
*
* @author WuDaoJiuXiao
* @Date 2022/4/15 14:44
* @since 1.0.0
*/
public class GetSessionInfo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=utf-8");
HttpSession session = req.getSession();
Student student = (Student) session.getAttribute("student");
resp.getWriter().write(student.toString());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
<!--GetSessionInfo-->
<servlet>
<servlet-name>GetSessionInfo</servlet-name>
<servlet-class>com.jiuxiao.servlet.GetSessionInfo</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>GetSessionInfo</servlet-name>
<url-pattern>/gsi</url-pattern>
</servlet-mapping>
启动项目后,先访问 /sd1
将 Student 对象设置到 Session 中去,然后再访问 /gsi
获取对象信息,成功获取
我们在 SessionDemo01
中向 Session 中添加了 Student 对象,而获取信息是在另外一个 Servlet 服务中获取的,这就实现了跨 Servlet
的资源共享
以前我们共享资源要使用 6.6.1 的 ServletContext 对象获取(不推荐),现在又可以使用 Session 进行获取(推荐)
使用了 Session 之后,怎么进行注销呢?
新建测试类 CloseSession
,注册路径为 /cs
package com.jiuxiao.servlet;
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;
/**
* 注销 Session
*
* @author WuDaoJiuXiao
* @Date 2022/4/15 15:08
* @since 1.0.0
*/
public class CloseSession extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=utf-8");
HttpSession session = req.getSession();
//取消添加到 Session 的信息
session.removeAttribute("student");
//注销 Session
session.invalidate();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
<!--CloseSession-->
<servlet>
<servlet-name>CloseSession</servlet-name>
<servlet-class>com.jiuxiao.servlet.CloseSession</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CloseSession</servlet-name>
<url-pattern>/cs</url-pattern>
</servlet-mapping>
我们先访问 /sd1
设置 Session 信息,然后访问 /cs
注销 Session,再次访问 /gsi
想要获取对象信息,发现 Session 已经被注销,报了个空指针异常
这里我们注销 Session 采用的是手动注销方式,那么能否自动注销呢?当然可以,在 web.xml
中配置一下 Session 失效时间即可
<!--自动注销 Session-->
<session-config>
<!--以分钟为单位-->
<session-timeout>1440</session-timeout>
</session-config>
Session 使用场景
保存一个用户的登录信息
购物车信息
在整个网站中经常要进行访问的数据,将其保存在 Session 中
7.5 Session 与 Cookie 区别
-
Cookie 是把用户的数据写给用户的浏览器,浏览器进行保存(可以保存多个)
-
Session 对象由服务器创建,将用户的数据写入到用户独占的 Session 中,服务器进行保存(保存重要信息,减少服务器资源消耗)
8 JSP
8.1 什么是 JSP
JSP 全称为 Java Servlet Pages:即 Java 服务器端页面,也和 Servlet 一样,用于动态 Web 技术
- HTML 页面中只能为用户提供静态的数据
- JSP 中可以嵌入 Java 代码,为用户提供动态数据
8.2 JSP 原理
JSP 看起来与 HTML 差不多,但是他执行原理是什么?凭什么可以支持动态数据响应?
我们在 IDEA中启动 Tomcat 服务,在控制台的输出信息中找到 Using CATALINA_BASE
一项,后面的那个路径就是我们项目的 JSP 文件编译后的路径
复制后边的路径,打开电脑的资源管理器,地址栏粘贴后打开,可以看到有一个 work 目录,这就是项目文件的保存地址
点击 work 目录,依次打开路径 work\Catalina\localhost\ROOT\org\apache\jsp\
我们神奇的发现,里面存放的居然是项目中 index.jsp
的 java
文件和 java
程序编译后才会有的 class
文件!
问题:所以,为什么呢?JSP 文件和 Java 的类到底有什么关联?值得深究
我们打开 index_jsp.java
文件,发现他继承了 HttpJspBase
父类,为了探究 JSP
原理,我们需要研究其父类
我们需要在 IDEA 中手动导入父类的 jar 包
-
依次点击【File】→【Project Structure】,在弹出的界面依次点击【Libraries】→【+】→【Java】
-
然后在 Tomcat 的安装目录下的【lib】目录中,依次找到 先找到名为
jasper
的 jar 包,然后选择它 org 目录下的runtime
包,点击【OK】,导入到当前项目中
将父类 jar 包导入之后,点开父类的源码,惊奇的发现, HttpJspBase
继承的父类,就是 HttpServlet
!
到此为止,一切疑问得到了解决,项目编译之后,JSP 会转化为一个 Servlet 对象!这也就是为什么 index.jsp
会转换为 java
文件!
浏览器向服务器发送请求,不管访问的是什么资源,其本质都是在访问 Servlet!
JSP 说到底只是一种动态 Web 的技术,所以最终还是会被转换为一个 Java 类!
接下来,我们一步步分析
index_jsp.java
的源码,看看他都做了什么
我们发现,它里面有三个方法 Init()、destory()、service()
,分别对应着初始化、销毁和服务
看起来有种似曾相识的感觉,这方法不就是 HttpServlet
的最基本方法吗?而且 _jspService()
方法的两个参数,不就是 6.2.3 中,我们耳熟能详的 HttpServletRequest
和 HttpServletResponse
!
下面我们分析一下
Service()
方法都干了些什么事情
刚开始对请求进行了一些判断
接下来初始化了一些内置对象
final javax.servlet.jsp.PageContext pageContext; // 页面上下文
javax.servlet.http.HttpSession session = null; // Session 对象
final javax.servlet.ServletContext application; // ApplicationContext
final javax.servlet.ServletConfig config; // Config 配置类
javax.servlet.jsp.JspWriter out = null; // out 输出流
final java.lang.Object page = this; // page = this 代表当前页
final javax.servlet.http.HttpServletRequest request; // 请求
final javax.servlet.http.HttpServletResponse response; // 响应
然后,在输出页面之前,又设置了一些参数
response.setContentType("text/html"); // 设置页面响应类型
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;
分析至此,我们明白了 JSP 的工作原理,其本质就是一个 Servlet 对象,只不过替我们简化了一些渲染页面的方法
在 JSP 页面中,只要是 Java 代码,都会原封不动的输出
如果是 HTML 代码,会被转换为 out.write("<h2>Hello</h2>")
形式输出到前端
8.3 JSP 基本语法
首先,将 JSP 相关的依赖导入
<dependencies>
<!--Servlet 依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<!--JSP 依赖-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<!--JSTL 表达式依赖-->
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl-api</artifactId>
<version>1.2</version>
</dependency>
<!--standard 标签依赖-->
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
</dependencies>
JSP 作为一种 Java 技术的应用,它支持 Java 的所有语法,除此之外还有一些自己所独有的语法
8.3.1 JSP 表达式
<%-- JSP表达式 : 一般用来输出结果到客户端 --%>
<%= new java.util.Date()%>
8.3.2 JSP 脚本片段
<%-- JSP脚本片段 --%>
<%
int sum = 1;
for (int i = 0; i < 10; i++) {
sum += i;
out.print("</h2>" + sum + "</h2> ");
}
%>
8.3.3 脚本嵌入 HTML 元素
<%--脚本嵌入 HTML 元素--%>
<%
for (int i = 0; i < 5; i++) {
Date obj = new Date();
Thread.sleep(1000);
%>
<h2>
Hello World Date : <%= obj.toLocaleString()%>
</h2>
<%
}
%>
8.3.4 JSP 声明
<%!
static {
System.out.println("Loading Servlet");
}
private int var = 0;
public void test(){
System.out.println("执行方法 test()...");
}
%>
JSP 声明会被编译到 JSP 生成的 Java 的类中!其他的会被生成到 _jspService()
方法中
JSP 的注释不会在客户端显示,HTML 的会在客户端显示
8.4 JSP 指令(了解)
JSP 指令是指:程序出错跳转自定义页面、导入 Java 包等指令
<%-- 自定义错误页面 : 一般统一在 web.xml 中配置--%>
<%@ page errorPage="../error/500.jsp"%>
<%-- 导入 Java 包 --%>
<%@ page import="java.util.Date" %>
<%-- 导入网页公共页面: 头部、尾部 --%>
<%@ include file="common/header.jsp"%> <%--导入方式1--%>
<%-- JSP 标签 --%>
<jsp:include page="500.jsp"/> <%--导入方式2,区别在于路径前多一个斜杠--%>
创建一个自定义错误页面 my-error.jsp
,然后在测试页面中故意制造一个错误
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<img src="../image/500.jpg">
</body>
</html>
在测试页面故意制造一个错误
<%@ page errorPage="error/my-error.jsp" %> <%-- 指令导入自定义页面方式 --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<%
int num = 1/0;
%>
</body>
</html>
启动项目后,进入了我们的自定义错误页面
一般情况下,不用在 jsp 页面中导入错误页面的设置,可以统一在 web.xml
中进行配置
<error-page>
<error-code>404</error-code>
<location>/error/404.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error/500.jsp</location>
</error-page>
8.5 九大内置对象
- 有存储信息功能的对象
pageContext
: 保存的数据只在一个页面中有效reques
t : 保存的数据再一次请求中有效,请求转发也会携带该参数session
: 保存的数据在一次会话中有效,从浏览器打开到关闭application
: 即ServletContext
保存的数据在服务器中有效,从服务器打开到关闭
- 其他对象
config
:即ServletConfig
out
page
:几乎不用exception
8.6 JSP、JSTL、EL
8.6.1 JSP 标签
我们在页面 test.jsp
中将请求转发到 header.jsp
页面,并且在转发时携带参数
<%-- 转发到特定页面 --%>
<jsp:forward page="/header.jsp">
<jsp:param name="name" value="Jack"/>
<jsp:param name="age" value="18"/>
</jsp:forward>
然后在 header.jsp
中去获取参数
<h2>页面header</h2>
姓名: <%=request.getParameter("name")%>
年龄: <%=request.getParameter("age")%>
启动项目,访问 test.jsp
页面,使用 JSP 标签形式完成了转发和传参
8.6.2 JSTL 表达式
JSTL 标签库的使用就是为了弥补 HTML 标签的不足,他可以自定义许多标签,标签的功能与 Java 代码功能一样
JSTL 标签库大致分为以下四种:核心标签、格式化标签、SQL 标签、XML 标签
JSTL 标签库的导入 <%@ taglib prefix="" uri=""%>
核心库常用标签
在 Tomcat 中也需要引入 JSTL 的标签库的 jar 包,否则会报错
<c:if>
标签
<form action="tag.jsp" method="get">
用户名:<input type="text" name="username" value="${param.username}">
密码:<input type="password" name="password" value="${param.password}">
<input type="submit">
</form>
<%--其中 ${} 形式就是 EL 表达式--%>
<c:if test="${param.username == 'admin'}" var="isAdmin">
<c:out value="欢迎管理员!"/>
</c:if>
<c:out value="${isAdmin}"/>
<c:when>、<c:choose>
标签
<c:set var="score" value="88"/>
<c:choose>
<c:when test="${score >= 80}">
<c:out value="优秀"/>
</c:when>
<c:when test="${score >= 60}">
<c:out value="一般"/>
</c:when>
<c:when test="${score < 60}">
<c:out value="不及格"/>
</c:when>
</c:choose>
<c:foreach>
标签
<%
List<String> people = new ArrayList<>();
people.add("张三");
people.add("李四");
people.add("王五");
request.setAttribute("list", people);
%>
%--不设置步长和起始位置--%>
<c:forEach var="people" items="${list}">
<c:out value="${people}"/>
</c:forEach>
<%
List<String> people = new ArrayList<>();
people.add(0, "张三");
people.add(1, "李四");
people.add(2, "王五");
people.add(3, "赵六");
people.add(4, "孙七");
request.setAttribute("list", people);
%>
<%--设置步长和起始位置--%>
<c:forEach var="people" items="${list}" begin="1" end="4" step="2">
<c:out value="${people}"/>
</c:forEach>
8.7 JavaBean
8.7.1 JavaBean 写法
-
必须要有一个无参构造
-
属性必须私有化
-
必须要有对应的
get/set
方法 -
一般用来做数据库的字段映射
8.7.2 JSP 获取 Bean 信息
新建一个 Student
类,属性有 id、name、age、addreee
package com.jiuxiao.pojo;
/**
* 学生类
*
* @author WuDaoJiuXiao
* @Date 2022/4/18 10:32
* @since 1.0.0
*/
public class Student {
private int id;
private String name;
private int age;
private String address;
public Student() {
}
public Student(int id, String name, int age, String address) {
this.id = id;
this.name = name;
this.age = age;
this.address = address;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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 String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
然后使用 JSP 获取实体类信息
<jsp:useBean id="student" class="com.jiuxiao.pojo.Student" scope="page"/>
<jsp:setProperty name="student" property="id" value="1"/>
<jsp:setProperty name="student" property="name" value="荒天帝"/>
<jsp:setProperty name="student" property="age" value="18"/>
<jsp:setProperty name="student" property="address" value="荒域"/>
id:<jsp:getProperty name="student" property="id"/>
姓名:<jsp:getProperty name="student" property="name"/>
年龄:<jsp:getProperty name="student" property="age"/>
地址:<jsp:getProperty name="student" property="address"/>
9 MVC 三层架构
9.1 什么是 MVC
MVC 分别为 Model、View、Controller
的简写,分别代表模型、视图、控制器
在该架构中,Servlet 应该专注于处理请求、响应、视图跳转,JSP 专注于显示数据
9.1.1 早期架构(两层)
在早期的架构中,还没有第三层的 Model 层,在此架构中,用户可以直接访问控制层,控制层直接访问及操作数据库,这样做的话,会让程序十分臃肿,不利于维护
在此种架构模式中,Servelt 需要处理请求、响应、视图跳转、JDBC 链接、处理业务代码、处理逻辑代码等等,会让程序异常臃肿
9.1.2 MVC 架构(三层)
Model 层
-
业务处理:业务逻辑(Service)
-
数据持久层:CURD(Dao)
View 层
-
展示数据
-
提供链接,发起 Servlet 请求
Controller 层
-
接受用户请求
-
交给业务层处理
-
控制视图的跳转
10 Filter 和 Listener
10.1 过滤器 Filter
Filter:过滤器,顾名思义,有着过滤网站的数据的作用
实现一个字符编码过滤器的 demo
- 创建一个 Maven 管理的 Web 项目,导入依赖
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl-api</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
</dependencies>
- 我们新建一个 Servlet 服务,响应信息里如果有中文,那么为了页面显示不乱码,我们应该设置它的编码格式
package com.jiuxiao.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 页面信息显示
*
* @author WuDaoJiuXiao
* @Date 2022/4/18 20:54
* @since 1.0.0
*/
public class ShowServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().print("你好,世界!");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
测试类的注册路径如下,为了明显区别,我们注册两个路径,分别对比使用过滤器和不使用过滤器
<servlet>
<servlet-name>showServlet</servlet-name>
<servlet-class>com.jiuxiao.servlet.ShowServlet</servlet-class>
</servlet>
<!--走过滤器-->
<servlet-mapping>
<servlet-name>showServlet</servlet-name>
<url-pattern>/servlet/show</url-pattern>
</servlet-mapping>
<!--不走过滤器-->
<servlet-mapping>
<servlet-name>showServlet</servlet-name>
<url-pattern>/show</url-pattern>
</servlet-mapping>
那么问题来了,如果有成千上万个 Servelt 服务呢?难道一个一个设置编码格式?显然不太现实,这个时候,过滤器就出场了
- 继承 Filter 接口,重写方法
package com.jiuxiao.filter;
import javax.servlet.*; // 此处的 Filter 类属于 javax.servlet.Filter
import java.io.IOException;
/**
* 字符编码过滤器
*
* @author WuDaoJiuXiao
* @Date 2022/4/18 20:44
* @since 1.0.0
*/
public class CharacterEncodingFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("过滤器初始化");
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("UTF-8");
servletResponse.setCharacterEncoding("UTF-8");
servletResponse.setContentType("text/html;charset=uft-8");
System.out.println("过滤器执行之前...");
// 只有执行了该句,请求和响应才会被放行,否则会被拦截
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("过滤器执行之后...");
}
public void destroy() {
System.out.println("过滤器销毁");
}
}
将过滤器进行注册,将 /servlet
之下的所有请求都进行过滤
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>com.jiuxiao.filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<!-- 该配置表示 /servlet 之下的所有请求都要走过滤器 CharacterEncodingFilter -->
<url-pattern>/servlet/*</url-pattern>
</filter-mapping>
启动项目,我们发现,在服务器开始启动时,过滤器也会一并初始化完成,因为它要随时监听请求与响应
我们先访问不使用过滤器的路径 /show
,字符果然是乱码
在访问使用了过滤器的路径 /servlet/show
,经过过滤器,字符编码正常
查看后台,可以看到走完了过滤器,字符编码问题也被解决
然后我们停止项目,发现当服务器停止时,过滤器才会被销毁
10.2 监听器 Listener
实现一个监听在线人数的监听器
创建监听器测试类 OnlineCountListener
,并在 web.xml
中进行注册
package com.jiuxiao.listener;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* 在线人数监听
*
* @author WuDaoJiuXiao
* @Date 2022/4/19 9:57
* @since 1.0.0
*/
public class OnlineCountListener implements HttpSessionListener {
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
ServletContext servletContext = httpSessionEvent.getSession().getServletContext();
Integer onlineCount = (Integer) servletContext.getAttribute("onlineCount");
if (onlineCount == null) onlineCount = 1;
else onlineCount += 1;
servletContext.setAttribute("onlineCount", onlineCount);
}
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
ServletContext servletContext = httpSessionEvent.getSession().getServletContext();
Integer onlineCount = (Integer) servletContext.getAttribute("onlineCount");
if (onlineCount == null) onlineCount = 0;
else onlineCount -= 1;
servletContext.setAttribute("onlineCount", onlineCount);
httpSessionEvent.getSession().invalidate(); // 销毁监听器
}
}
<listener>
<listener-class>com.jiuxiao.listener.OnlineCountListener</listener-class>
</listener>
然后去前端页面取值
<h2>当前在线人数共
<span style="color: firebrick">
<%=this.getServletConfig().getServletContext().getAttribute("onlineCount")%>
</span>人
</h2>
启动项目,发现刚开始显示的为 3 人,这是 Tomcat 启动导致,重新发布项目,用两个浏览器模拟两个用户,人数显示正常
10.3 应用场景
1. 监听器应用:在 GUI 中常用,Web 中几乎不用
2. 过滤器应用
实现用户登录功能,只有登录成功才能访问主页,否则不能访问
- 用户登录之后,将信息放入 Session
- 进入主页是要判断用户是否已经登录,过滤器中实现
用户登录类 LoginServlet
,注册路径为 /servlet/login
package com.jiuxiao.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 登录
*
* @author WuDaoJiuXiao
* @Date 2022/4/19 14:31
* @since 1.0.0
*/
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
if (username.equals("admin")) {
req.getSession().setAttribute("USER_SESSION", req.getSession().getId());
resp.sendRedirect("/sys/success.jsp");
} else {
resp.sendRedirect("/sys/error.jsp");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
注销类 LogoutServlet
,注册路径位 /servlet/logout
package com.jiuxiao.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 注销登录
*
* @author WuDaoJiuXiao
* @Date 2022/4/19 14:41
* @since 1.0.0
*/
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Object user_session = req.getSession().getAttribute("USER_SESSION");
if (user_session != null) {
req.getSession().removeAttribute("USER_SESSION");
resp.sendRedirect("/login.jsp");
} else {
resp.sendRedirect("/login.jsp");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
登录权限过滤器 SysFilter
,在 web.xml
注册
package com.jiuxiao.filter;
import javax.print.attribute.standard.NumberUp;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 登录权限过滤器
*
* @author WuDaoJiuXiao
* @Date 2022/4/19 14:53
* @since 1.0.0
*/
public class SysFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
if (req.getSession().getAttribute("USER_SESSION") == null) {
resp.sendRedirect("/sys/error.jsp");
}
filterChain.doFilter(servletRequest, servletResponse);
}
public void destroy() {
}
}
11 JDBC
11.1 什么是 JDBC
JDBC:是一个统一的驱动接口,通过该接口可以实现不同数据库的链接,只针对 JDBC 接口编写程序,不必针对每个数据库编写
11.2 JDBC 链接数据库
- 首先,建立一个简单的数据库
people
,创建一张测试表student
CREATE TABLE `student` (
`id` int(4) PRIMARY KEY,
`name` VARCHAR(20),
`age` int(8),
`password` VARCHAR(20),
`email` VARCHAR(40),
`tel` VARCHAR(20)
);
INSERT INTO `student`(`id`, `name`, `age`, `password`, `email`, `tel`) VALUES
(1, '孙二', 18, '123456', 'se@qq.com', '8339564'),
(2, '张三', 24, '465456564', 'zs@qq.com', '8334654'),
(3, '李四', 35, 'sdfds45', 'ls@qq.com', '8356485'),
(4, '王五', 12, '453544', 'ww@qq.com', '8953456'),
(5, '赵六', 10, '4534533', 'zl@qq.com', '8775412');
创建好之后 student
表如下所示
- 项目中导入数据库相关依赖
<dependencies>
<!--Mysql驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
</dependencies>
- 在 IDEA 中链接数据库发生时区错误的问题,在链接后边加上
?serverTimezone=GMT%2B8
即可解决问题
- 测试查询,成功输出查询结果
package com.jiuxiao.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
/**
* 数据库连接测试
*
* @author WuDaoJiuXiao
* @Date 2022/4/20 10:42
* @since 1.0.0
*/
public class JdbcTest {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://localhost:3306/people?useUnicode=true&characterEncoding=utf-8";
String username = "root";
String password = "0531";
//1.加载驱动
//mysql-connector-java 如果为 6.0 之上,需要用 com.mysql.cj.jdbc.Driver,之下为 com.mysql.jdbc.Driver
Class.forName("com.mysql.cj.jdbc.Driver");
//2.建立连接
Connection connection = DriverManager.getConnection(url, username, password);
//3.向数据库发送 SQL 的对象 statement
Statement statement = connection.createStatement();
//4.执行语句
String sql = "select * from student where age > 16";
ResultSet resultSet = statement.executeQuery(sql);
//5.输出结果
while (resultSet.next()) {
System.out.println("id : " + resultSet.getObject("id") + "\t"
+ "name : " + resultSet.getObject("name") + "\t"
+ "age : " + resultSet.getObject("age") + "\t"
+ "password : " + resultSet.getObject("password") + "\t"
+ "email : " + resultSet.getObject("email") + "\t"
+ "tel : " + resultSet.getObject("tel") + "\t"
);
}
//6.关闭连接,释放资源(一定要做)
resultSet.close();
statement.close();
connection.close();
}
}
11.3 预编译
package com.jiuxiao.jdbc;
import com.sun.corba.se.impl.resolver.SplitLocalResolverImpl;
import java.sql.*;
/**
* 插入数据
*
* @author WuDaoJiuXiao
* @Date 2022/4/20 11:24
* @since 1.0.0
*/
public class JdbcTest02 {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://localhost:3306/people?useUnicode=true&characterEncoding=utf-8";
String username = "root";
String password = "0531";
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, username, password);
String sql = "insert into student(id, name, age, password, email, tel) value (?,?,?,?,?,?)";
PreparedStatement prepareStatement = connection.prepareStatement(sql);
prepareStatement.setInt(1, 6);
prepareStatement.setString(2, "荒天帝");
prepareStatement.setInt(3, 500);
prepareStatement.setString(4, "112233");
prepareStatement.setString(5, "htd@qq.com");
prepareStatement.setString(6, "8415252");
int i = prepareStatement.executeUpdate();
if (i > 0) System.out.println("插入成功");
prepareStatement.close();
connection.close();
}
}
11.4 事务
事务的 ACID 性质:原子性、一致性、隔离性、持久性
junit 单元测试
在一个项目中测试方法时,可以引入 junit 包,直接使用注解 @Test
进行测试运行,该注解只能用在方法上
<!--junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
public class JdbcTest03 {
@Test
public void test() {
System.out.println("Hello World");
}
}
使用事务,模拟三个人转账
新建一个测试数据库
CREATE TABLE account(
`id` INT PRIMARY KEY,
`name` VARCHAR(30),
`money` FLOAT
);
INSERT INTO account(`id`, `name`, `money`) VALUES
(1, 'A', 1000),
(2, 'B', 1000),
(3, 'C', 1000);
下面使用代码模拟以下事务
start transaction ;
update account set money = money - 100 where name = 'A';
update account set money = money + 100 where name = 'B';
commit ;
若事务执行出错,需要执行回滚操作,保持数据库的一致性
package com.jiuxiao.jdbc;
import org.junit.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
/**
* 事务
*
* @author WuDaoJiuXiao
* @Date 2022/4/20 14:02
* @since 1.0.0
*/
public class JdbcTest03 {
@Test
public void accountChange() {
String url = "jdbc:mysql://localhost:3306/people?useUnicode=true&characterEncoding=utf-8";
String username = "toot";
String pwd = "0531";
String sql01 = "update account set money = money - 100 where name = 'A'";
String sql02 = "update account set money = money + 100 where name = 'B'";
Connection connection = null;
Statement statement = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
connection = DriverManager.getConnection(url, username, pwd);
connection.setAutoCommit(false); // 通知数据库开启事务
statement = connection.createStatement();
statement.executeUpdate(sql01);
statement.executeUpdate(sql02);
//int num = 1/0; //制造错误
connection.commit();
System.out.println("提交成功");
} catch (Exception e) {
try {
assert connection != null;
connection.rollback();
} catch (SQLException se) {
se.printStackTrace();
}
} finally {
try {
assert statement != null;
statement.close();
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}