Servlet詳解(一)

什麼是Servlet

Servlet(Server Applet),全稱Java Servlet。一個Java Servlet就是一個小型Java應用程序,它可以繼承HttpServlet實現,運行在Web服務器中。Servlet 會接收並響應來自瀏覽器的請求,通常是基於Http協議的請求。

如下圖:
這裏寫圖片描述

創建Servlet

實現Servlet或者繼承Servlet的實現類:

方法一:實現Servlet接口

1> 實現接口Servlet並實現以下幾個生命週期方法:

  • init(ServletConfig config):當Servlet被創建時,將會使用此方法對自己初始化
  • service(ServletRequest req,ServletResponse res):當瀏覽器對服務器發出請求後,servlet會使用該方法處理請求
  • destory():當Servlet處理完請求後,在銷燬前會調用此方法,然後GC會將它回收掉

生命週期:

當client訪問Servlet時,創建並初始化Servlet,在服務器中只有一個Servlet實例,當服務器被關閉時,Servlet纔會被銷燬。

package com.feathers.servletdemo;
import java.io.IOException;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class HelloServlet implements Servlet {

    private ServletConfig config;

    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("init(): servlet 初始化");
        this.config = config;
    }

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        System.out.println("service():servlet 收到請求了,開始取出請求的數據(request)並作出處理,\n 並將處理後的結果放置到響應對象(response)中");
        // 響應對象向客戶端寫東西
        response.getWriter().write("Hello Client, I'm Servlet");
    }

    @Override
    public void destroy() {
        System.out.println("destory(): servlet被銷燬");
    }


    @Override // 獲取配置信息,鍵值對的方式
    public ServletConfig getServletConfig() { 
        return config;
    }

    @Override  // 返回servlet信息,比如servlet的author version copyright等
    public String getServletInfo() {
        return null;
        // return "Feather著作 Feathers 版權所有";
    }
}

2> 在web.xml 中註冊Servlet,告訴服務器有這麼一個Servlet

<!-- 註冊servlet到服務器 -->
  <servlet>
    <servlet-name>HelloServlet</servlet-name><!-- 註冊名稱 -->
    <servlet-class>com.feathers.servletdemo.HelloServlet</servlet-class><!-- java字節碼路徑 -->
  </servlet>
  <!-- 給註冊過的Servlet配置路徑 -->
  <servlet-mapping>
    <servlet-name>HelloServlet</servlet-name><!-- 給哪一個Servlet配置路徑 -->
    <url-pattern>/HelloServlet</url-pattern><!-- 要配置的路徑,servle訪問路徑  / 代表項目路徑 -->
  </servlet-mapping>

什麼?太麻煩了?還好我們有註解@WebServlet

使用java註解註冊Servlet

@WebServlet("/FirstServlet") // 後面是Servlet的訪問路徑

@WebServlet(  
    name="Hello",   // Servlet名稱
    urlPatterns={"/hello.view"}, // 訪問路徑   
    loadOnStartup=1 // 加載級別
)  
public class HelloServlet extends HttpServlet {  

啓動服務器,訪問此Servlet:

這裏寫圖片描述

在tomcat中的項目結構:
這裏寫圖片描述
我們可以看到,Java編譯的class文件全部被eclipse放置到WEB-INF—> classes目錄下了。

輸出:
這裏寫圖片描述

生命週期時序圖:
這裏寫圖片描述

服務器是如何查找Servlet的?
這裏寫圖片描述

我們看到,通過<url-pattern>訪問servlet,最後訪問到他的class文件。
首先根據<url-pattern>找到它的name,然後根據name找到<servlet> <name>相同的servlet標籤,根據name找到<servlet-class>class路徑,使用找到的class生成servlet實例到服務器內存中。

ServletConfig接口:

servlet容器使用servlet配置對象,該對象在初始化期間將信息傳遞給ServletConfig。

  • String getInitParamter(String name):根據初始化參數名稱獲取初始化參數的值,如果沒有此參數,返回null
  • Enumeration getInitParamterNames():獲取所有初始化參數名稱,以枚舉方式返回,如果沒有參數,返回一個空的枚舉

web.xml中初始化參數:

