https://www.bilibili.com/video/BV1UN411x7xe
tomcat
tomcat 架构图,与 jre,应用程序之前的关系
安装使用
tomcat 10 开始,api 从 javax.* 转为使用 jakarta.*,需要至少使用 jdk 11+
cmd 中默认 gbk 编码,解决控制台乱码,修改\conf\logging.properties
java.util.logging.ConsoleHandler.encoding = GBK
startup.bat 启动
目录
- conf
server.xml,可配置连接器启动端口
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxParameterCount="1000"
/>
tomcat-users.xml 配置 manager 项目的访问
<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
version="1.0">
<role rolename="manager-gui"/>
<role rolename="manager-script"/>
<role rolename="manager-jmx"/>
<role rolename="manager-status"/>
<user username="admin" password="admin" roles="manager-gui,manager-script,manager-jmx,manager-status"/>
</tomcat-users>
访问 http://localhost:8080/manager 访问管理页面
- libs: 公共依赖的jar
- webapps: 存放部署的web项目,有五个默认的项目
- work: 与 jsp 有关,运行时生成文件(了解)
访问自带的 examples 项目,区分项目上下文路径与项目部署目录之间的关系
WebAPP 标准结构
<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_6_0.xsd"
version="6.0"
metadata-complete="true">
</web-app>
web 项目部署的方式
方式1:直接将编译好的项目放在webapps目录下(已经演示)
方式2:将编译好的项目打成war包放在webapps目录下,tomcat启动后会自动解压war包(其实和第一种一样)
方式3:可以将项目放在非webapps的其他目录下,在tomcat中通过配置文件指向app的实际磁盘路径
apache-tomcat-10.1.26\conf\Catalina\localhost
<Context docBase="D:\tool\apache-tomcat-10.1.26-windows-x64\apache-tomcat-10.1.26\webappsnew\app" path="/app"/>
idea 关联 tomcat
- 建立tomcat和idea的关联
- 使用idea创建一个javaWEB工程 在WEB 工程中开发代码
- 使用idea将工程构建成一个可以发布的app
<?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"
metadata-complete="true">
</web-app>
- 使用idea将构建好的app部署到tomcat中, 启动运行
idea 部署 web项目原理
- idea并没有直接进将编译好的项目放入tomcat的webapps中
- idea根据关联的tomcat, 创建了一个tomcat副本, 将项目部署到了这个副本中
- idea的tomcat副本在C:\用户\当前用户\AppData\Local\JetBrains\IntelliJIdea2022.2\tomcat\中
- idea的tomcat副本并不是一个完整的tomcat, 副本里只是准备了和当前项目相关的配置文件而已
- idea启动tomcat时, 是让本地tomcat程序按照tomcat副本里的配置文件运行
- idea的tomcat副本部署项目的模式是通过conf/Catalina/localhost/*.xml配置文件的形式实现项目部署的
servlet
Servlet 概念
不是所有的JAVA类都能用于处理客户端请求,能处理客户端请求并做出响应的一套技术标准就是Servlet
Servlet是运行在服务端的,所以Servlet必须在WEB项目中开发且在Tomcat这样的服务容器中运行
HttpServletRequest与HttpServletResponse
servelt 程序编写
添加依赖
或者添加 maven 依赖
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0</version>
<scope>provided</scope>
</dependency>
:::color1
provided 作用域的servlet依赖包的理解
开发过程:
在IDE中编写代码时需要Servlet API来编译
运行测试用例时需要Servlet API
部署过程:
将WAR包部署到Tomcat时,不需要包含Servlet API
Tomcat自带Servlet API,会提供给所有部署的应用使用
:::
注解
/**
* Servlet配置方式有两种:
* 1. 注解配置:@WebServlet("/hello")
* 2. XML配置:在web.xml中配置servlet和servlet-mapping
*
*/
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head>");
out.println("<title>Hello Servlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>欢迎使用Servlet!</h1>");
out.println("<p>当前时间: " + new java.util.Date() + "</p>");
out.println("<p>这是使用XML配置方式的Servlet</p>");
out.println("</body>");
out.println("</html>");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
xml
<!-- Servlet配置 -->
<servlet>
<!-- servlet-name是servlet的唯一标识 -->
<servlet-name>helloServlet</servlet-name>
<!-- servlet-class指定Servlet类的完全限定名 -->
<servlet-class>com.example.servlet.HelloServlet</servlet-class>
</servlet>
<!-- Servlet映射配置 -->
<servlet-mapping>
<!-- 需要和上面的servlet-name保持一致 -->
<servlet-name>helloServlet</servlet-name>
<!-- url-pattern定义访问路径 -->
<url-pattern>/hello</url-pattern>
</servlet-mapping>
Servlet 生命周期与作用域
生命周期 | 对应方法 | 执行时机 | 执行次数 |
---|---|---|---|
构造对象 | 构造器 | 第一次请求或者容器启动 | 1 |
初始化 | init() | 构造完毕后 | 1 |
处理服务 | service(HttpServletRequest req, HttpServletResponse resp) | 每次请求 | 多次 |
销毁 | destory() | 容器关闭 | 1 |
:::color1
Servlet 初始化时可以指定启动参数
在 init() 阶段加载
:::
Servlet在Tomcat中是单例的
Servlet的成员变量在多个线程栈之中是共享的
不建议在service方法中修改成员变量 在并发请求时,会引发线程安全问题
ServletConfig&ServletContext
ServletConfig
接口方法
方法名 | 作用 |
---|---|
getServletName() | 获取<servlet-name>HelloServlet</servlet-name>定义的Servlet名称 |
getServletContext() | 获取ServletContext对象 |
getInitParameter() | 获取配置Servlet时设置的『初始化参数』,根据名字获取值 |
getInitParameterNames() | 获取所有初始化参数名组成的Enumeration对象 |
<!-- 配置Servlet本身 -->
<servlet>
<!-- 全类名太长,给Servlet设置一个简短名称 -->
<servlet-name>HelloServlet</servlet-name>
<!-- 配置Servlet的全类名 -->
<servlet-class>com.atguigu.servlet.HelloServlet</servlet-class>
<!-- 配置初始化参数 -->
<init-param>
<param-name>goodMan</param-name>
<param-value>me</param-value>
</init-param>
<!-- 配置Servlet启动顺序 -->
<load-on-startup>1</load-on-startup>
</servlet>
public class HelloServlet implements Servlet {
// 声明一个成员变量,用来接收init()方法传入的servletConfig对象
private ServletConfig servletConfig;
public HelloServlet(){
System.out.println("我来了!HelloServlet对象创建!");
}
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("HelloServlet对象初始化");
// 将Tomcat调用init()方法时传入的servletConfig对象赋值给成员变量
this.servletConfig = servletConfig;
}
@Override
public ServletConfig getServletConfig() {
// 返回成员变量servletConfig,方便使用
return this.servletConfig;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
// 控制台打印,证明这个方法被调用了
System.out.println("我是HelloServlet,我执行了!");
// 返回响应字符串
// 1、获取能够返回响应数据的字符流对象
PrintWriter writer = servletResponse.getWriter();
// 2、向字符流对象写入数据
writer.write("Hello,I am Servlet");
// =============分割线===============
// 测试ServletConfig对象的使用
// 1.获取ServletConfig对象:在init()方法中完成
System.out.println("servletConfig = " + servletConfig.getClass().getName());
// 2.通过servletConfig对象获取ServletContext对象
ServletContext servletContext = this.servletConfig.getServletContext();
System.out.println("servletContext = " + servletContext.getClass().getName());
// 3.通过servletConfig对象获取初始化参数
Enumeration<String> enumeration = this.servletConfig.getInitParameterNames();
while (enumeration.hasMoreElements()) {
String name = enumeration.nextElement();
System.out.println("name = " + name);
String value = this.servletConfig.getInitParameter(name);
System.out.println("value = " + value);
}
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("HelloServlet对象即将销毁,现在执行清理操作");
}
}
ServletContext
- 代表:整个Web应用
- 是否单例:是
- 典型的功能:
- 获取某个资源的真实路径:getRealPath()
- 获取整个Web应用级别的初始化参数:getInitParameter()
- 作为Web应用范围的域对象
- 存入数据:setAttribute()
- 取出数据:getAttribute()
<!-- 配置Web应用的初始化参数 -->
<context-param>
<param-name>handsomeMan</param-name>
<param-value>alsoMe</param-value>
</context-param>
String handsomeMan = servletContext.getInitParameter("handsomeMan");
System.out.println("handsomeMan = " + handsomeMan);
三大域对象
作用域 | 生命周期 | 典型对象 |
---|---|---|
Request | 一次 HTTP 请求 | <font style="color:rgba(0, 0, 0, 0.87);background-color:rgb(241, 241, 241);">HttpServletRequest</font> 属性 |
Session | 用户会话期间(如登录状态) | <font style="color:rgba(0, 0, 0, 0.87);background-color:rgb(241, 241, 241);">HttpSession</font> 属性 |
Application | Web 应用全局(如配置信息) | <font style="color:rgba(0, 0, 0, 0.87);background-color:rgb(241, 241, 241);">ServletContext</font> 属性 |
继承结构
Servlet
接口
public interface Servlet {
// 初始化方法,构造完毕后由tomcat自动调用完成初始化功能的方法
void init(ServletConfig var1) throws ServletException;
// 获得ServletConfig对象的方法 -> 提供一些初始信息 <init-param>
ServletConfig getServletConfig();
// 接收用户请求,向用于响应信息的方法
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
// 返回Servlet字符串形式描述信息的方法-了解
String getServletInfo();
// Servlet在回收前,由tomcat调用的销毁方法,往往用于做资源的释放工作
void destroy();
}
GenericServlet
抽象类
public abstract class GenericServlet implements Servlet {
private transient ServletConfig config;
public void destroy() {
// 将抽象方法重写为普通方法,在方法内部没有任何的实现代码
// 平庸实现
}
// tomcat在调用init方法时,会读取配置信息进入一个ServletConfig对象并将该对象传入init方法
public void init(ServletConfig config) throws ServletException {
// 将config对象存储为当前的属性
this.config = config;
// 调用了重载的无参的init
this.init();
}
// 重载的初始化方法,我们重写初始化方法时对应的方法
public void init() throws ServletException {
}
// 返回ServletConfig的方法
public ServletConfig getServletConfig() {
return this.config;
}
// 再次抽象声明service方法
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
}
HttpServlet
抽象类 侧重 service 的处理
public abstract class HttpServlet extends GenericServlet {
// 参数的父转子 调用重载service方法
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
// 参数的父转子
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
// 调用重载的service
this.service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求的方式
String method = req.getMethod(); // GET POST PUT DELETE OPTIONS ...
// 根据请求方式,调用对应do... 方法
if (method.equals("GET")) {
this.doGet(req, resp);
} else if (method.equals("HEAD")) {
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
resp.sendError(501, errMsg);
}
}
// 故意响应405
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
"http.method_get_not_supported"
// 故意响应405 请求方式不允许的信息
resp.sendError(405, msg);
}
// 故意响应405
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
"http.method_get_not_supported"
// 故意响应405 请求方式不允许的信息
resp.sendError(405, msg);
}
}
- 部分程序员推荐在Servlet中重写do***方法处理请求。理由是:service方法中可能做了一些处理,如果我们直接重写service的话,父类中的service方法处理的功能则失效
- 目前直接重写service也没有什么问题
- 后续使用了SpringMVC框架后,我们则无需继承HttpServlet,处理请求的方法也无需是do***_service
- 如果doGet 和doPost方法中,我们定义的代码都一样,可以让一个方法直接调用另一个方法掌握的技能
- 继承HttpServlet后,要么重写service方法,要么重写doGet/doPost
请求转发与重定向
请求转发和响应重定向是web应用中间接访问项目资源的两种手段,也是Servlet控制页面跳转的两种手段
请求转发通过HttpServletRequest实现,响应重定向通过HttpServletResponse实现
请求转发
servlet 之间的接力
请求转发是通过HttpServletRequest对象实现的
请求转发是服务器内部行为,对客户端是屏蔽的
客户端只产生了一次请求,服务端只产生了一对request和response对象
客户端的地址栏是不变的
请求的参数是可以继续传递的
目标资源可以是servlet动态资源,也可以是html静态资源
目标资源可以是WEB-INF下的受保护的资源,该方式也是WEB-INF下的资源的唯一访问方式
重定向
响应重定向通过HttpServletResponse对象的sendRedirect方法实现
响应重定向是服务端通过302响应码和路径告诉客户端自己去找其他资源,是在服务端提示下进行的,客户端的行为
客户端至少发送了两次请求,客户端地址栏是要变化的
服务端产生了多对请求和响应对象,且请求和响应对象不会传递给下一个资源
因为全程产生了多个HttpServletRequest对象,所以请求参数不可以传递,请求域中的数据也不可以传递
重定向可以是其他Servlet动态资源,也可以是一些静态资源以实现页面跳转
重定向不可以到WEB-INF下受保护的资源
filter
图
介绍
- Filter接口定义了过滤器的开发规范,所有的过滤器都要实现该接口
- Filter的工作位置是项目中所有目标资源之前,容器在创建HttpServletRequest和HttpServletResponse对象后,会先调用Filter的doFilter方法
- Filter的doFilter方法可以控制请求是否继续,如果放行,则请求继续,如果拒绝,则请求到此为止,由过滤器本身做出响应
- Filter不仅可以对请求做出过滤,也可以在目标资源做出响应前,对响应再次进行处理
- Filter是GOF中责任链模式的典型案例
- Filter的常用应用包括但不限于:登录权限检查,解决网站乱码,过滤敏感字符,日志记录,性能分析……
api
package jakarta.servlet;
import java.io.IOException;
public interface Filter {
default public void init(FilterConfig filterConfig) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException;
default public void destroy() {}
}
API | 目标 |
---|---|
default public void init(FilterConfig filterConfig) | 初始化方法,由容器调用并传入初始配置信息filterConfig对象 |
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) | 过滤方法,核心方法,过滤请求,决定是否放行,响应之前的其他处理等都在该方法中 |
default public void destroy() | 销毁方法,容器在回收过滤器对象之前调用的方法 |
案例-记录日志
public class HelloFilter implements Filter {
/**
* 过滤请求的和响应的方法
* 1.请求到达目标资源之前,先经过该方法
* 2.该方法有能力控制请求是否继续向后到达目标资源,可以在该方法内直接向客户端做响应处理
* 3.请求到达目标资源后,响应之前还会经过该方法
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 请求到达目标资源之前的代码
System.out.println("loggingFilter before doFilter invoked");
// 放行
chain.doFilter(request, response);
// 响应之前的功能代码
System.out.println("loggingFilter after doFilter invoked");
}
}
<filter>
<filter-name>loggingFilter</filter-name>
<filter-class>com.example.servlet.HelloFilter</filter-class>
</filter>
<filter-mapping>
<!--
url-pattern 根据请求的资源路径 对指定的请求进行过滤
/* 过滤全部资源
/a/* 过滤以a开头的资源
*.html 过滤以html为后缀的资源
/servlet1 对servlet1请求进行过滤
servlet-name 根据请求的servlet的别名,最指定的servlet资源进行过滤
一个 filter-mapping 中可以同时存在多个 url-pattern 和 servlet-name
-->
<filter-name>loggingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
生命周期
过滤器作为web项目的组件之一,和Servlet的生命周期类似,略有不同,没有servlet的load-on-startup的配置,默认就是系统启动立刻构造
阶段 | 对应方法 | 执行时机 | 执行次数 |
---|---|---|---|
创建对象 | 构造器 | web应用启动时 | 1 |
初始化方法 | void init(FilterConfig filterConfig) | 构造完毕 | 1 |
过滤请求 | void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) | 每次请求 | 多次 |
销毁 | default void destroy() | web应用关闭时 | 1次 |
过滤器链
一个web项目中,可以同时定义多个过滤器,多个过滤器对同一个资源进行过滤时,工作位置有先后,整体形成一个工作链,称之为过滤器链。
- 注解方式下顺序->类名
- xml方式下顺序->filter-mapping 声明顺序
Listener
监听器:专门用于对域对象对象身上发生的事件或状态改变进行监听和相应处理的对象
- 监听器是GOF设计模式中,观察者模式的典型案例
- 观察者模式:当被观察的对象发生某些改变时,观察者自动采取对应的行动的一种设计模式
- 监听器使用的感受类似JS中的事件,被观察的对象发生某些情况时,自动触发代码的执行
- 监听器并不监听web项目中的所有组件,仅仅是对三大域对象做相关的事件监听
监听器的分类
- web中定义八个监听器接口作为监听器的规范,这八个接口按照不同的标准可以形成不同的分类
按监听的对象划分
○ application域监听器 ServletContextListener ServletContextAttributeListener
○ session域监听器 HttpSessionListener HttpSessionAttributeListener HttpSessionBindingListener HttpSessionActivationListener
○ request域监听器 ServletRequestListener ServletRequestAttributeListener
按监听的事件分
○ 域对象的创建和销毁监听器 ServletContextListener HttpSessionListener ServletRequestListener
○ 域对象数据增删改事件监听器 ServletContextAttributeListener HttpSessionAttributeListener ServletRequestAttributeListener
○ 其他监听器 HttpSessionBindingListener HttpSessionActivationListener
案例
ServletContextListener
在应用程序启动的时候会触发,SpringMVC 会使用到<font style="color:rgb(51, 51, 51);">ContextLoaderListener</font>
,在web环境下能在应用程序启动的时候加载spring容器,就是ServletContextListener
的实现类
// @WebListener
public class HelloListener implements ServletContextListener {
@Override
public void contextInitialized(
// Event对象代表本次事件,通过这个对象可以获取ServletContext对象本身
ServletContextEvent sce) {
System.out.println("Hello,我是ServletContext,我出生了!");
ServletContext servletContext = sce.getServletContext();
System.out.println("servletContext = " + servletContext);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("Hello,我是ServletContext,我打算去休息一会儿!");
}
}
<!-- 每一个listener标签对应一个监听器配置,若有多个监听器,则配置多个listener标签即可 -->
<listener>
<!-- 配置监听器指定全类名即可 -->
<listener-class>com.atguigu.listener.AtguiguListener</listener-class>
</listener>