什么是 Servlet
是基于 java 的服务端小程序,用来跟客户端交互提供交互的接口等。 Servlet 是 JavaWeb 中的基础,也是 SpringMVC 的基础
Servlet 中的继承关系
javax(jakarta).servlet.ServletName (最原始的 Servlet 接口) javax(jakarta).servlet.GenericServlet implements Servlet (实现自 Servlet 的抽象类) javax(jakarta).servlet.HttpServlet extends GenericServlet (继承自 GenericServlet 的抽象类)
GenericServlet (适配器模式)
GenericServlet 继承自 javax(jakarta).servlet.Servlet ,已经默认实现了 Servlet 接口中许多的方法,在编写 自己的 Servlet 程序时可以继承自 GenericServlet ,只需要完成 service 方法即可
ServletConfig 接口
- 谁实现了 ServletConfig 接口
- web 容器
- 每个 web 容器实现自己的 ServletConfig 接口(Tomcat 实现了自己的 ServletConfig,Jetty 实现了自己的 ServletConfig)
- 不同的 web 容器实现的 ServletConfig 有所不同,(包名,类名 ...) 但是他们一定都符合 ServletConfig 的规范
- 和 Servlet 对象一样,在用户访问的时候创建
-
如何获取 servletConfig 对象
- this.getServletConfig()
-
如何在 servlet 对象创建的时候,注入参数
- 在 web.xml 中 中加入
- 在 中使用 指定参数的名字
- 在 中使用 指定参数的值
<servlet>
<servlet-name></servlet-name>
<servlet-class></servlet-class>
<init-param>
<param-name>name</param-name>
<pram-value>value</pram-value>
</init-param>
</servlet>
- 如何在 Servlet 程序中获取注入的参数
-
先获取 ServletConfig 对象 ServletConfig config = this.getServletConfig;
-
再获取指定的参数(如 db) String db = config.getParameter("db");
-
通过 config.getInitParameterNames() 可以获取全部配置的
- Servlet 对象跟 ServletConfig 对象是一对一的关系,就是说每一个 Servlet 对象都有自己对应的 ServletConfig 对象
ServletConfig 对象的4个方法
- public String getInitParameter(String name);
- public Enumeration getInitParameterNames();
- public String getServletName();
- public ServletContext getServletContext();
以上的4个方法在继承了 GenericServlet 的类中都可以通过 this 调用
ServletContext
- 如何获取 ServletContext 对象
- 通过 ServletConfig 对象获取 ServletContext 对象:ServletContext application = config.getServletContext();
- 在 GenericServlet 的继承类中:ServletContext application = this.getServletContext();
- ServletContext 还有一个名字: 应用域 存放在这里的数据应该是:数据量较小,冬拥湖共享的,修改较少的或者不修改的,占用内存较小的
- ServletContext 接口是什么
- 也是 Servlet 规范中重要的一员,是有 web 容器实现的
- ServletContext 在什么时候实例化,谁来负责初始化 ServletContext 对象
- web 容器在启动的时候创建的,关闭的时候销毁
- 对于一个 web App 项目来说,ServletContext 对象只有一个
- 怎么理解 ServletContext 对象
- Servlet 的上下文对象(环境对象)
- ServletContext 对象中容纳了全部的 Servlet 对象, 从源码来看,ServletContext 中有一个 servlets 属性,之中存放了所有创建的 Servlet 对象
- 某些东西如果想要让全部的 Servlet 对象共享,可以把它放在 ServletContext 对象中
- 在 web.xml 中配置上下文的初始化参数
- 在 web.xml 中加入
- 在 中使用 和 代表参数和值
<context-param>
<context-name>name</context-name>
<ocntext-value>value</ocntext-value>
</context-param>
- 从配置的层面来看,ServletContext 的配置主要是针对全局的配置
- 在每个 Servlet 中的 配置属于当前 Servlet 的自己的配置
- 获取以上配置的 context-param 的信息 (同 ServletConfig 类似)
- public Enumeration getInitParameterNames(): 获取全部上下文配置参数名
- public String getInitParameter(String name): 获取指定上下文配置的参数的参数值
- 常用的方法
-
String getContextPath(): 获取应用上下文的根
-
String getInitParameter(String name): 获取指定名称的配置参数值
-
Enumeration getInitParameterNames(): 获取所有的配置参数名称
-
String getRealPath(String path): 获取文件的绝对路径 (参数 path 是相对于项目的根目录开始,不管什么时候都推荐使用 / 开头)
-
void log(String message): 记录日志,日子将会记录到 Tomcat 安装目录下 logs 中
-
void log(String message, Throwable t): 记录日志,并且做出异常提醒
- 使用 IDEA 工具的在开发的时候,注意日志的存放路径并不是 Tomcat\logs
- Tomcat\logs 中都有那些日志文件
- catalina.2022-11-05.log // 当日java服务器运行的日志文件
- localhost.2022-11-05.log // Servlet 对象的 log 日志
- localhost_access_log.2022-11-02.txt // 访问日志
-
void setAttribute(String name, Object value): 往 ServletContext 中存放数据
-
Object getAttribute(String name): 获取数据
-
void removeAttribute(String name); 删除数据
缓存机制都有哪些
- 堆内存中的字符串常量池
- 创建字符串的时候先从字符串常量池中查找有没有存在的常量,有则直接使用,没有就创建
- 堆内存中的整数型常量池
- 同字符串常量池,范围是 [-128, 127] (包括 -128,127)
- 连接池
- java 语言连接数据库的连接对象 (Connection 对象)
- JVM 是一个进程,MySQL也是一个进程,进程之间的打开通道是很耗费资源的,怎么办?可以提前创建好多个 Connection 对象放到 线程池中,当每一用户连接的时候直接使用,
- 常见的连接池类型(最大连接池,最小连接池),可以提高用户的访问效率,也可以保证数据库的安全性
- 线程池
- Tomcat 本身是支持多线程的
- Tomcat 在服务器器启动的时候,会先创建多个线程 Thread 对象,然后将线程对象放到集合中,称为线程池,在用户请求到来的时候,需要一个对应的线程来处理 这个时候,线程对象就会直接从线程池中拿出来。
- 所以所有的 web 服务器或者应用服务器都是有线程池的。
- redis
如何配置欢迎页 (在用户直接访问的项目的根路径的时候,默认重定向到欢迎页)
在 web.xml 中使用 配置 使用 路径 作为一个欢迎页的配置,可以配置多个,优先级从上往下越来越低。 也就是说先找最顶上的,找不到再依次向下
<!-- 注意:页面的路径不需要使用 "/" 开始,并且这个路径默认是从 webapp 的根目录下开始 -->
<welcome-file-list>
<welcome-file>welcome.html</welcome-file>
</welcome-file-list>
如果在 webapp 的根目录下
- Tomcat 安装的根目录下的 /con/web.xml 这个文件可以对存在于该服务器中的全部项目做统一配置,
- 在我们访问一个目录的时候,会默认去访问这个目录中的 index.html 文件,这个过程的原理就是 Tomcat 等大多数服务器都在这个全体的配置的 web.xml 中将 index.html,index.htm,index,jsp 配置成了欢迎页
- 当然项目中配置的优先级肯定是高于全局配置的(就近原则)
- 当然欢迎页也可以是一个 Servlet ,只要是服务器中存在的资源(静态资源或者动态资源)就可以
使用注解的方式注册 Servlet
- 一般而言,我们在 Web.xml 中通过 Servlet 标签来注册 一个 Servlet
- 在一个大型的系统中 Servlet 对象是很多的,每一个都需要在 Web.xml 中去注册,就会导致 Web.xml 文件非常的庞大,后期维护非常的困难
- 使用注解的形式去注册 Servlet,将该 Servlet 的配置在当前 Servlet 类的文件中,需要修改哪个 Servlet 类的配置就去该类的文件中去修改
- 使用 @WebServlet(key = value) 注解完成 Servlet 的注册
- name(String): 指定 Servlet 的名字
- urlPatterns(String[]): 指定 Servlet 的请求路径,可以配置多个所以是一个数组,当数组中只有一个元素的时候,可以写成 key=value,省略 {}
- initParams(@WebInitParam(name="属性名", value="属性值")[]): 指定 Servlet 初始化时候的参数
- loadOnStartup(int): 指定服务器启动的时候是否初始化 Servlet 对象,和初始化的优先级
- ... 在 Web.xml 中 Servlet 的配置都有对应的注解
- 什么时候使用 Web.xml 做配置,什么时候使用注解做配置
- 一些公共的配置可以放在 Web.xml 中
- 每个 Servlet 中自己的配置(请求路径,name ...)使用注解
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebInitParam;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(
name = "HttpServletTest",
urlPatterns = {"/getTest"},
initParams = {@WebInitParam(name="user", value="root"), @WebInitParam()},
loadOnStartup = 1
)
class HttpServletTest extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
Servlet 的生命周期
-
什么是 Servlet 的生命周期 就是一个 Servlet 对象从创建到销毁的过程
-
Servlet 对象的生命周期是由 Tomcat 来维护的,程序员是没有办法去干预的
-
我们自己 new 的 Servlet 对象并不会被 Tomcat 管理,所以也不会具有 Servlet 的生命周期
- Tomcat 创建的 Servlet 对象会添加到一个集合中,在这个集合中的所有 Servlet 对象会被 Tomcat 管理
- 自己 new 的 Servlet 对象不会添加到这个集合中,也就不会被 Tomcat 管理
-
Tomcat 服务器启动的时候并不会创建出具体的 Servlet 对象,只有客户端访问到才会创建 Servlet 对象
-
想要在 Tomcat 启动的时候就创建 Servlet 对象,可以在 web.xml 中添加配置
<servlet>
<load-an-startup>number</load-a-startup>
</servlet>
/**
number 不是创建的 Servlet 对象个数,而是创建 Servlet 对象的优先级
如果两个 Servlet 对象同时配置了 load-an-startup,Tomcat 会根据配置的优先级去生成 Servlet 对象
优先级搞得会先被创建。
**/
- Servlet 对象的生命周期
- 用户访问指定的路由时,服务器会根据 web.xml 中配置的类名 去 new 对应的 Servlet 对象
- Servlet 对象的 init 方法执行
- Servlet 对象的 service 方法执行
- 用户第二次请求的时候,Servlet 对象的 service 第二次执行
- 用户第二次请求的时候,Servlet 对象的 service 第三次执行
- ...
- 当服务器关闭的时候,所创建的 Servlet 对象调用 destroy 方法,调用结束之后进入等待销毁内存地址的状态
说明
- init 和 destroy 方法在一个生命周期中只会执行一次
- Servlet 是单例的,多次请求的时候都是调用的最初创建的那个 Servlet 对象,但是 Servlet 对象是假单例
- 某个 Servlet 对象在请求结束之后并不会销毁,在用户请求的时候一直被重复的使用,直到服务器关闭的时候才统一调用 destroy 方法,调用完毕之后进入等待销毁内存地址的状态
思考:在 Servlet 对象调用 destroy 方法的时候销毁了吗? 答:没有,这个 Servlet 对象能调用自身的某个非 static 的方法,说明这个对象是还存在的。在 destroy 方法调用结束之后,Servlet 对象才会进入等待销毁的状态,销毁的过程由 Tomcat 负责。
Session
- 什么是 session 对象
- 浏览器的一次连接中,会在服务器中生成一个 sesion 对象,代表着当前这一次的访问,也就是从访问服务器到最后关闭网页的一个过程
- 在浏览器中的多个窗口都访问服务器,服务器中 session 对象都是同一个,也可以理解为用户连接标识
- 一个 session 对象对应一次会话,一个会话中存在多次请求
- session 什么时候销毁
- session 对象使用超时机制,当用户在一定的时间馁没有发送请求,session 就会销毁
- 如何在 Servlet 中获取 session 对象(HttpServletRequest 对象的 getSession() 方法)
- HttpSession session = req.getSession(): 获取 session 对象,获取不到就新建 Session
- HttpSession session = req.getSession(false): 获取 session 对象,获取不到就返回 null
- Session 由服务器负责生成,管理,销毁
- session 对象在服务器中的存储形式一般是以 Map 形式存储,其中的 key 可以理解为 Session 对象的 sessionId,value 就是具体的 Session 对象
- Session 对象什么时候销毁
- 当用户关闭了浏览器的时候,服务器中对应的 Session 对象就销毁了吗
- 服务器一般不会知道浏览器什么时候关闭,所以不能根据判断浏览器的关闭就销毁 Session 对象
- Session 对象的销毁一般有以下的两种方式
- 超时关闭:当浏览器一段时间内没有请求服务器的时候,服务器就关闭 Session 对象
- 手动关闭:服务器手动销毁 Session 对象
例如:给用户提供一个退出按钮,当用户点击这个按钮的时候,向服务器发送退出的请求,服务器手动销毁 Session 对象
Session 机制的原理
- 浏览器第一次访问服务器的时候,服务器中还没有生存 Session 对象
- 服务器向浏览器发送一个标识(sessionId),浏览器保存这个标识
- 当浏览器继续请求服务器的时候,每次请求都会带上这个标识
- 服务器再次收到浏览器的请求时,根据携带的 Session 标识,判断跟该浏览器对应的 Session 对象
注:
- 一般在浏览器中,使用 Cookie 来记录 Session 标识
- 当用户的浏览器禁止了 Cookie,Session 机制还可以实现吗
- 可以在请求的路径中携带 Session 标识h或者在请求体中携带,例:www.baidu.com?sessionId=12545645;
- 这就要重写 Session 的机制,导致开发的成本变高
- 现在的网站一般是,当用户禁止了 Cookie,就不能正常使用该网站了
在 Web.xml 中配置 Session
- 在 web.xml 中可以使用 标签来对 Session 做一些配置
- : 配置 Session 超时销毁的时间(单位:分钟)
- : 对记录在浏览器中的 cookie 的配置
- : cookie 的名字
- : 对应 cookie 中 domian,web 容器一般已经做了配置,不需要自己配置
- : 对应 cookie 中 path,web 容器一般已经做了配置,不需要自己配置
- : cookie中添加Comment特性,在其中可以添加任意文本。这通常用于解释cookie的目的,并告诉用户网站的隐私策略。
<session-config>
<!-- 配置 Session 超时销毁的时间 -->
<session-timeout>30</session-timeout>
<cookie-config>
<name>JSESSIONID</name>
<domain>example.org</domain>
<path>/shop</path>
<comment>
<!-- some information -->
</comment>
<http-only>true</http-only>
<secure>false</secure>
<max-age>1800</max-age>
</cookie-config>
</session-config>
请求和响应的时候中文乱码怎么解决
- post
- Tomcat 9包括9之前如果前端 post 请求体中有中文,就会出现乱码的情况,这时候要调用将字符集设置文UTF-8)
- 在 Tomcat 10之后采用了UTF-8的字符集,所以不需要调用
- 在 Tomcat 9之前包括9,响应的内容中如果又中文的话也会乱码,此时需要设置这个来解决乱码多的问题
- 在 Tomcat 10之后,响应中文的时候不会出现乱码
解决
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 Server extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=UTF-8");
}
}
- get 修改 Tomcat 安装目录下的 /conf/server.xml , 加上 URIEncoding="UTF-8" 从 Tomcat 8之后,这里的默认 URIEncoding 就是 UTF-8,不需要修改
<Connector
port="8080"
protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
URIEncoding="UTF-8" />
HttpServlet (专门为 Http 协议适配的专门的 Servlet,继承自 GenericServlet)
HttpServlet 类只专门为了 Http 请求准备的。这个类位于 javax(jakarta).servlet.http.HttpServlet 包下
到目前位置,我们接触的 Servlet 接口都有那些
- Servlet // 核心接口
- ServletConfig // Servlet 配置信息接口
- ServletContext // Servlet 上下文接口
- GenericServlet // 标准通用的 Servlet 接口
- ServletRequest // Servlet 请求接口
- ServletResponse // Servlet 响应接口
- ServletException// Servlet 异常接口
- HttpServlet // Http Servlet 接口
http 包下有哪些接口 (javax(jakarta).servlet.http)
-
HttpServlet Http: Servlet 抽象类
-
HttpServletRequest: Http 请求专用对象
- 将请求协议中的数据全部解析出来吗,然后封装到了这个对象中
- 从这个对象中可以获取到 HTTP 请求的所有内容
- HttpServletResponse: Http 响应专用对象
- 负责响应信息到浏览器
- 具体响应什么,以什么形式响应,
- 如何做基于 HttpServlet 的 Servlet 程序开发
- 在 HttpServlet 中,service 方法一般不需要我们来重写,HttpServlet 已经重写了 service 方法,并将 service 方法作为一个骨架,伪代码如下
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
public class HttpServlet extends GenericServlet {
public void service(ServletRequest req, ServletResponse resp) {
String method = req.getMethod(); // 获取请求的方式
if (method == "GET") {
doGet();
} else if (method == "POST") {
doPost();
} else if (method == "PUT") {
doPut();
} else if ...
.....
}
}
也就是说 HttpServlet 中的 service 方法会根据请求的方式去调用 doGet,doPost,...
-
所以在实际的开发过程中,我们会根据这个 Servlet 具体是采用什么请求的方法来决定重写 doGet 还是 dePost 方法,而不是 service 方法
-
当这个 Servlet 采用了 get 的请求方法,但是前端却采用 post 请求的方式访问(也就是说决定的请求方式跟实际的访问方式不一样),根据 HttpServlet 中的 service 方法,就会调用到子类的 doPost 方法,但是子类的 doPost 没有重写 (决定了使用 get 的请求方法,所以重写的是 goGet,没有重写 doPost),就会调用到 HttpServlet(父类)的 doPost 方法,就会报出 405 访问方式不正确的异常
-
在 HttpServlet 的开发中,不建议同时重写 doGet 和 doPost 方法,而是根据具体的请求方式重写对应的方法
-
完成 Servlet 子类的开发,在 web.xml 中注册 Servlet ,就完成了一个 Servlet 的开发过程
HttpServletRequest (HttpServlet 请求对象)
HttpServletRequest 是一个接口,具体的实现是由 web 容器来实现的。也是由 web 容器来负责创建的 其中封装了 http 请求协议的信息。通过 HttpServletRequest 我们可以在请求时获取到请求的所有信息
常用的方法
- 获取用户请求的数据
- String getParameter(String name): 获取指定的字段名的数据
- Map<String, String[]> getParameterMap(): 获取全部的请求数据,返回一个 Map 对象
- Enumeration getParameterNames(): 获取全部的请求字段名,返回一个枚举对象
- String getParameter(String name): 根据指定的字段名获取字段值,(当字段的值是一个 checkbox 的时候,只能获取多选的第一个选中值,可能会造成数据的获取不准确)
- String[] getParameterValues(String name): 根据指定的字段名获取字段的值,返回一个String[] (日常开发中推荐使用这个)
- 设置请求体中的字符集,
- (Tomcat 9包括9之前如果前端 post 请求体中有中文,就会出现乱码的情况,这时候要调用这个方法将字符集设置文UTF-8)
- (在 Tomcat 10之后采用了UTF-8的字符集,所以不需要调用)
- void setCharacterEnCoding("UTF-8")
- 在请求域中存储数据,获取数据,删除数据
- void setAttribute(String key, Object data)
- Object getAttribute(String key)
- void removeAttribute(String key)
- 获取应用根路径
- String getContextPath()
- 获取请求方式
- String getMethod()
- 获取请求的 URI,
- String getRequestURI()
- 获取 Servlet 的路径 (不带项目的根路径)
- String getServletPath()
- 获取请求转发处理对象
- RequestDispatcher getRequestDispatcher(String path)
import jakarta.servlet.RequestDispatcher;
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 Server extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 第一步,获取请求转发器,
RequestDispatcher requestDispatcher = req.getRequestDispatcher("/b");
// 第二步,调用请求转发器的 forward 方法完成跳转/转发
requestDispatcher.forward(req, resp);
}
}
过滤器 (Filter)
- 什么是过滤器 在请求执行到对应的 Servlet 的时候,先劫拦请求执行一些公共的代码,或者在响应的时候先执行一些公共的代码
- 请求劫拦器 (客户端请求的时候先做验证是否有登录,有则正常请求,无则重定向到登录页)
- 响应劫拦器 (将数据做 json 化处理)
如何实现一个 Filter
- 实现 jakarta.servlet.Filter 接口
- 重写 init,destroy,doFilter 方法
- init:Filter 初始化
- doFilter:过滤器处理方法
- doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain): 执行 filterChain.doFilter(req, resp) 为执行下一个过滤器,没有下一个过滤器的话,就执行目标 Servlet
- destroy:Filter 销毁
import jakarta.servlet.*;
import java.io.IOException;
public class LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void destroy() {
Filter.super.destroy();
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("AFilter doFilter !");
filterChain.doFilter(servletRequest, servletRequest);
}
}
注册 Filter
- 在 web.xml 文件中注册 Filter
<filter>
<filter-name>filter 名称</filter-name>
<filter-class>filter 类路径</filter-class>
</filter>
<filter-mapping>
<filter-name>filter 名称</filter-name>
<url-pattern>需要过滤的路径</url-pattern>
</filter-mapping>
- 在 Servlet 中使用注解注册
import jakarta.servlet.Filter;
@WebFilter({ "/test01", "/test02" })
public class AFilter extends Filter {
}
- 在注册中使用模糊匹配
- 使用 *
- @WebFilter("/user/*") 匹配所有以 /user 开头的请求
- @WebFilter("*/user") 匹配所有以 /user 结尾的请求
转发
转发是由 Tomcat 服务器决定的,将前端请求的资源转到另一个资源处理
例:前端请求 /getData 这个服务器资源,在 /getData 对应的 Servlet 代码中将请求转发 给了 /searchFromDB 这个服务器资源对应的 Servlet 处理。
- 最终返回响应由 /searchFormDB 这个资源来完成
- 对前端而言没有任何的影响,该拿到什么数据就拿到什么数据
- 转发的时候路径是不带项目名的
// Servlet 中核心代码
void doGet(HttpServletRequest req, HttpServletResponse resp) {
req.getRequestDispatcher("/searchFormDB").forward(req, resp);
}
重定向
重定向是由前端完成的,具体跳转到什么路由是由前端决定的,是浏览器说了算 将会产生两次请求,依次重定向之前的请求,依次重定向到新的路由之后的请求
例: 重定向到 /todo/b.html
- 重定向的时候路由要带上项目名
// 核心代码
void doGet(HttpServletRequest req, HttpServletResponse resp) {
resp.sendRedirect("/todo/b.html");
}