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/jakartaeehttps://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/jakartaeehttps://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 {@Overrideprotected 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>");}@Overrideprotected 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對象創建!");}@Overridepublic void init(ServletConfig servletConfig) throws ServletException {System.out.println("HelloServlet對象初始化");// 將Tomcat調用init()方法時傳入的servletConfig對象賦值給成員變量this.servletConfig = servletConfig;}@Overridepublic ServletConfig getServletConfig() {// 返回成員變量servletConfig,方便使用return this.servletConfig;}@Overridepublic 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);}}@Overridepublic String getServletInfo() {return null;}@Overridepublic 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;// 調用了重載的無參的initthis.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;// 調用重載的servicethis.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.請求到達目標資源后,響應之前還會經過該方法*/@Overridepublic 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 {@Overridepublic void contextInitialized(// Event對象代表本次事件,通過這個對象可以獲取ServletContext對象本身ServletContextEvent sce) {System.out.println("Hello,我是ServletContext,我出生了!");ServletContext servletContext = sce.getServletContext();System.out.println("servletContext = " + servletContext);}@Overridepublic void contextDestroyed(ServletContextEvent sce) {System.out.println("Hello,我是ServletContext,我打算去休息一會兒!");}
}
<!-- 每一個listener標簽對應一個監聽器配置,若有多個監聽器,則配置多個listener標簽即可 -->
<listener><!-- 配置監聽器指定全類名即可 --><listener-class>com.atguigu.listener.AtguiguListener</listener-class>
</listener>