  <servlet>
    <servlet-name>HelloServlet</servlet-name>
    <servlet-class>com.feathers.servletdemo.HelloServlet</servlet-class>
    <!-- 配置兩個初始化參數 -->
    <init-param>
        <param-name>name</param-name><!-- key -->
        <param-value>Feathers</param-value><!-- value -->
    </init-param>
    <init-param>
        <param-name>age</param-name>
        <param-value>100</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>HelloServlet</servlet-name>
    <url-pattern>/HelloServlet</url-pattern>
  </servlet-mapping>

在Servlet service()中對初始化參數進行處理:

package com.feathers.servletdemo;
import java.io.IOException;
import java.util.Enumeration;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class HelloServlet implements Servlet {

    private ServletConfig config;
    @Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
    }

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        // 響應對象向客戶端寫東西
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write("Hello Client, I'm Servlet");
        // 獲取初始化參數 作出處理
        Enumeration<String> enums = getServletConfig().getInitParameterNames();
        while (enums.hasMoreElements()){
            String key = enums.nextElement();
            String value = getServletConfig().getInitParameter(key);
            response.getWriter().write(key+":"+value + "<br/>");
        }
    }

    @Override
    public void destroy() {
    }


    @Override
    public ServletConfig getServletConfig() {
        return config;
    }

    @Override
    public String getServletInfo() {
        return null;
    }

}

請求結果如下:
這裏寫圖片描述

  • getServletName():獲取Servlet的名稱<servlet-name>
  • getServletContext():獲取ServletContext上下文對象

點擊form表單提交,跳轉servlet

<!-- 注意action, 這個是Servlet的訪問路徑,但是不能加/ 不能加/ 不能加/  -->
<form action="FirstServlet" method="get">
        <table>
            <tr>
                <td>用戶名:</td>
                <td><input type="text" name="username" value=""/></td>
            </tr>
            <tr>
                <td>密碼:</td>
                <td><input type="password" name="password" value=""/></td>
            </tr>
            <tr>
                <td><input type="checkbox" name="remember" value=“yes”/>記住用戶名</td>
            </tr>
            <tr>
                <td><input type="submit" value="提交"/></td>
            </tr>
        </table>
    </form>

方法二、繼承GenericServlet

所謂的GenericServlet只是一個Servlet的實現類,因爲用戶每次使用Servlet接口自定義一個Servlet都會進行許多不必要的操作,所以就封裝了一個GenericServlet以方便程序員使用,

使用方法:在方法service()中進行請求的處理,具體請查看源碼。

