1. 程式人生 > >Javaweb三大元件之Servlet

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例項是單例的,而且沒有同步處理,所以是執行緒不安全的。