1、Servlet體系結構html
在 servlet-api.jar (2.5) 中有兩個包:javax.servlet 和 javax.servlet.http前端
1 Servlet、GenericServlet及HttpServletjava
Servlet 是一個接口,其方法以下:web
- public void init(ServletConfig config);
- public void service(ServletRequest req, ServletResponse res);
- public void destroy();
- public String getServletInfo(); // 返回servlet的信息,如做者、版本和版權
- public ServletConfig getServletConfig(); // 獲取servlet配置屬性對象
GenericServlet 實現了 Servlet接口,是一個通用的、不特定於任何協議的 Servletspring
- public void init()
- public void init(ServletConfig config)
- public abstract void service(ServletRequest req, ServletResponse res)
- public void destroy()
- public void log(String msg) // 將消息寫入日誌,利用ServletContext的方法寫入
- public void log(String message, Throwable t)
- public String getInitParameter(String name) // 獲取初始化參數,利用ServletConfig的方法獲取
- public Enumeration getInitParameterNames()
- public String getServletName()
- public String getServletInfo() // 返回servlet的信息,如做者、版本和版權
- public ServletConfig getServletConfig()
- public ServletContext getServletContext()
HttpServlet 繼承於 GenericServlet,針對於 HTTP 協議的類apache
- public void service(ServletRequest req, ServletResponse res)
- protected void service(HttpServletRequest req, HttpServletResponse resp)
- protected void doGet(...)、doPost(...)、doHead(...)、doPut(...)、doDelete(...)、doOptions(...)、doTrace(...)
- protected long getLastModified(HttpServletRequest req) // 最後修改時間,應該重寫實現這個方法
2 ServletConfig、ServletContextapi
ServletConfig 對象儲存了 Servlet 的一些配置屬性,在 Servlet 執行 init() 方法時傳入,其方法以下:數組
- public String getServletName(); // 獲取Servlet名稱
- public String getInitParameter(String name); // 獲取初始化參數值
- public Enumeration getInitParameterNames(); // 獲取全部參數名稱
- public ServletContext getServletContext();
另外在 ServletConfig 中還有一個 ServletContext,它定義了有關 Servlet 容器的方法,其方法以下:瀏覽器
- public String getContextPath(); // 返回web項目的路徑
- public String getRealPath(String path);
- public URL getResource(String path); // 返回webapp下的文件路徑對應的URL
- public Set getResourcePaths(String path); // 返回path路徑下的目錄或文件
- public InputStream getResourceAsStream(String path); // 返回path路徑的資源
- public ServletContext getContext(String uripath);
- public RequestDispatcher getRequestDispatcher(String path);
- public RequestDispatcher getNamedDispatcher(String name);
- public String getMimeType(String file); // 返回指定文件的類型,如 text/html、image/gif
- public String getServerInfo(); // 返回Servlet容器的名稱和版本
- public String getServletContextName(); // 返回這個web應用程序名稱
- public String getInitParameter(String name);
- public Enumeration getInitParameterNames();
- public Enumeration getAttributeNames(); // 返回Servlet容器的全部屬性
- public Object getAttribute(String name); // 返回Servlet容器的指定屬性
- public void setAttribute(String name, Object object);
- public void removeAttribute(String name);
- public void log(String msg); // 將消息寫入到日誌文件中
- public void log(String message, Throwable throwable);
- public int getMajorVersion(); // 返回這個容器支持的Servlet主版本,如2.5返回2
- public int getMinorVersion(); // 返回這個容器支持的Servlet小版本,如2.5返回5
3 ServletRequest、ServletResponse緩存
當請求達到時,容器將 ServletRequest 和 ServletResponse 傳遞給 Servlet。
ServletRequest 接口的方法以下:
- public Enumeration getAttributeNames();
- public Object getAttribute(String name);
- public void setAttribute(String name, Object o);
- public void removeAttribute(String name);
- public void setCharacterEncoding(String env); // 設置請求體的編碼類型,讀取參數前使用
- public String getCharacterEncoding(); // 獲取請求體的編碼類型
- public String getContentType(); // 請求體的類型
- public int getContentLength(); // 請求體的長度,長度未知則返回-1
- public ServletInputStream getInputStream(); // 請求體的字節流
- public BufferedReader getReader(); // 請求體的字符流
- public String getParameter(String name); // 名爲name的參數值
- public String[] getParameterValues(String name); // 名爲name的參數值,是一個數組
- public Enumeration getParameterNames(); // 全部參數名稱
- public Map getParameterMap(); // 全部參數的名稱和值
- public String getProtocol(); // 請求的協議版本,如 HTTP/1.1
- public String getScheme(); // 請求的協議方式,如 http https ftp
- public String getServerName(); // 服務端的主機、服務器名或服務器IP地址
- public int getServerPort(); // 服務端的端口號
- public String getRemoteHost(); // 客戶端或最終代理的主機名稱
- public String getRemoteAddr(); // 客戶端或最終代理的IP地址
- public int getRemotePort(); // 客戶端或最終代理的端口號
- public String getLocalName();
- public String getLocalAddr();
- public int getLocalPort();
- public Locale getLocale(); // 返回請求頭Accept-Language設置的語言環境
- public Enumeration getLocales();
- public boolean isSecure(); // 是否使用HTTPS等安全通道進行的請求
- public RequestDispatcher getRequestDispatcher(String path);
ServletResponse 接口的方法以下:
- public String getCharacterEncoding(); // 獲取響應體的編碼類型
- public void setCharacterEncoding(String charset); // 設置響應體編碼類型
- public String getContentType(); // 獲取響應體的類型
- public void setContentType(String type); // 設置響應體的類型
- public void setContentLength(int len); // 設置響應體長度
- public ServletOutputStream getOutputStream(); // 獲取響應體的字節流
- public PrintWriter getWriter(); // 獲取響應體的字符流
- public int getBufferSize(); // 返回實際緩衝大小,不使用緩衝則爲0
- public void setBufferSize(int size); // 設置響應體緩衝大小
- public void flushBuffer(); // 將緩衝區內容寫入到客戶端
- public void resetBuffer(); // 清除緩衝區數據,若是緩衝已經被寫入客戶端,則拋異常
- public void reset(); // 清除緩衝區的數據、狀態碼及響應頭,若是已經寫入客戶端,則拋異常
- public boolean isCommitted(); // 響應是否已提交
- public void setLocale(Locale loc);
- public Locale getLocale();
HttpServletRequest 和 HttpServletResponse 接口分別繼承自 ServletRequest 和 ServletResponse,在其基礎上
HttpServletRequest 接口增長的方法以下:
- public String getAuthType();
- public Cookie[] getCookies();
- public long getDateHeader(String name);
- public int getIntHeader(String name);
- public String getHeader(String name);
- public Enumeration getHeaders(String name);
- public Enumeration getHeaderNames();
- public String getMethod();
- public String getPathInfo();
- public String getPathTranslated();
- public String getContextPath();
- public String getQueryString();
- public String getRemoteUser();
- public boolean isUserInRole(String role);
- public java.security.Principal getUserPrincipal();
- public String getRequestedSessionId();
- public String getRequestURI();
- public StringBuffer getRequestURL();
- public String getServletPath();
- public HttpSession getSession(boolean create);
- public HttpSession getSession();
- public boolean isRequestedSessionIdValid();
- public boolean isRequestedSessionIdFromCookie();
- public boolean isRequestedSessionIdFromURL();
HttpServletResponse 接口增長的方法以下:
- public void addCookie(Cookie cookie);
- public boolean containsHeader(String name);
- public String encodeURL(String url);
- public String encodeRedirectURL(String url);
- public void sendError(int sc, String msg) throws IOException;
- public void sendError(int sc) throws IOException;
- public void sendRedirect(String location) throws IOException;
- public void setDateHeader(String name, long date);
- public void addDateHeader(String name, long date);
- public void setHeader(String name, String value);
- public void addHeader(String name, String value);
- public void setIntHeader(String name, int value);
- public void addIntHeader(String name, int value);
- public void setStatus(int sc);
上述的四個接口分別有一個包裝類的實現,利用了裝飾者模式。
4 Filter、FilterConfig、FilterChain
Filter 即過濾器,它是 AOP 思想的一種實現(利用回調函數實現的),經過它咱們能夠實現權限訪問控制、過濾敏感詞彙、日誌記錄等等。爲何要使用 Filter 呢?或者說爲何要使用 AOP 的方式去作這個呢?若是咱們不使用 Filter 而直接在 Servlet 的 doGet()、doPost() 方法中實現上述功能也是能夠的,可是這樣致使了代碼冗餘,因此咱們須要把這些公共的代碼抽象出來進行封裝。像 OOP 的封裝方式針對的是對具備上下關係的對象,而像訪問控制、日誌等功能並不適合這樣的封裝,它更像是一種左右關係,因此咱們要用 AOP 的方式進行封裝。
Filter 能夠實如今 Servlet 的 service() 調用的先後執行一段代碼,從而實現了公共代碼的複用。使用 Filter 與 Servlet 類似,首先要本身編寫一個類實現 Filter 接口,而後在 web.xml 中配置好直接該 Filter 對應的 URL。Filter 中有一個 doFilter() 方法,其使用方式大體以下
public class FilterTest implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init");
// 獲取過濾器的名字
String filterName = filterConfig.getFilterName();
// 獲取其初始化參數,在 web.xml 中指定的
String param1 = filterConfig.getInitParameter("name");
String param2 = filterConfig.getInitParameter("like");
// 返回過濾器的全部初始化參數的名字的枚舉集合。
Enumeration<String> paramNames = filterConfig.getInitParameterNames();
System.out.println(filterName);
System.out.println(param1);
System.out.println(param2);
while (paramNames.hasMoreElements()) {
String paramName = (String) paramNames.nextElement();
System.out.println(paramName);
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws ServletException, IOException {
// 執行前的操做
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
System.out.println("before");
// 執行service()方法或下一個過濾器方法
chain.doFilter(request, response); //讓目標資源執行,放行
// 執行後的操做
System.out.println("after");
}
@Override
public void destroy() {
System.out.println("destroy");
}
}
編寫完 Filter 實現類後還要在 web.xml 文件中對其註冊和映射
<!-- filter註冊 -->
<filter>
<filter-name>FilterTest</filter-name>
<filter-class>com.filter.FilterTest</filter-class>
<init-param>
<param-name>name</param-name>
<param-value>t</param-value>
</init-param>
<init-param>
<param-name>like</param-name>
<param-value>java</param-value>
</init-param>
</filter>
<!-- filter映射 -->
<filter-mapping>
<filter-name>FilterTest</filter-name>
<url-pattern>*.do</url-pattern>
<!-- 指定過濾器所攔截的 Servlet 名稱
<servlet-name></servlet-name> -->
<!-- 指定過濾器所攔截的資源被 Servlet 容器調用的方式,
REQUEST:用戶直接訪問時調用,即不包括經過RequestDispatcher訪問的狀況
INCLUDE:經過RequestDispatcher的include()方法訪問時調用
FORWARD:經過RequestDispatcher的forward()方法訪問時調用
ERROR:若是目標資源是經過聲明式異常處理機制調用時,那麼該過濾器將被調用
默認REQUEST,而且能夠設置多個<dispatcher>
<dispatcher></dispatcher> -->
</filter-mapping>
咱們能夠編寫多個 Filter,組成了一個 Filter 鏈。執行順序與它們在 web.xml 文件中配置順序有關,先配置則先執行。在上述代碼中,咱們調用了 FilterChain 對象的 doFilter() 方法,此時會先檢查 FilterChanin 對象中是否還有下一個 Filter,若是有則繼續調用,若是沒有則調用 Servlet 的 service() 方法。
4.1 Filter
Filter 的建立和銷燬由其容器負責,容器啓動的時候建立 Filter 實例對象,並調用 init() 方法完成初始化,Filter 只會實例化一次。
- public void init(FilterConfig filterConfig); // 初始化並傳入Filter的配置對象
- public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain); // 執行攔截器的內容
- public void destroy(); // Filter銷燬時調用,當這個方法被調用後,容器還會再調用一次 doFilter() 方法
4.2 FilterConfig
- public String getFilterName();
- public ServletContext getServletContext();
- public String getInitParameter(String name);
- public Enumeration getInitParameterNames();
4.3 FilterChain
Filter 類的核心就是傳遞 FilterChain 對象,在 Tomcat 中 FilterChain 的實現類是 ApplicationFilterChain,它在 filters 數組中保存了到最終 Servlet 對象的全部 Filter 對象,當執行完全部 Filter 對象後就會執行 Servlet。
- public void doFilter(ServletRequest request, ServletResponse response);
5 RequestDispatcher
- public void forward(ServletRequest request, ServletResponse response)
- public void include(ServletRequest request, ServletResponse response)
6 Listener
Listener 是基於觀察者模式設計的,可以方便的從另外一個縱向維度控制程序和數據。在 Servlet 中有兩類共6中觀察者接口,EventListeners 類型的 ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener、HttpSessionAttrbuteListener,還有 LifecycleListeners 類型的 ServletContextListener、HttpSessionListener,如圖所示
這些標籤的實現類能夠配置在 web.xml 的 <listener> 標籤中,也能夠在程序中動態的添加。如 Spring 的 org.springframework.web.context.ContextLoaderLister 就實現了一個 ServletContextListener,當容器加載時啓動 Spring,以下所示
<!-- spring啓動監聽器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- spring配置文件,默認查找 WEB-INF 下的 applicationContext.xml 文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
下面咱們看一下各個 Listener 的具體方法:
ServletContextListener
- public void contextInitialized(ServletContextEvent sce); //在 Context 容器初始時,Filter 和 Servlet 的 init() 前調用
- public void contextDestroyed(ServletContextEvent sce); //在 Context 容器銷燬時,Filter 和 Servlet 的 destroy() 後調用
ServletRequestListener
- public void requestInitialized(ServletRequestEvent sre); // HttpServeltRequest 傳遞到 Servlet 前調用
- public void requestDestroyed(ServletRequestEvent sre);
HttpSessionListener
- public void sessionCreated(HttpSessionEvent se);
- public void sessionDestroyed(HttpSessionEvent se);
HttpSessionBindingListener
- public void valueBound(HttpSessionBindingEvent event); // 對象被放入 session 時調用
- public void valueUnbound(HttpSessionBindingEvent event); // 對象被移出 session 時調用
ServletContextAttributeListener
- public void attributeAdded(ServletContextAttributeEvent scab); //調用 servletContext 的 setAttribute() 時觸發
- public void attributeRemoved(ServletContextAttributeEvent scab); //調用 servletContext 的 removeAttribute() 時觸發
- public void attributeReplaced(ServletContextAttributeEvent scab); //調用 servletContext 的 setAttribute() 替換舊值時觸發
ServletRequestAttributeListener
- public void attributeAdded(ServletRequestAttributeEvent srae);
- public void attributeRemoved(ServletRequestAttributeEvent srae);
- public void attributeReplaced(ServletRequestAttributeEvent srae);
HttpSessionAttributeListener
- public void attributeAdded(HttpSessionBindingEvent se);
- public void attributeRemoved(HttpSessionBindingEvent se);
- public void attributeReplaced(HttpSessionBindingEvent se);
HttpSessionActivationListener
- public void sessionWillPassivate(HttpSessionEvent se); // 通知 session 被鈍化
- public void sessionDidActivate(HttpSessionEvent se); // 通知 session 被激活
7 ServletInputStream、ServletOutputStream
8 ServletException
2、Tomcat 組件
Servlet 不可以獨立運行,須要在它的容器中運行,容器管理着它建立到銷燬的整個過程。在看 Servlet 的生命週期前,咱們先看下 Servlet 的咱們最熟悉的一個容器——Tomcat。
Tomcat 有兩個重要組件:鏈接器(Connector)和容器(Engine容器及其子容器),咱們結合 server.xml 配置文件來看一下這兩個組件。
1 鏈接器(Connector)
首先向 Tomcat 發送的請求能夠分爲兩類:
- Tomcat 做爲應用服務器:請求來自前端的 Web 服務器,如 Nginx、Apache、IIS 等。
- Tomcat 做爲獨立服務器:請求來自瀏覽器。
這些不一樣的請求須要不一樣的鏈接器來接收,在 Service 中有一個引擎和多個鏈接器,以適應不一樣狀況。常見的鏈接器有四種:HTTP鏈接器、SSL鏈接器、AJP鏈接器、proxy鏈接器。在定義鏈接器時能夠配置的屬性有不少,鏈接器公用屬性以下:
- className 指定實現 Connector 接口的類
- enableLookups 是否經過request.getRemoteHost()獲取客戶端的主機名,默認true
- redirectPort 若是鏈接器的協議是HTTP,當收到HTTPS請求時,轉發到此端口
HttpConnector 的屬性:
- className 指定實現 Connector 接口的類
- port 監聽端口,默認8080
- address 指定監聽地址,默認爲全部地址
- bufferSize 設置由端口建立的輸入流緩存大小,默認2048byte
- protocol 鏈接器使用的協議,默認HTTP/1.1
- maxThreads 支持的最大併發鏈接數,默認200
- connectionTimeout 等待客戶端發送請求的超時時間,默認60000,即1分鐘
- acceptCount 設置等待隊列的最大長度,默認爲10。當tomcat全部處理線程均繁忙時,新連接被放置於等待隊列中
JkConnector 的屬性:
- className 指定實現 Connector 接口的類
- port 設定AJP端口號
- protocol 必須爲 AJP/1.3
2 容器(Engine容器及其子容器)
在 Tomcat 中有 Engine、Host、Context 及 Wrapper 四種容器,它們的包含關係以下圖所示
上述的包含並非繼承關係,而是當子容器建立好後會放入到父容器中。Servlet 被包裝成 Wrapper,而後真正管理 Servlet 的是 Context 容器,一個 Context 對應一個 Web 應用。
- Wrapper 封裝了具體訪問的資源,即 Servlet;
- Context 封裝了各個 Wrapper 資源的集合;
- Host 封裝了 Context 資源的集合;
- Engine 能夠當作是對 Host 的邏輯封裝。
咱們再來看一下它們的繼承關係,這些容器的接口都繼承自 Container 接口,爲何要按層次分別封裝一個對象呢?爲了方便統一管理,在不一樣層次的配置其做用域是不同的。
2.1 Engine
Engine 下面擁有多個 Host,即虛擬主機,它的責任就是將用戶的請求分配給一個虛擬主機處理。爲何要使用虛擬主機呢?當咱們有兩個應用時,以下圖的 Love 應用和 SDJTU 應用。咱們想訪問「倪培.我愛你」域名時直接達到 Love 應用,訪問「www.sdjtu.net.cn」域名時直接到達 SDJTU 應用,可是若是不設置虛擬主機是沒法在一個 Tomcat中作到的。那麼,咱們能夠設置兩個虛擬主機,並指定請求到達這個虛擬主機後要去訪問的目錄。
在 Engine 標籤中有幾個屬性能夠填寫
- name 定義 Engine 的名字
- className 指定實現 Engine 接口的類,默認是 StandardEngine
- defaultHost 指定處理請求的默認主機
在 Engine 標籤裏還能夠包含如下幾個元素
2.2 Host
Host 表明一個虛擬主機,在它下面有多個 Context,一個 Context 表明一個 Web 應用。
在 Host 標籤中的幾個屬性
- name 定義 Host 的名字
- className 指定實現 Host 接口的類,默認是 StandardHost
- appBase 指定虛擬主機的目錄,默認是 webapps
- unpackWARs 是否先展開war文件再運行。若是爲 false 將直接運行 war 文件
- autoDeploy 表示是否支持熱部署
- alias 用來指定主機別名
- deployOnStartup 是否在啓動時自動發佈目錄下的全部Web應用
在 Host 標籤中還能夠包含如下幾個元素
2.3 Context
Context 表明運行在虛擬主機上的單個 Web 應用。
在 Context 標籤中的幾個屬性
- className 指定現實 Context 接口的類,默認是 StandardContext 類
- path 配置Web應用對應的URL,即跟在域名後面的內容
- docBase 指定要執行的Web應用
- reloadable 當項目下的 class 文件被更新時,是否從新加載Web應用
- cookies 指定是否經過 Cookies 來支持 Session,默認爲 true
- useNaming 指定是否支持 JNDI,默認值爲 ture
在 Context 標籤中的元素
- Logger
- Realm
- Resource
- ResourceParams
2 Tomcat 啓動過程
Tomcat 從 7.0 開始增長了一個啓動類 org.apache.catalina.startup.Tomcat。經過這個類的實例調用 start() 方法就能夠啓動 Tomcat,還能夠經過這個對象增長和修改 Tomcat 的配置參數,來動態的添加 Context、Servlet 等。
Tomcat 的啓動是基於觀察者模式設計的,全部的容器都繼承了 Lifecycle 接口,由它來管理容器的生命週期,全部容器的修改和狀態改變都會由它去通知已經註冊的觀察者(Listener)。
當 Context 容器初始化狀態爲 init 時,添加到 Context 容器的 Listener 將會被調用。ContextConfig 繼承了 LifecycleListener 接口,它是在調用了 Tomcat.addWebapp 時被加入到 StandardContext 容器的,這個類將會負責整個 Web 應用的配置解析工做。ContextConfig 的 init 方法將會主要完成如下工做:
- 建立 ContextDigester 對象來解析 XML 配置文件
- 讀取默認的 context.xml 配置文件,若是存在則解析它
- 讀取默認的 Host 配置文件,若是存在則解析它
- 讀取默認的 Context 自身的配置文件,若是存在則解析它
- 設置 Context 的 DocBase
當 ContextConfig 的 init 方法完成後,Context 容器會執行 startInternal 方法,主要包括如下工做
- 建立讀取資源文件的對象
- 建立 ClassLoader 對象
- 設置應用的工做目錄
- 啓動相關的輔助類,如 logger、realm、resources 等
- 修改啓動狀態,通知感興趣的觀察者
- 子容器的初始化
- 獲取 servletContext 並設置必要參數
- 初始化「load on startup」的 Servlet
Web 應用的初始化是在 ContextConfig 的 configureStart 方法中實現的,應用初始化主要是解析 web.xml 文件。web.xml 文件中的配置會被解析成 WebXml 對象,而後這些配置會放入 Context 中,而且 Servlet 配置會被包裝成 StandardWrapper 並做爲子容器添加到 Context 中。
3、Servlet 生命週期
前面咱們知道 Servlet 由 Tomcat 解析,並被包裝成 Wrapper 添加在 Context 容器中,下面就要進行 Servlet 的實例化。
1 建立實例
建立 Servlet 實例的方法是從 StandardWrapper 的 loadServlet() 方法開始的。loadServlet() 方法獲取了 servletClass,而後將它交給了 InstanceManager 去建立一個基於 ServletClass.class 的對象。
Servlet 並非單例的,但通常只會有一個實例,即一個<servlet>標籤對應一個實例。另外若是 Servlet 沒有配置<servlet-mapping>標籤,則沒法經過請求時建立,只能配置 load-on-startup 使其在容器啓動時便建立。
2 初始化
初始化 Servlet 是在 StandardWrapper 對象的 initServlet() 方法中,這個方法會去調用 Servlet 的 init() 方法,同時把 StandardWrapperFacade 對象做爲 ServletConfig 傳遞進去。
3 處理請求
客戶端發出 Http 請求,Tomcat 接收到請求後將信息封裝進了 HttpRequest 對象,接着建立一個 HttpResponse 對象,而後調用 HttpServlet 對象的 service() 方法,把 HttpRequest 對象與 HttpRespnse 對象傳入進去。當執行完 service() 方法後,Tomcat 把響應傳遞給客戶端。
4 銷燬