下面是它的源碼:

    package javax.servlet;  

    import java.io.IOException;  
    import java.util.Enumeration;  

    //GenericServlet實現了Servlet、ServletConfig、Serializable三個接口
    public abstract class GenericServlet   
        implements Servlet, ServletConfig, java.io.Serializable  
    {  
        private transient ServletConfig config;  

        //無參的構造方法  
        public GenericServlet() { }  

        /* 
        實現接口Servlet接口生命週期初始化init(ServletConfig Config)方法,將ServletConfig對象保存到成員變量中,以擴展他的生命週期,讓service方法也能訪問 
        **/  

        public void init(ServletConfig config) throws ServletException {  
          this.config = config;  
          this.init(); // 用戶的初始化方法
        }  

        public void init() throws ServletException {  
        }  

        public void destroy() { }      


        //返回ServletConfig對象  
        public ServletConfig getServletConfig(){  
           return config;  
        }      

        //該方法實現接口<Servlet>中的ServletInfo,默認返回空字符串  
        public String getServletInfo() {  
           return "";  
        }  


       // service方法沒有去實現,而是丟給使用者去實現         
        public abstract void service(ServletRequest req, ServletResponse res)  
     throws ServletException, IOException;  


      // 爲了方便用戶調用,簡化操作,實現了ServletConfig接口,這樣就不用使用getServletConfig().getServletContext()獲取對象了
      // 只需使用getServletContext即可
      // 即實現ServletConfig的目的
        public ServletContext getServletContext() {  
           return getServletConfig().getServletContext();  
        }  

        public String getInitParameter(String name) {  
         return getServletConfig().getInitParameter(name);  
        }  

        public Enumeration getInitParameterNames() {  
           return getServletConfig().getInitParameterNames();  

        public String getServletName() {  
            return config.getServletName();  
        }  


        public void log(String msg) {  
           getServletContext().log(getServletName() + ": "+ msg);  
        }    


        public void log(String message, Throwable t) {  
           getServletContext().log(getServletName() + ": " + message, t);  
        }  
    }  

方法三、繼承HttpServlet

HttpServlet繼承自GenericServlet,是針對Http協議的Servlet,是進一步的封裝,
使用只需要繼承HttpServlet,重寫doGet或doPost方法即可,doGet用來處理get請求,而doPost方法用來進行post請求處理。

源碼如下:

    package javax.servlet.http;  
    .....  

    public abstract class HttpServlet extends GenericServlet  
        implements java.io.Serializable   
    {  

        private static final String METHOD_GET = "GET";  
        private static final String METHOD_POST = "POST";  
        ......  
        public HttpServlet() { }  

        public void service(ServletRequest req, ServletResponse res)  
            throws ServletException, IOException  
        {  
        // 1. 強轉ServletRequest與ServletResponse
            HttpServletRequest        request;  
            HttpServletResponse        response;  

            try {  
                request = (HttpServletRequest) req;  
                response = (HttpServletResponse) res;   
            } catch (ClassCastException e) {  
                throw new ServletException("non-HTTP request or response");  
            }  
            //調用重載的service()方法  
            service(request, response);  
        }


        //重載的service方法  
        protected void service(HttpServletRequest req, HttpServletResponse resp)  
            throws ServletException, IOException  
        {  
            // 根據操作的不同,作出不同的處理
            // 處理方法有 doGet,doPost,doHead,doDelete...
            // 這些都是http協議中的方法,get請求,post修改,delete刪除,put上傳等。

            // 獲取請求方式
            String method = req.getMethod();  

            if (method.equals(METHOD_GET)) {  // get請求
                long lastModified = getLastModified(req);  
                if (lastModified == -1) {  
                    doGet(req, resp);  
                } else {  
                    long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);  
                    if (ifModifiedSince < (lastModified / 1000 * 1000)){
                        maybeSetLastModified(resp, lastModified);  
                        doGet(req, resp);  
                    } else {  
                        resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);  
                    }  
                }  

            } else if (method.equals(METHOD_HEAD)) {   
                long lastModified = getLastModified(req);  
                maybeSetLastModified(resp, lastModified);  
                doHead(req, resp);  

            } else if (method.equals(METHOD_POST)) {   // post請求
                doPost(req, resp);  

            } else if (method.equals(METHOD_PUT)) {  // put 增加操作
                doPut(req, resp);          

            } else if (method.equals(METHOD_DELETE)) {   // 刪除操作
                doDelete(req, resp);  

            } else if (method.equals(METHOD_OPTIONS)) {  
                doOptions(req,resp);  

            } else if (method.equals(METHOD_TRACE)) {  
                doTrace(req,resp);  

            } else {
                String errMsg = lStrings.getString("http.method_not_implemented");  
                Object[] errArgs = new Object[1];  
                errArgs[0] = method;  
                errMsg = MessageFormat.format(errMsg, errArgs);  

                resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);  
            }  
        }  

        ......

protected void doGet(HttpServletRequest req, HttpServletResponse resp)  
            throws ServletException, IOException  
        {  
            String protocol = req.getProtocol();  
            String msg = lStrings.getString("http.method_get_not_supported");  
            if (protocol.endsWith("1.1")) {  
                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);  
            } else {  
                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);  
            }  
        }  


        protected void doHead(HttpServletRequest req, HttpServletResponse resp)  
            throws ServletException, IOException  
        {  
            .......  
        }    
        protected void doPost(HttpServletRequest req, HttpServletResponse resp)  
            throws ServletException, IOException  
        {  
            String protocol = req.getProtocol();  
            String msg = lStrings.getString("http.method_post_not_supported");  
            if (protocol.endsWith("1.1")) {  
                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);  
            } else {  
                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);  
            }  
        }     
        protected void doPut(HttpServletRequest req, HttpServletResponse resp)  
            throws ServletException, IOException  {  
            //todo  
        }     


        protected void doOptions(HttpServletRequest req, HttpServletResponse resp)  
            throws ServletException, IOException {  
           //todo  
        }  

        protected void doTrace(HttpServletRequest req, HttpServletResponse resp)   
            throws ServletException, IOException   {         
          //todo  
        }     

        protected void doDelete(HttpServletRequest req,  
                                HttpServletResponse resp)  
            throws ServletException, IOException   {  
            //todo  
        }     

    }  

