Servlet 简介
动态资源和静态资源
静态资源:
- 无需在程序运行时(在服务端运行)通过代码运行生成的资源,在程序运行之前就写好的资源。例如:html、css、js、img、音频文件和视频文件等
动态资源:
- 需要在程序运行时通过代码运行生成的资源,在程序运行之前无法确定的数据,运行时动态生成,例如:Servlet、Thymeleaf 等
- 动态资源指的不是视图上的动画效果或者是简单的人机交互效果
生活举例:
去蛋糕店买蛋糕
- 直接买柜台上已经做好的:静态资源
- 和柜员说要求后现场制作:动态资源
Servlet 简介
Servlet(server applet)是运行在服务端(如 Tomcat)的 Java 小程序,是 SUN 公司提供一套定义动态资源规范;从代码层面上来讲 Servlet 就是一个接口
- 用来接收、处理客户端请求、响应给浏览器的动态资源。在整个 Web 应用中,Servlet 主要负责接收处理请求、协同调度功能以及响应数据。我们可以把 Servlet 称为 Web 应用中的控制器
- 不是所有的 Java 类都能用于处理客户端请求,能处理客户端请求并做出响应的一套技术标准就是 Servlet
- Servlet 是运行在服务端的,所以 Servlet 必须在 Web 项目中开发且在 Tomcat 这样的服务容器中运行
请求响应与 HttpServletRequest 和 HttpServletResponse 之间的对应关系:
Servlet 开发流程
目标
校验注册时,用户名是否被占用。通过客户端向一个 Servlet 发送请求,携带 username
,如果用户名是 'atguigu'
,则向客户端响应 NO,否则响应 YES
开发过程
-
步骤 1:创建一个 Web 类型的 module
-
步骤2:开发一个
UserServlet
public class UserServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求中的参数
String username = req.getParameter("username");
if ("atguigu".equals(username)) {
// 通过响应对象响应信息
resp.getWriter().write("NO");
} else {
resp.getWriter().write("YES");
}
}
}
- 自定义一个类,要继承
HttpServlet
类 - 重写
service
方法,该方法主要就是用于处理用户请求的服务方法 HttpServletRequest
代表请求对象,是由请求报文经过 Tomcat 转换而来的,通过该对象可以获取请求中的信息HttpServletResponse
代表响应对象,该对象会被 Tomcat 转换为响应的报文,通过该对象可以设置响应中的信息- Servlet 对象的生命周期(创建、初始化、处理服务、销毁)是由 Tomcat 管理的,无需我们自己
new
HttpServletRequest
、HttpServletResponse
两个对象也是由 Tomcat 负责转换,在调用service
方法时传入给我们用的
- 步骤 3:在
web.xml
为UseServlet
配置请求的映射路径
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<servlet>
<!-- 给U serServlet 起一个别名 -->
<servlet-name>userServlet</servlet-name>
<servlet-class>com.atguigu.servlet.UserServlet</servlet-class>
</servlet>
<servlet-mapping>
<!-- 关联别名和映射路径 -->
<servlet-name>userServlet</servlet-name>
<!-- 可以为一个 Servlet 匹配多个不同的映射路径,
但是不同的 Servlet 不能使用相同的 url-pattern -->
<url-pattern>/userServlet</url-pattern>
<!-- <url-pattern>/userServlet2</url-pattern> -->
<!--
/ 表示通配所有资源,不包括 jsp 文件
/* 表示通配所有资源,包括 jsp 文件
/a/* 匹配所有以 a 前缀的映射路径
*.action 匹配所有以 action 为后缀的映射路径
-->
<!-- <url-pattern>/*</url-pattern> -->
</servlet-mapping>
</web-app>
- Servlet 并不是文件系统中实际存在的文件或者目录,所以为了能够请求到该资源,我们需要为其配置映射路径
- Servlet 的请求映射路径配置在
web.xml
中 servlet-name
作为 Servlet 的别名,可以自己随意定义,见名知意就好url-pattern
标签用于定义 Servlet 的请求映射路径- 一个 Servlet 可以对应多个不同的
url-pattern
- 但多个 Servlet 不能使用相同的
url-pattern
url-pattern
中可以使用一些通配写法/
表示通配所有资源,不包括 jsp 文件/*
表示通配所有资源,包括 jsp 文件/a/*
匹配所有以a
前缀的映射路径*.action
匹配所有以.action
为后缀的映射路径
- 步骤 4:开发一个 form 表单,向 Servlet 发送一个
get
请求并携带username
参数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="userServlet">
请输入用户名:<input type="text" name="username" /> <br>
<input type="submit" value="校验">
</form>
</body>
</html>
- 步骤 5:启动项目,访问
index.html
,提交表单测试
使用 debug 模式运行测试:
映射关系图:
Servlet 注解方式配置
@WebServlet
注解源码
官方 JavaEE API 文档下载地址:Java EE - Technologies (oracle.com)
@WebServlet
注解的源码阅读:
package jakarta.servlet.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @since Servlet 3.0
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
/**
* The name of the servlet
* 相当于 servlet-name
* @return the name of the servlet
*/
String name() default "";
/**
* The URL patterns of the servlet
* 如果只配置一个 url-pattern,则通过该属性即可,和 urlPatterns 属性互斥
* @return the URL patterns of the servlet
*/
String[] value() default {};
/**
* The URL patterns of the servlet
* 如果要配置多个 url-pattern,需要通过该属性,和 value 属性互斥
* @return the URL patterns of the servlet
*/
String[] urlPatterns() default {};
/**
* The load-on-startup order of the servlet
* 配置 Servlet 是否在项目加载时实例化
* @return the load-on-startup order of the servlet
*/
int loadOnStartup() default -1;
/**
* The init parameters of the servlet
* 配置初始化参数
* @return the init parameters of the servlet
*/
WebInitParam[] initParams() default {};
/**
* Declares whether the servlet supports asynchronous operation mode.
*
* @return {@code true} if the servlet supports asynchronous operation mode
* @see jakarta.servlet.ServletRequest#startAsync
* @see jakarta.servlet.ServletRequest#startAsync( jakarta.servlet.ServletRequest,jakarta.servlet.ServletResponse)
*/
boolean asyncSupported() default false;
/**
* The small-icon of the servlet
*
* @return the small-icon of the servlet
*/
String smallIcon() default "";
/**
* The large-icon of the servlet
*
* @return the large-icon of the servlet
*/
String largeIcon() default "";
/**
* The description of the servlet
*
* @return the description of the servlet
*/
String description() default "";
/**
* The display name of the servlet
*
* @return the display name of the servlet
*/
String displayName() default "";
}
@WebServlet
注解使用
使用 @WebServlet
注解替换 Servlet 配置
@WebServlet(
name = "userServlet",
// value = "/user",
urlPatterns = {"/userServlet1", "/userServlet2", "/userServlet"},
initParams = {@WebInitParam(name = "encoding", value = "UTF-8")},
loadOnStartup = 6
)
public class UserServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String encoding = getServletConfig().getInitParameter("encoding");
System.out.println(encoding);
// 获取请求中的参数
String username = req.getParameter("username");
if ("atguigu".equals(username)) {
// 通过响应对象响应信息
resp.getWriter().write("NO");
} else {
resp.getWriter().write("YES");
}
}
}
Servlet 生命周期
生命周期简介
什么是 Servlet 的生命周期:
- 应用程序中的对象不仅在空间上有层次结构的关系,在时间上也会因为处于程序运行过程中的不同阶段而表现出不同状态和不同行为——这就是对象的生命周期。
- 简单的叙述生命周期,就是对象在容器中从开始创建到销毁的过程。
Servlet 容器:
Servlet 对象是 Servlet 容器创建的,生命周期方法都是由容器(目前我们使用的是 Tomcat)调用的。这一点和我们之前所编写的代码有很大不同。在今后的学习中我们会看到,越来越多的对象交给容器或框架来创建,越来越多的方法由容器或框架来调用,开发人员要尽可能多的将精力放在业务逻辑的实现上。
Servlet 主要的生命周期执行特点:
生命周期 | 对应方法 | 执行时机 | 执行次数 |
---|---|---|---|
构造对象 | 构造器 | 第一次请求或者容器启动 | 1 |
初始化 | init() | 构造完毕后 | 1 |
处理服务 | service(HttpServletRequest req, HttpServletResponse resp) | 每次请求 | 多次 |
销毁 | destory() | 容器关闭 | 1 |
生命周期测试
package com.atguigu.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ServletLifeCycle extends HttpServlet {
public ServletLifeCycle() {
System.out.println("构造器");
}
@Override
public void init() throws ServletException {
System.out.println("初始化方法");
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("service 方法");
}
@Override
public void destroy() {
System.out.println("销毁方法");
}
}
<servlet>
<servlet-name>servletLifeCycle</servlet-name>
<servlet-class>com.atguigu.servlet.ServletLifeCycle</servlet-class>
<!-- load-on-startup
如果配置的是正整数则表示容器在启动时就要实例化 Servlet,
数字表示的是实例化的顺序
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>servletLifeCycle</servlet-name>
<url-pattern>/servletLiftCycle</url-pattern>
</servlet-mapping>
生命周期总结
- 通过生命周期测试我们发现 Servlet 对象在容器中是单例的
- 容器是可以处理并发的用户请求的,每个请求在容器中都会开启一个线程
- 多个线程可能会使用相同的 Servlet 对象,所以在 Servlet 中,我们不要轻易定义一些容易经常发生修改的成员变量
load-on-startup
中定义的正整数表示实例化顺序,如果数字重复了,容器会自行解决实例化顺序问题,但是应该避免重复- Tomcat 容器中,已经定义了一些随系统启动实例化的 Servlet,我们自定义的 Servlet 的
load-on-startup
尽量不要占用数字 1-5
Servlet 继承结构
Servlet
接口
Servlet 规范接口,所有的 Servlet 必须实现
public void init(ServletConfig config) throws ServletException;
- 初始化方法,容器在构造 Servlet 对象后,自动调用的方法,容器负责实例化一个
ServletConfig
对象,并在调用该方法时传入 ServletConfig
对象可以为 Servlet 提供初始化参数
- 初始化方法,容器在构造 Servlet 对象后,自动调用的方法,容器负责实例化一个
public ServletConfig getServletConfig();
- 获取
ServletConfig
对象的方法,后续可以通过该对象获取 Servlet 初始化参数
- 获取
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
- 处理请求并做出响应的服务方法,每次请求产生时由容器调用
- 容器创建一个
ServletRequest
对象和ServletResponse
对象,容器在调用service
方法时,传入这两个对象
public String getServletInfo();
- 获取 Servlet 信息的方法
public void destroy();
- Servlet 实例在销毁之前调用的方法
GenericServlet
抽象类
GenericServlet
抽象类是对 Servlet 接口一些固定功能的粗糙实现,以及对 service
方法的再次抽象声明,并定义了一些其他相关功能方法
private transient ServletConfig config;
- 初始化配置对象作为属性
public GenericServlet() {}
- 构造器,为了满足继承而准备
public void destroy() {}
- 销毁方法的平庸实现
public String getInitParameter(String name)
- 获取初始参数的快捷方法
public Enumeration<String> getInitParameterNames()
- 返回所有初始化参数名的方法
public ServletConfig getServletConfig()
- 获取初始 Servlet 初始配置对象
ServletConfig
的方法
- 获取初始 Servlet 初始配置对象
public ServletContext getServletContext()
- 获取上下文对象
ServletContext
的方法
- 获取上下文对象
public String getServletInfo()
- 获取 Servlet 信息的平庸实现
public void init(ServletConfig config) throws ServletException()
- 初始化方法的实现,并在此调用了
init
的重载方法
- 初始化方法的实现,并在此调用了
public void init() throws ServletException
- 重载
init
方法,为了让我们自己定义初始化功能的方法
- 重载
public void log(String msg)
public void log(String message, Throwable t)
- 打印日志的方法及重载
public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
- 服务方法再次声明
public String getServletName()
- 获取
ServletName
的方法
- 获取
HttpServlet
抽象类
abstract class HttpServlet extends GenericServlet HttpServlet
:抽象类,除了基本的实现以外,增加了更多的基础功能
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
private static final String METHOD_OPTIONS = "OPTIONS";
private static final String METHOD_POST = "POST";
private static final String METHOD_PUT = "PUT";
private static final String METHOD_TRACE = "TRACE";
- 上述属性用于定义常见请求方式名常量值
public HttpServlet() {}
- 构造器,用于处理继承
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
- 对服务方法的实现
- 在该方法中,将请求和响应对象转换成对应 HTTP 协议的
HttpServletRequest
、HttpServletResponse
对象 - 调用重载的
service
方法
public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
- 重载的
service
方法,被重写的service
方法所调用 - 在该方法中,通过请求方式判断,调用具体的
doXxx
方法完成请求的处理
- 重载的
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- 对应不同请求方式的处理方法
- 除了
doOptions
和doTrace
方法,其他的doXxx
方法都在故意响应错误信息
自定义 Servlet
继承关系图解:
自定义 Servlet 中,必须要对处理请求的方法进行重写
- 要么重写
service
方法 - 要么重写
doGet
/doPost
方法
ServletConfig
和 ServletContext
ServletConfig
的使用
ServletConfig
是什么:
- 为 Servlet 提供初始配置参数的一种对象,每个 Servlet 都有自己独立唯一的
ServletConfig
对象 - 容器会为每个 Servlet 实例化一个
ServletConfig
对象,并通过 Servlet 生命周期的init
方法传入给 Servlet 作为属性
ServletConfig
是一个接口,定义了如下 API:
package jakarta.servlet;
import java.util.Enumeration;
public interface ServletConfig {
String getServletName();
ServletContext getServletContext();
String getInitParameter(String var1);
Enumeration<String> getInitParameterNames();
}
方法名 | 作用 |
---|---|
getServletName() | 获取 <servlet-name>HelloServlet</servlet-name> 定义的 Servlet 名称 |
getServletContext() | 获取 ServletContext 对象 |
getInitParameter() | 获取配置 Servlet 时设置的『初始化参数』,根据名字获取值 |
getInitParameterNames() | 获取所有初始化参数名组成的 Enumeration 对象 |
ServletConfig
怎么用,测试代码如下:
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletConfig servletConfig = this.getServletConfig();
// 根据参数名获取单个参数
String value = servletConfig.getInitParameter("param1");
System.out.println("param1: " + value);
// 获取所有参数名
Enumeration<String> parameterNames = servletConfig.getInitParameterNames();
// 迭代并获取参数名
while (parameterNames.hasMoreElements()) {
String paramaterName = parameterNames.nextElement();
System.out.println(paramaterName + ":" + servletConfig.getInitParameter(paramaterName));
}
}
}
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletConfig servletConfig = this.getServletConfig();
// 根据参数名获取单个参数
String value = servletConfig.getInitParameter("param1");
System.out.println("param1: " + value);
// 获取所有参数名
Enumeration<String> parameterNames = servletConfig.getInitParameterNames();
// 迭代并获取参数名
while (parameterNames.hasMoreElements()) {
String paramaterName = parameterNames.nextElement();
System.out.println(paramaterName + ":" + servletConfig.getInitParameter(paramaterName));
}
}
}
<servlet>
<servlet-name>ServletA</servlet-name>
<servlet-class>com.atguigu.servlet.ServletA</servlet-class>
<!-- 配置 ServletA 的初始参数 -->
<init-param>
<param-name>param1</param-name>
<param-value>value1</param-value>
</init-param>
<init-param>
<param-name>param2</param-name>
<param-value>value2</param-value>
</init-param>
</servlet>
<servlet>
<servlet-name>ServletB</servlet-name>
<servlet-class>com.atguigu.servlet.ServletB</servlet-class>
<!-- 配置 ServletB 的初始参数 -->
<init-param>
<param-name>param3</param-name>
<param-value>value3</param-value>
</init-param>
<init-param>
<param-name>param4</param-name>
<param-value>value4</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>ServletA</servlet-name>
<url-pattern>/servletA</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>ServletB</servlet-name>
<url-pattern>/servletB</url-pattern>
</servlet-mapping>
ServletContext
的使用
ServletContext
是什么:
ServletContext
对象又称呼为上下文对象,或者叫应用域对象(后面统一讲解域对象)- 容器会为每个 app 创建一个独立唯一的
ServletContext
对象 ServletContext
对象为所有的 Servlet 所共享ServletContext
可以为所有的 Servlet 提供初始配置参数
ServletContext
怎么用,测试代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<context-param>
<param-name>paramA</param-name>
<param-value>valueA</param-value>
</context-param>
<context-param>
<param-name>paramB</param-name>
<param-value>valueB</param-value>
</context-param>
</web-app>
package com.atguigu.servlet;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();
String valueA = servletContext.getInitParameter("paramA");
System.out.println("paramA:" + valueA);
// 获取所有参数名
Enumeration<String> initParameterNames = servletContext.getInitParameterNames();
// 迭代并获取参数名
while (initParameterNames.hasMoreElements()) {
String paramaterName = initParameterNames.nextElement();
System.out.println(paramaterName + ":" + servletContext.getInitParameter(paramaterName));
}
}
}
ServletContext
其他重要 API
获取资源的真实路径:
String realPath = servletContext.getRealPath("资源在 Web 目录中的路径");
例如我们的目标是需要获取项目中某个静态资源的路径,不是工程目录中的路径,而是部署目录中的路径;我们如果直接拷贝其在我们电脑中的完整路径的话其实是有问题的,因为如果该项目以后部署到公司服务器上的话,路径肯定是会发生改变的,所以我们需要使用代码动态获取资源的真实路径。
只要使用了 servletContext
动态获取资源的真实路径,那么无论项目的部署路径发生什么变化,都会动态获取项目运行时候的实际路径,所以就不会发生由于写死真实路径而导致项目部署位置改变引发的路径错误问题。
获取项目的上下文路径:
String contextPath = servletContext.getContextPath();
项目的部署名称,也叫项目的上下文路径,在部署进入 Tomcat 时所使用的路径,该路径是可能发生变化的,通过该 API 动态获取项目真实的上下文路径,可以帮助我们解决一些后端页面渲染技术或者请求转发和响应重定向中的路径问题
域对象的相关 API:
- 域对象:一些用于存储数据和传递数据的对象,传递数据不同的范围,我们称之为不同的域,不同的域对象代表不同的域,共享数据的范围也不同
ServletContext
代表应用,所以ServletContext
域也叫作应用域,是 webapp 中最大的域,可以在本应用内实现数据的共享和传递- webapp 中的三大域对象:分别是应用域,会话域,请求域
- 后续我们会将三大域对象统一进行讲解和演示,三大域对象都具有的 API 如下
API | 功能解释 |
---|---|
void setAttribute(String key,Object value); | 向域中存储/修改数据 |
Object getAttribute(String key); | 获得域中的数据 |
void removeAttribute(String key); | 移除域中的数据 |
HttpServletRequest
HttpServletRequest
简介
HttpServletRequest
是一个接口,其父接口是ServletRequest
HttpServletRequest
是 Tomcat 将请求报文转换封装而来的对象,在 Tomcat 调用service
方法时传入HttpServletRequest
代表客户端发来的请求,所有请求中的信息都可以通过该对象获得
HttpServletRequest
常见 API
获取请求行信息相关(方式、请求的 URL、协议及版本等):
API | 功能解释 |
---|---|
StringBuffer getRequestURL(); | 获取客户端请求的 URL |
String getRequestURI(); | 获取客户端请求项目中的具体资源 |
int getServerPort(); | 获取客户端发送请求时的端口 |
int getLocalPort(); | 获取本应用在所在容器的端口 |
int getRemotePort(); | 获取客户端程序的端口 |
String getScheme(); | 获取请求协议 |
String getProtocol(); | 获取请求协议及版本号 |
String getMethod(); | 获取请求方式 |
获得请求头信息相关:
API | 功能解释 |
---|---|
String getHeader(String headerName); | 根据头名称获取请求头 |
Enumeration<String> getHeaderNames(); | 获取所有的请求头名字 |
String getContentType(); | 获取 content-type 请求头 |
获得请求参数相关:
API | 功能解释 |
---|---|
String getParameter(String parameterName); | 根据请求参数名获取请求单个参数值 |
String[] getParameterValues(String parameterName); | 根据请求参数名获取请求多个参数值数组 |
Enumeration<String> getParameterNames(); | 获取所有请求参数名 |
Map<String, String[]> getParameterMap(); | 获取所有请求参数的键值对集合 |
BufferedReader getReader() throws IOException; | 获取读取请求体的字符输入流 |
ServletInputStream getInputStream() throws IOException; | 获取读取请求体的字节输入流 |
int getContentLength(); | 获得请求体长度的字节数 |
其他 API:
API | 功能解释 |
---|---|
String getServletPath(); | 获取请求的 Servlet 的映射路径 |
ServletContext getServletContext(); | 获取 ServletContext 对象 |
Cookie[] getCookies(); | 获取请求中的所有 Cookie |
HttpSession getSession(); | 获取 Session 对象 |
void setCharacterEncoding(String encoding); | 设置请求体字符集 |
HttpServletResponse
HttpServletResponse
简介
HttpServletResponse
是一个接口,其父接口是ServletResponse
HttpServletResponse
是 Tomcat 预先创建的,在 Tomcat 调用service
方法时传入HttpServletResponse
代表对客户端的响应,该对象会被转换成响应的报文发送给客户端,通过该对象我们可以设置响应信息
HttpServletResponse
的常见 API
设置响应行相关:
API | 功能解释 |
---|---|
void setStatus(int code); | 设置响应状态码 |
设置响应头相关:
API | 功能解释 |
---|---|
void setHeader(String headerName, String headerValue); | 设置/修改响应头键值对 |
void setContentType(String contentType); | 设置 content-type 响应头及响应字符集(设置 MIME 类型) |
设置响应体相关:
API | 功能解释 |
---|---|
PrintWriter getWriter() throws IOException; | 获得向响应体放入信息的字符输出流 |
ServletOutputStream getOutputStream() throws IOException; | 获得向响应体放入信息的字节输出流 |
void setContentLength(int length); | 设置响应体的字节长度,其实就是在设置 content-length 响应头 |
其他 API:
API | 功能解释 |
---|---|
void sendError(int code, String message) throws IOException; | 向客户端响应错误信息的方法,需要指定响应码和响应信息 |
void addCookie(Cookie cookie); | 向响应体中增加 Cookie |
void setCharacterEncoding(String encoding); | 设置响应体字符集 |
MIME 类型:
- MIME 类型,可以理解为文档类型,用户表示传递的数据是属于什么类型的文档
- 浏览器可以根据 MIME 类型决定该用什么样的方式解析接收到的响应体数据
- 可以这样理解:前后端交互数据时,告诉对方发给对方的是 html、css、js、图片、声音、视频…
tomcat/conf/web.xml
中配置了常见文件的拓展名和 MIME 类型的对应关系- 常见的 MIME 类型举例如下
文件拓展名 | MIME 类型 |
---|---|
.html | text/html |
.css | text/css |
.js | application/javascript |
.png / .jpeg / .jpg | image/png 、image/jpeg |
.mp3 / .mpe / .mpeg | audio/mpeg |
.mp4 | video/mp4 |
.m1v / .m2v / .mpe | video/mpeg |
请求转发和响应重定向
概述
什么是请求转发和响应重定向:
- 请求转发和响应重定向是 Web 应用中间接访问项目资源的两种手段,也是 Servlet 控制页面跳转的两种手段
- 请求转发通过
HttpServletRequest
实现,响应重定向通过HttpServletResponse
实现 - 请求转发生活举例:张三找李四借钱,李四没有,李四找王五,让王五借给张三
- 响应重定向生活举例:张三找李四借钱,李四没有,李四让张三去找王五,张三自己再去找王五借钱
请求转发
请求转发运行逻辑图:
请求转发特点(背诵):
- 请求转发通过
HttpServletRequest
对象获取请求转发器实现 - 请求转发是服务器内部的行为,对客户端是不可见的
- 客户端只发送了一次请求,客户端地址栏不变
- 服务端只产生了一对请求和响应对象,这一对请求和响应对象会继续传递给下一个资源
- 因为全程只有一个
HttpServletRequset
对象,所以请求参数可以传递,请求域中的数据也可以传递 - 请求转发可以转发给其他 Servlet 动态资源,也可以转发给一些静态资源以实现页面跳转
- 请求转发可以转发给
WEB-INF
下受保护的资源 - 请求转发不能转发到本项目以外的外部资源
请求转发测试代码:
@WebServlet("/servletA")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求转发器
// 转发给 servlet ok
RequestDispatcher requestDispatcher = req.getRequestDispatcher("servletB");
// 转发给一个视图资源 ok
// RequestDispatcher requestDispatcher = req.getRequestDispatcher("welcome.html");
// 转发给 WEB-INF 下的资源 ok
// RequestDispatcher requestDispatcher = req.getRequestDispatcher("WEB-INF/views/view1.html");
// 转发给外部资源 no
// RequestDispatcher requestDispatcher = req.getRequestDispatcher("http://www.atguigu.com");
// 获取请求参数
String username = req.getParameter("username");
System.out.println(username);
// 向请求域中添加数据
req.setAttribute("reqKey", "requestMessage");
// 做出转发动作
requestDispatcher.forward(req, resp);
}
}
@WebServlet("/servletB")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求参数
String username = req.getParameter("username");
System.out.println(username);
// 获取请求域中的数据
String reqMessage = (String) req.getAttribute("reqKey");
System.out.println(reqMessage);
// 做出响应
resp.getWriter().write("servletB response");
}
}
响应重定向
响应重定向运行逻辑图:
响应重定向特点(背诵):
- 响应重定向通过
HttpServletResponse
对象的sendRedirect
方法实现 - 响应重定向是服务端通过 302 响应码和路径,告诉客户端自己去找其他资源,是在服务端提示下的客户端的行为
- 客户端至少发送了两次请求,客户端地址栏是要变化的
- 服务端产生了多对请求和响应对象,且请求和响应对象不会传递给下一个资源
- 因为全程产生了多个
HttpServletRequset
对象,所以请求参数不可以传递,请求域中的数据也不可以传递 - 重定向可以是其他 Servlet 动态资源,也可以是一些静态资源以实现页面跳转
- 重定向不可以到给
WEB-INF
下受保护的资源 - 重定向可以到本项目以外的外部资源
响应重定向测试代码:
@WebServlet("/servletA")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求参数
String username = req.getParameter("username");
System.out.println(username);
// 向请求域中添加数据
req.setAttribute("reqKey", "requestMessage");
// 响应重定向
// 重定向到 servlet 动态资源 ok
resp.sendRedirect("servletB");
// 重定向到视图静态资源 ok
// resp.sendRedirect("welcome.html");
// 重定向到 WEB-INF 下的资源 no
// resp.sendRedirect("WEB-INF/views/view1");
// 重定向到外部资源 ok
// resp.sendRedirect("http://www.atguigu.com");
}
}
@WebServlet("/servletB")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求参数
String username = req.getParameter("username");
System.out.println(username);
// 获取请求域中的数据
String reqMessage = (String) req.getAttribute("reqKey"); // 获取不到
System.out.println(reqMessage);
// 做出响应
resp.getWriter().write("servletB response");
}
}
Web 乱码和路径问题总结
乱码问题
乱码问题产生的根本原因是什么:
- 数据的编码和解码使用的不是同一个字符集
- 使用了不支持某个语言文字的字符集
各个字符集的兼容性:
- 由上图得知,上述字符集都兼容了 ASCII
- ASCII 中有英文字母和一些通常使用的符号,所以这些东西无论使用什么字符集都不会乱码
HTML 乱码问题
设置项目文件的字符集要使用一个支持中文的字符集(推荐 UTF-8)
查看当前文件的字符集:
查看项目字符集配置,将 Global Encoding(全局字符集)、Project Encoding(项目字符集)、 Properties Files(属性配置文件字符集)都设置为 UTF-8
当前视图文件的字符集通过 <meta charset="UTF-8">
来告知浏览器通过什么字符集来解析当前文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
中文
</body>
</html>
Tomcat 控制台乱码
修改 tomcat/conf/logging.properties
:
修改 IDEA 控制台编码为 UTF-8,修改 cmd DOS 命令行编码为 UTF-8
sout
乱码问题,设置 JVM 加载 .class
文件和编译时使用的字符集都使用 UTF-8 字符集
请求乱码问题
GET 请求方式乱码分析:
- GET 方式提交参数的方式是将参数放到 URL 后面,如果使用的不是 UTF-8,那么会对参数进行 URL 编码处理
- HTML 中的
<meta charset='字符集'/>
影响了 GET 方式提交参数的 URL 编码 - Tomcat 的 URI 编码默认为 UTF-8
- 当 GET 方式提交的参数 URL 编码和 Tomcat 默认的 URI 编码不一致时,就会出现乱码
POST 请求方式乱码分析:
- POST 请求将参数放在请求体中进行发送
- 请求体使用的字符集受到了
<meta charset="字符集"/>
的影响 - Tomcat 默认使用 UTF-8 字符集对请求体进行解析
- 如果请求体的 URL 转码和 Tomcat 的请求体解析编码不一致,就容易出现乱码
GET 请求方式乱码演示:
- 浏览器解析文档的
<meta charset="GBK" />
- GET 方式提交时,会对数据进行 URL 编码处理,是将 GBK 转码为“百分号码”
- Tomcat 默认使用 UTF-8 对 URI 进行解析,造成前后端使用的字符集不一致,出现乱码
GET 请求方式乱码解决:
- 方式 1:设置 GET 方式提交的编码和 Tomcat 的 URI 默认解析编码一致即可(推荐)
- 方式 2:设置 Tomcat 的 URI 解析字符集和 GET 请求发送时所使用 URL 转码时的字符集一致即可,修改
conf/server.xml
中Connecter
添加URIEncoding="GBK"
(不推荐)
POST 同理。
响应乱码问题
响应乱码分析:
- 在 Tomcat 中,向响应体中放入的数据默认使用了工程编码 UTF-8
- 浏览器在接收响应信息时,使用了不同的字符集或者是不支持中文的字符集就会出现乱码
响应乱码演示:
响应乱码解决:
- 方式 1:手动设定浏览器对本次响应体解析时使用的字符集(不推荐)
- 方式 2:后端通过设置响应体的字符集和浏览器解析响应体的默认字符集一致(不推荐)
- 方式 3:通过设置
content-type
响应头,告诉浏览器以指定的字符集解析响应体(推荐)
路径问题
相对路径和绝对路径:
- 相对路径
- 相对路径的规则是:以当前资源所在的路径为出发点去寻找目标资源
- 相对路径不以
/
开头 - 在 file 协议下,使用的是磁盘路径
- 在 http 协议下,使用的是 URL 路径
- 相对路径中可以使用
./
表示当前资源所在路径,可以省略不写 - 相对路径中可以使用
../
表示当前资源所在路径的上一层路径,需要时要手动添加
- 绝对路径
- 绝对路径的规则是:使用以一个固定的路径做出出发点去寻找目标资源,和当前资源所在的路径没有关系
- 绝对路径要以
/
开头 - 绝对路径的写法中,不以当前资源的所在路径为出发点,所以不会出现
./
和../
- 不同的项目和不同的协议下,绝对路径的基础位置可能不同,要通过测试确定
- 绝对路径的好处就是:无论当前资源位置在哪,寻找目标资源路径的写法都一致
- 应用场景
- 前端代码中,
href
、src
、action
等属性 - 请求转发和重定向中的路径
- 前端代码中,
- 前端中的路径问题
- 请求转发中的路径问题
- 响应重定向中的路径问题
MVC 架构模式
MVC(Model、View、Controller)是软件工程中的一种软件架构模式,它把软件系统分为模型、视图和控制器三个基本部分。用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
- Model(模型层)
- 存放和数据库对象的实体类以及一些用于存储非数据库表完整相关的
VO
对象 - 存放一些对数据进行逻辑运算操作的的一些业务处理代码
- 存放和数据库对象的实体类以及一些用于存储非数据库表完整相关的
- View(视图层)
- 存放一些视图文件相关的代码:html、css、js 等
- 在前后端分离的项目中,后端已经没有视图文件,该层次已经衍化成独立的前端项目
- Controller(控制层)
- 接收客户端请求,获得请求数据
- 将准备好的数据响应给客户端
MVC 模式下,项目中的常见包:
- M
- 实体类包(
pojo
、entity
、bean
)专门存放和数据库对应的实体类和一些VO
对象 - 数据库访问包(
dao
、mapper
)专门存放对数据库不同表格 CURD 方法封装的一些类 - 服务包(
service
)专门存放对数据进行业务逻辑运算的一些类
- 实体类包(
- C
- 控制层包(
controller
)
- 控制层包(
- V
- Web 目录下的视图资源:html、css、js、img 等
- 前端工程化后,在后端项目中已经不存在了
非前后端分离的 MVC:
前后端分离的 MVC: