Javaweb三大元件之Servlet
Javaweb開發中有很重要的三個元件分別是:Servlet,Filter,Listener。這些元件在Javaweb的開發中起著不同且至關重要的作用,而我們常用的web框架基本都是基於這些基礎元件進行封裝之後的用法,所以對於這些原生的方式進行Javaweb開發也要需要了解的,這樣對框架的認識也能更加深入,理解為什麼要那麼做。
1.Serlvet的廣義概念
Servlet是執行在伺服器容器中的一個小程式,所有的使用者請求最終都會匯入這裡,然後根據請求,通過url交給不同的Servlet物件去處理,也就是業務邏輯的核心部分。
2.Servlet的作用
Servlet在Javaweb擔當核心的作用,最核心的業務邏輯是在Servlet物件中進行處理,然後返回給使用者的,也是我們最需要關注的元件。
3.Servlet的狹義概念
但是其實Servlet在Java中就是一個有5個抽象方法的介面而已,通過實現這個介面構建不同的Servlet類,處理不同的業務請求。
這就是那個神奇的Servlet最本來的樣子。
這幾個函式去看api就知道了,下面主要演示一個Servlet的簡單例子,看看怎麼使用Servlet來進行web的開發。
4. Servlet的基本使用
首先是使用最基本的Servlet介面來實現訪問一個url,然後返回success,順便說一下Servlet的一些xml設定和生命週期
1. 首先需要實現Serlvet介面來建立一個Serlvet類,這樣才能建立物件(介面不能例項化,應該都知道吧)
public class ServletDemo implements Servlet{ //物件銷燬時呼叫 @Override public void destroy( ){ System.out.println("Servlet has been destroyed"); } //這個方法一般不用管 @Override public ServletConfig getServletConfig(){ return null; } //這個方法一般不用管 @Override public String getServletInfo(){ return null; } //做Servlet的一些初始化操作,在Serlvet例項建立的同時呼叫 @Override public void init(ServletConfig servletConfig)throws ServletException{ //測試ServletConfig物件是否接收到xml裡面配置的引數 String username = servletConfig.getInitParameter("username"); System.out.println("username: "+ username); System.out.println("Servlet has been created"); } //這個是Servlet裡面最重要的函式,通過這個函式來處理對應的請求的業務 // 這裡可以去學習ServletRequest和ServletResponse這兩個類,整個web的前後端互動都是基於Request和Response的 @Override public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException{ response.getWriter().write("success"); } }
這樣用一個類ServletDemo來實現了我們最原始的Serlvet介面,這樣這個SerlvetDemo就具備處理請求的能力了,但是處理是誰的請求,又是如何對映到這個類的例項上的呢?
接下來配置Servlet另一個很重要的部分,就是web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <!-- 宣告一個Servlet,並且與一個實體類之間構成對映--> <servlet> <!-- 這是Servlet的name可以自己隨便取--> <servlet-name>servletDemo</servlet-name> <!-- 這是這個名字對應的那個class --> <servlet-class>com.lyh.servlet.ServletDemo</servlet-class> <!-- 這是為Servlet的init函式中的引數ServletConfig物件裡面新增值,通過這樣配置就可以在ServletCongfig物件中取到了 --> <init-param> <param-name>username</param-name> <param-value>lyh</param-value> </init-param> <init-param> <param-name>password</param-name> <param-value>112233</param-value> </init-param> <!-- 這條配置設定Servlet隨著伺服器啟動就建立例項,值必須為大於零的整數,如果有多個,就根據值的從小到大排序進行建立例項 --> <!-- 如果沒有的話,Servlet就在他對應的url第一次被訪問的時候建立 --> <load-on-startup>1</load-on-startup> </servlet> <!-- 這是Servlet和一個url的對映,即對url的訪問會被轉向name等於這個的Servlet來處理 --> <servlet-mapping> <!-- 一個已經聲明瞭的Servlet的name --> <servlet-name>servletDemo</servlet-name> <!-- 想要轉向此Servlet處理的url --> <!-- URL格式支援三種格式的URL形式 完全路徑匹配:例如/login 目錄匹配:例如/user/* , /user/* 副檔名匹配:例如*.do,這種情況是不能加/的,比如/*.do就是錯誤的 當上述三種匹配都通過話,匹配的優先順序是完全路S徑>目錄匹配>副檔名匹配 --> <url-pattern>/ServletDemo</url-pattern> </servlet-mapping> </web-app>
這裡的xml就配置了我們訪問"專案路徑/ServletDemo”的時候,會將請求轉到ServletDemo這個name的Servlet例項去處理,然後就去找name為ServletDemo的例項,然後發現找到了,對應的類是com.lyh.Servlet.ServletDemo這個使用者建立的類。那麼就會去建立一個這個類的物件(一般方便起見,都稱為Servlet例項),呼叫init方法。
此時這個Servlet的生命週期就開始了,通過自己的Service方法來處理從各個ip發來的Http請求,只要是對這個url的請求,都會轉給他來處理,知道伺服器關閉的時候,這個Servlet例項就被銷燬了,然後destroy方法被呼叫,Servlet例項生命週期結束。
這樣一個基礎的web響應就完成了,通過Servlet,Java裡比較原生的方式實現了。
但是我們發現Servlet這個介面直接實現使用有很多不方便的地方,而且我們一般只是和Http協議進行互動,Java設計者自然也想到了這一點,所以在javax.servlet庫中已經有為Http協議專門設計的一個類,HttpServlet。
5.HttpServlet的基本使用
HttpServlet類繼承自GenericServlet類,GenericServlet類實現了Servlet介面,ServletConfig介面和Serializable介面。這裡的原始碼也比較簡單,建議去讀讀原始碼。既然有ServletConfig了,那就能用ServletContext這個全域性的東西。
下面用HttpServlet來做一個簡單的web應用,就可以看到這個這個類比Servlet介面好用多了。
HttpServlet裡有很多方法,我們需要重寫的一般是doGet(),doPost(),init()等等這些方法,因為在HttpServlet中,通過Service方法實際上呼叫的都是doGet()和doPost()這樣的方法,來對Http協議中具體的方法進行業務處理。
先寫一個HttpServletDemo類,繼承自HttpServlet類,重寫了doGet()和init()方法(重寫那個方法是有講究的,還是建議去讀原始碼,就是看最後呼叫到那個方法然後父類將這個方法空了下來,那麼我們就需要去重寫的是這個方法)
public class HttpServletDemo extends HttpServlet{
@Override
public void init() throws ServletException {
System.out.println("HttpServlet has been Created");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//測試ServletConfig物件是否接收到xml裡面配置的引數
ServletContext sc = getServletContext();
System.out.println(sc);
//測試向ServletContext中新增資料,進行讀取和更新
if(sc.getAttribute("count") == null){
sc.setAttribute("count", 1);
}else{
int count = (int) sc.getAttribute("count");
sc.setAttribute("count", count+1);
}
Integer count = (Integer) sc.getAttribute("count");
resp.getWriter().write(count.toString());
}
@Override
public void destroy() {
super.destroy();
System.out.println("HttpServlet has been destroyed");
}
}
這裡可以看到和前面的方式不同的,一個是我們重寫的是doGet()方法不再是service方法,這是由於基類為我們細分了請求型別;另外是使用了ServletContext這個物件,至於具體的說明,看下面的web.xml吧
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!-- 這裡配置的是新增到ServletContext物件中的資料,和前面ServletConfig不同的是,在一個web專案中
所有的Servlet共享一份ServletContext物件,但是ServletConfig卻是每一個Servlet例項都有獨立的
同樣,這裡配置的引數可以再ServletContext物件中取到和修改-->
<context-param>
<param-name>test</param-name>
<param-value>hello</param-value>
</context-param>
<!-- 宣告一個Servlet,並且與一個實體類之間構成對映-->
<servlet>
<!-- 這是Servlet的name可以自己隨便取-->
<servlet-name>servletDemo</servlet-name>
<!-- 這是這個名字對應的那個class -->
<servlet-class>com.lyh.servlet.ServletDemo</servlet-class>
<!-- 這是為Servlet的init函式中的引數ServletConfig物件裡面新增值,通過這樣配置就可以在ServletCongfig物件中取到了 -->
<init-param>
<param-name>username</param-name>
<param-value>lyh</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>112233</param-value>
</init-param>
<!-- 這條配置設定Servlet隨著伺服器啟動就建立例項,值必須為大於零的整數,如果有多個,就根據值的從小到大排序進行建立例項 -->
<!-- 如果沒有的話,Servlet就在他對應的url第一次被訪問的時候建立 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 這是Servlet和一個url的對映,即對url的訪問會被轉向name等於這個的Servlet來處理 -->
<servlet-mapping>
<!-- 一個已經聲明瞭的Servlet的name -->
<servlet-name>servletDemo</servlet-name>
<!-- 想要轉向此Servlet處理的url -->
<!-- URL格式支援三種格式的URL形式
完全路徑匹配:例如/login
目錄匹配:例如/user/* , /user/*
副檔名匹配:例如*.do,這種情況是不能加/的,比如/*.do就是錯誤的
當上述三種匹配都通過話,匹配的優先順序是完全路S徑>目錄匹配>副檔名匹配
-->
<url-pattern>/ServletDemo</url-pattern>
</servlet-mapping>
<!-- 這裡是配置的第二個Servlet,是一個HttpServlet -->
<servlet>
<servlet-name>HttpServletDemo</servlet-name>
<servlet-class>com.lyh.servlet.HttpServletDemo</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HttpServletDemo</servlet-name>
<url-pattern>/HttpServletDemo</url-pattern>
</servlet-mapping>
</web-app>
這個就是HttpServlet的基本使用,實際web開發中不同的知識doGet()方法要處理的業務邏輯更加複雜罷了,為了方便業務處理等,大牛們封裝了Servlet而形成了框架。
最後根據Serlvet的生命週期我們知道Servlet例項是單例的,而且沒有同步處理,所以是執行緒不安全的。