Servlet線程安全問題:

Servlet存在這線程安全的問題,
假設在Servlet中放置一個成員變量name,用來接受用戶的表單信息。當多個用戶訪問Servlet時,會對name進行多次賦值,那麼用戶得到的結果就很有可能出錯,因爲多個用戶共用一塊內存。

解決辦法也很簡單,讓每個用戶都有一塊內存保存自己的name值,只需要將name值保存在方法中,建立一個局部變量即可。因爲java方法在被調用時,每次被調用,都會開闢一個新的方法區。

修改Servlet創建時機

通常情況下,servlet在被用戶第一次調用時創建,如果servlet要進行非常耗時的創建操作,用戶就會等待很久,影響用戶體驗,所以,我們可以將servlet設置在服務器啓動之後,解決這類問題:
web.xml中,添加加載時間標籤:

<servlet>
    <servlet-name>HelloServlet</servlet-name>
    <servlet-class>com.feathers.servletdemo.HelloServlet</servlet-class>
    <!-- 讓servlet隨項目的啓動而啓動 value: 0-5  數字越小,啓動優先級越高       如果多個servlet優先級相同,則按照配置順序啓動-->
    <load-on-startup>3</load-on-startup>
  </servlet>

Servlet訪問路徑配置

相對路徑:
/ 代表 項目路徑
/AServlet => http://localhost:8080/ServletDemo/Aservlet

/File/* => /File/ => http://localhost:8080/ServletDemo/File/sfsdfdsfsdfe 匹配任意

匹配範圍越大,優先級越低:
兩個配置/File/AServlet/File/*
訪問:http://localhost:8080/ServletDemo/File/AServlet
則會訪問AServlet,而不會訪問第二個路徑,因爲第一個優先級高。

後綴名匹配:
*.dohttp://localhost:8080/ServletDemo/sfdsfds.do
該方式不常用,Filter過濾器常用
注意:不能同時使用後綴名匹配和相對路徑。例如:/File/*.do

ServletContext

項目級別的對象,一個Web項目,有且只有一個ServletContext對象,在項目啓動時創建,到項目關閉時銷燬,可以理解爲這個類集合了項目所有的功能方法,代表了項目,所以這個類非常強大。
我們使用ServletConfig.getServletContext()獲得這個對象。

功能如下:

獲取項目參數

配置項目初始化參數:

<webapp>
  <context-param>
    <param-name>name</param-name>
    <param-value>tom</param-value>
  </context-param>
</webapp>

獲取項目初始化參數:
getServletContext().getInitParamter(String name)
getServletContext().getInitParamterNames()

ServletContext 域

域是服務器兩個組件之間的通訊,比如兩個Servlet之間通訊。

Application域

在ServletContext中,有一個Map<String,Object> 集合,用來保存信息,這個Map集合就是ServletContext的域。
AServlet獲取ServletContext對象,向域中添加信息,BServlet也可以獲取Servlet,然後從ServletContext域中獲取AServlet填入的信息。
這樣,每個Servlet之間就可以共享信息了。
同樣存在線程不安全的問題。

/*AServlet*/
// 1. 獲取ServletContext
ServletContex scontext = getServletContext();
// 2. 向域中存放鍵值對
scontext.setAttribute(key,object);

/*BServlet*/
// 3. 在另一個Servlet中從域中取出鍵值對
getServletContext().getAttribute(key);
// 4. 刪除key
getServletContext().removeAttribute(key);
// 5. 獲取所有建
getServletContext().getAttributeNames();//返回枚舉

ServletContext獲取項目內的資源

獲取WebRoot/WebContent下的user.xml文件:

InputStream is = getServletContext().getResourceAsStream("/user.xml"); // 獲得資源流
String path = getServletContext().getRealPath("/user.xml");// 獲取資源絕對路徑(tomcat webapp下 項目的部署路徑)

// 獲取WEB-INFO下的`user.xml`文件
getServletContext().getResourceAsStream("/WEB-INFO/user.xml");

獲取class資源:

// 方法1:使用上面的方法
getServletContext().getResourceAsStream("/WEB-INFO/classes/com/feathers/servlet/AServlet.class"); // 麻煩

// 方法2:
// “/” 代表src目錄,或者是classes目錄,詳見getResourceAsStream方法
InputStrean is2 = AServlet.class.getResourceAsStream("/user.xml");
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章