day05 Servlet 開發和 ServletConfig 與 ServletContext 對象

Author:相忠良
Email: ugoood@163.com
起始於:April 14, 2018
最後更新日期:April 18, 2018css

聲明:本筆記依據傳智播客方立勳老師 Java Web 的授課視頻內容記錄而成,中間加入了本身的理解。本筆記目的是強化本身學習所用。如有疏漏或不當之處,請在評論區指出。謝謝。
涉及的圖片,文檔寫完後,一次性更新。html

day05 Servlet 開發和 ServletConfig 與 ServletContext 對象

1. Servlet 開發入門 - hello world

Servelet 是動態 web 資源開發技術。html 是靜態 web 資源開發技術。Jsp 是另外一種動態web資源開發技術,但 jsp 裏面就是 Servelet。因此應先搞明白 Servelet。java

sun公司提供了一個servlet接口,用戶若想開發一個動態web資源,需完整下面2個步驟:mysql

  1. 編寫一個 java 類,實現 servlet 接口;
  2. 把開發好的 java 類部署到 web 服務器中。

快速入門:用servlet向瀏覽器輸出"hello servlet"。
閱讀 J2EE 的 servlet api 文檔,解決2個問題:程序員

  1. 輸出 "hello servlet" 的 java 代碼應該寫在 servlet 的那個方法內?
  2. 如何向 IE 瀏覽器輸出數據?

查看 api 後,作實驗。
過程以下:
1.在tomcat webapps 中新建 day05 目錄, 而後再web應用中創建WEB-INF/classes目錄;
2.在classes目錄中新建一個FirstServlet.javaweb

package cn.wk;

import java.io.*;
import javax.servlet.*;

// GenericServlet已經覆蓋了Servlet接口中的方法,繼承它,用起來方便
public class FirstServlet extends GenericServlet{
  // 服務器接受客戶請求並給出響應,咱們需重寫 service 方法
    public void service(ServletRequest req,
        ServletResponse res)
    throws ServletException,java.io.IOException{

    // 向瀏覽器輸出的流
        OutputStream out = res.getOutputStream();
    //OutputStream是字節流,故用getBytes()把字符轉字節
        out.write("hello servlet!!!".getBytes());
    }
}

注意到,servlet 程序是跑在 web 服務器中,受 web 服務器調用的。服務器會給 servlet 程序的service方法送來一個客戶發來的 request, 向客戶發 「hello servelt!!!」響應的話,應由servlet程序中service方法中的ServletResponse對象去處理。我猜想OutputStream輸出流由servlet程序發給web服務器,再由web服務器封裝後,輸出給客戶的瀏覽器。sql

3.編譯servlet程序FirstServlet.java。其中需在cmd下將servlet-api.jar添加到classpath中。我機器例子:C:\apache-tomcat-8.5.9\webapps\day05\WEB-INF\classes>set classpath=%classpath%;C:\apache-tomcat-8.5.9\lib\servlet-api.jar
而後編譯:C:\apache-tomcat-8.5.9\webapps\day05\WEB-INF\classes>javac -d . FirstServlet.java數據庫

4.在WEB-INF目錄中新建web.xml文件,並配置servlet的對外訪問路徑,內容抄自C:\apache-tomcat-8.5.9\conf\web.xml文件,並作修改。以下:apache

<?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-name>FirstServlet</servlet-name>
    <!-- 位置 -->
    <servlet-class>cn.wk.FirstServlet</servlet-class>        
  </servlet>

  <!-- 配置servlet的對外訪問路徑 -->
  <servlet-mapping>
    <!-- 名字 -->
    <servlet-name>FirstServlet</servlet-name>
    <!-- 對外路徑 -->
    <url-pattern>/FirstServlet</url-pattern>
  </servlet-mapping>

</web-app>

5.啓動tomcat,開啓瀏覽器,訪問http://localhost:8080/day05/FirstServlet查看結果。windows

2. Servlet 的調用過程和生命週期

1
生命週期:servlet第一次被訪問時被建立一個實例對象,web服務器會調用init方法完成對象初始化,service方法會執行,用來響應客戶端的請求,當web服務器關閉時或者當前web應用被刪掉時,web服務器會調用destroy方法,從web容器中移除該servlet對象。

3. 使用 Eclipse 開發 Servlet

2

步驟:
1.新建一個 Web Project,不直接點 Finish,點下一步勾選上 web.xml;
2.src目錄下新建一個ServletDemo1的類並繼承GenericServlet實現類;
3.把apache-tomcat-8.5.9-src源碼attached到項目中去,而後在ServletDemo1中寫入:

public class ServletDemo1 extends GenericServlet {
    @Override
    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException {
        res.getOutputStream().write("hello servlet!!!!!".getBytes());
    }
}

4.這個servlet程序外界沒法訪問,需用web.xml配置對外訪問路徑,添加以下內容:
注意:要想得到這種類名cn.wk.ServletDemo1,需將ServletDemo1.java點開,而後右鍵點擊類名,再點 copy qualified name 按鈕獲取類全名!

<servlet>
    <servlet-name>ServletDemo1</servlet-name>
    <servlet-class>cn.wk.ServletDemo1</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>ServletDemo1</servlet-name>
    <url-pattern>/ServletDemo1</url-pattern>
</servlet-mapping>

5.想發佈這個web應用,需先爲Eclipse集成一個tomcat服務器(我用的是 tomcat 8),以下:
windows->preference->myeclipse->servers->tomcat->tomcat 8.x
而後選擇 tomcat 8 所在目錄,再點 Enable,其餘默認,點肯定便可。

6.點擊屏幕上方的 Deploy MyEclipse J2EE Project to Server 按鈕,把本工程部署到服務器;
7.點擊屏幕上方的啓動服務器按鈕(選擇tomcat 8.x);
8.瀏覽器中輸入http://localhost:8080/day05/ServletDemo1查看結果。

實際開發中要想建 servlet 程序,直接右鍵 new 一個 servlet 便可,Eclipse 直接在 web.xml 中把<servlet>元素和<servlet-mapping>元素寫好了!

4. HttpServlet 和一些開發細節

3

寫sevlet程序繼承HttpServlet就好了,再根據想處理的請求複寫doGet()或者doPost()等方法就能夠了,沒必要再複寫service()方法了,讀api看下HttpServlet就會徹底明白了!

實驗,在day05項目中cn.wk包下直接創建名爲ServletDemo2的servlet程序,以下:

public class ServletDemo2 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.getOutputStream().write("Hello, HttpServlet!!!!".getBytes());
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

web.xml中對於該資源的外部訪問路徑就不用配了,Eclipse 已經給咱配好了。
瀏覽器中輸入http://localhost:8080/day05/servlet/ServletDemo2查看結果。

注意:servlet程序重命名後(按F2重命名,而不能在程序中改!),相關引用該類的程序也自動修改,但web.xml中的名字沒改,得手動修改!!!方立勳老師特地強調:寫servlet程序要當心,寫錯名字的話,web.xml中的內容有不少地方需修改,工程很大的話,就是災難了!

可在MyEclipse目錄下查找Servlet.java修改模板,使得doGet和doPost方法變清爽,但我沒成功。

5. Servlet 開發的一些重要細節

拷貝別人的工程後,可能涉及到修改web訪問路徑。方法:Eclipse中右擊點屬性->MyEclipse->找Web->修改Web Context-root

配置servlet程序的訪問地址URL:
4

一個servlet程序能夠有多個URL,還涉及到通配符:
5

例子,修改day05工程下的 web.xml 以下:

<!-- servlet註冊 -->
    <servlet>
        <servlet-name>ServletDemo1</servlet-name>
        <servlet-class>cn.wk.ServletDemo1</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>ServletDemo2</servlet-name>
        <servlet-class>cn.wk.ServletDemo2</servlet-class>
    </servlet>

    <!-- servlet URL 映射 -->
    <servlet-mapping>
        <servlet-name>ServletDemo1</servlet-name>
        <url-pattern>/ServletDemo1</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>ServletDemo2</servlet-name>
        <url-pattern>/servlet/ServletDemo2</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>ServletDemo2</servlet-name>
        <url-pattern>/1.html</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>ServletDemo2</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>ServletDemo2</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

多個通配符表達的url,涉及到具體匹配哪個servlet程序的問題(瞭解):
6

Servlet 引擎: Web 服務器中用來調 servlet 程序的那個程序。 Servlet 程序不能單獨運行。

servlet程序的init()和destroy():
7

實驗,新建立了一個ServletDemo3.java的 servlet 程序,並覆蓋了init()destroy()。結果顯示客戶 第一次訪問 該servlet程序時,web容器執行init(),當 服務器關閉或該servlet程序從web容器被移除 時,web容器執行該servlet程序的destroy()

public class ServletDemo3 extends HttpServlet {

    @Override
    public void init() throws ServletException {
        super.init();
        System.out.println("init");
    }

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.getOutputStream().write("Hello ServletDemo3!!!".getBytes());
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

    @Override
    public void destroy() {
        super.destroy();
        System.out.println("destroy");
    }
}

ServletDemo3設置啓動服務器就建立對象並執行init()方法。在 web.xml的<servlet>元素裏設置<load-on-startup>元素,並設置其內容爲1,以下:

<servlet>
    <servlet-name>ServletDemo3</servlet-name>
    <servlet-class>cn.wk.ServletDemo3</servlet-class>

    <load-on-startup>1</load-on-startup>
</servlet>

<load-on-startup>1</load-on-startup>裏的1表示優先級,越小優先級越高,但必須是正整數。
該技術用在隨服務器的啓動,啓動某些框架上。

缺省的 Servlet 程序

<servlet-mapping>
  <servlet-name>ServletDemo3</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

注意上面代碼<url-pattern>/</url-pattern>中只有/,意味着其餘servlet都不支持的 url 由這個缺省的ServletDemo3 servlet程序來處理!即 處理別人都不處理的請求

注意:即便咱們沒配置該web應用的缺省servlet程序,web容器也會爲咱們弄一個缺省的servlet程序。若是咱們配置了,服務器爲咱們準備的缺省servlet程序會被覆蓋。程序員本身不要把某個servlet弄成缺省的,由於html文件將沒法訪問。如:http://localhost:8080/day05/1.html將訪問本身設置的缺省servlet程序。

6. Servlet的線程安全

6.1 Servlet的線程安全的產生及同步鎖解決方案(然並卵方案)

靜態成員變量要慎用,可能致使 線程安全問題內存溢出,如:

public class Person{
  public static List<String> list = new ArrayList<String>();
}

若 Person 類隨服務器啓動而加載,另外一個 Demo 類訪問 Person 類,當有不少用戶用 Demo 類代碼訪問 Person 類時均向list添加數據,靜態成員list會無限變大,最終可能致使內存溢出,Demo 代碼以下:

public class Demo{
  public static void main(String[] args) {
    Person p = new Person();
    p.list.add("aaa");
  }
}

下面代碼有線程安全問題:

// 線程安全問題
public class ServletDemo4 extends HttpServlet {
    int i = 0;

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        try {
            Thread.sleep(1000 * 3);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        i++;
        response.getOutputStream().write((i + "").getBytes());
    }
}

開兩個瀏覽器窗口,同時訪問http://localhost:8080/day05/servlet/ServletDemo4會出線程安全問題。
加上同步鎖,解決了線程安全問題,代碼以下:

// 線程安全問題
public class ServletDemo4 extends HttpServlet {
    int i = 0;

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        response.getOutputStream().write((i + "").getBytes());
        i++;

        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        response.getOutputStream().write((i + "").getBytes());
    }
}

加同步鎖解決線程安全問題:

// 加同步鎖
public class ServletDemo4 extends HttpServlet {
    int i = 0;

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        synchronized (this) {
            response.getOutputStream().write((i + "").getBytes());
            i++;
            try {
                Thread.sleep(1000 * 5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            response.getOutputStream().write((i + "").getBytes());
        }
    }
}

以上線程安全問題的解決方案是不行的,最後一個用戶訪問這個頁面等1年?

6.2 Servlet的線程安全可行解決方案(結論:還得用 6.1 的解決方案)

接口中沒有任何定義,這種接口是標記接口,如:SerializableCloneable以及本節所涉及的SingleThreadModel
但這試驗我沒作出來,達不到開2個ie窗口,都顯示結果爲1的效果,代碼以下:

public class ServletDemo4 extends HttpServlet implements SingleThreadModel {
    int i = 0;

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        i++;
        try {
            Thread.sleep(1000 * 8);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        response.getOutputStream().write((i + "").getBytes());
    }
}

Java中的諺語:子類在覆蓋父類的方法時,不能拋出比父類更多的異常。想法是:子類比父類強。

7. ServletConfig 對象 - 用於封裝 servlet 的配置信息

Web 服務器會給 Servlet 傳各類對象,咱們編寫的 Servlet 程序接收這些對象後,解析它,再搞對應的操做,如圖:
8

認識 ServletConfig 對象:
9

問題:爲何要在 web.xml 這個配置文件裏添加 servlet 所需的數據,而不是在 servlet 程序裏寫入數據?
實際開發中,有一些東西不適合在 servlet 程序中寫死,這類數據就能夠經過 配置的方式 配給 servlet 程序,例如:

  • servlet 採用哪一個碼錶;
  • servlet 鏈接哪一個數據庫;
  • servlet 用哪一個配置文件(用 Struts 舉的例子)。

舉實際的例子,ServletDemo5以下:

// ServletConfig 對象:用於封裝 servlet 的配置信息
public class ServletDemo5 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 獲得指定的
        String value = this.getServletConfig().getInitParameter("config");
        System.out.println(value);

        // 獲得全部的
        Enumeration e = this.getServletConfig().getInitParameterNames();
        while (e.hasMoreElements()) {
            String name = (String) e.nextElement();
            String value_ = this.getServletConfig().getInitParameter(name);
            System.out.println(name + " = " + value_);
        }
    }
}

修改 web.xml,想該 servlet 註冊中添加配置數據,以下:

<servlet>
        <servlet-name>ServletDemo5</servlet-name>
        <servlet-class>cn.wk.ServletDemo5</servlet-class>

        <!-- 配置 ServletConfig -->
        <init-param>
            <param-name>charset</param-name>
            <param-value>UTF-8</param-value>
        </init-param>

        <init-param>
            <!-- 配置數據庫鏈接地址 -->
            <param-name>url</param-name>
            <param-value>jdbc:mysql://localhost:3306/test</param-value>
        </init-param>

        <init-param>
            <param-name>username</param-name>
            <param-value>root</param-value>
        </init-param>

        <init-param>
            <param-name>password</param-name>
            <param-value>root</param-value>
        </init-param>

        <init-param>
            <!-- 本 servlet 讀哪一個配置文件 -->
            <param-name>config</param-name>
            <param-value>/struts-config.xml</param-value>
        </init-param>

</servlet>

瀏覽器中輸入http://localhost:8080/day05/servlet/ServletDemo5,則控制檯中輸出結果爲:

config = /struts-config.xml
username = root
charset = UTF-8
config = /struts-config.xml
password = root
url = jdbc:mysql://localhost:3306/test

8. ServletContext 對象(爲整個 web 應用而生)

產生:web 服務器有多少 web 應用,服務器就建立多少個 ServletContext 容器!
銷燬:停服務器或者刪除了某 web 應用,對應的 Context 容器們或容器會被銷燬。

8.1 獲取 ServletContext 對象

ServletContext 用來操做整個該 servlet 程序所在的 web 應用全局性資源的。
10

獲取 ServletContext 對象:

// ServletContext 示例
public class ServletDemo6 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 獲取 ServletContext 對象
        ServletContext context = this.getServletContext();
    }
}

8.2 ServletContext 域

11

ServletContext 域,表達瞭如下幾點意思:

  1. 這是一個容器;
  2. ServletContext 域表明這個容器的做用範圍時 整個特定的 web 應用,如day05這個應用

下面的例子,實現了 day05 這個 web 應用範圍內的,ServletDemo7ServletDemo8這2個servlet程序的數據共享

public class ServletDemo7 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        String data = "aaa";

        // 向 Context 容器內添加 全web應用域 都能共享的數據
        this.getServletContext().setAttribute("data", data);
    }
}
public class ServletDemo8 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 獲取 ServletContext 對象
        ServletContext context = this.getServletContext();
        // 獲取 Context 容器內的數據
        String value = (String) context.getAttribute("data");
        // 向瀏覽器輸出
        response.getOutputStream().write(value.getBytes());
    }
}

瀏覽器中輸入http://localhost:8080/day05/servlet/ServletDemo7,再開一個小窗,地址欄中輸入http://localhost:8080/day05/servlet/ServletDemo8,在當前的這個瀏覽器中會出現ServletDemo7輸出到ServletContext域中的共享數據aaa

咱們能夠在 web.xml 文件中寫<context-param> 爲整個 web 應用作配置,以下:

<!-- 爲當前整個web應用作的配置 -->
<!-- 全部servlet均可經過ServletContext容器訪問下面的數據 -->
<context-param>
  <param-name>data</param-name>
  <param-value>xxxx</param-value>
</context-param>

而後建一個servlet程序,輸出Context容器中內容便可,String value = this.getServletContext.getAttribute("data"),輸出這個value便可。

問題:什麼狀況下,需爲整個 web 應用配置初始化參數?
上百個 servlet 都需連數據庫的話,難道上百個servlet配置都用ServletConfig來配?這時就得用這個 表明全局的ServletContext容器來配置

8.3 ServletContext 域 - Servlet 轉發技術

Servlet的轉發和重定向
轉發:你向我借錢,我沒有,我幫你去找他;
重定向:你向我借錢,我沒有,你本身去找他!

Servlet 只適合產生數據,不適合美化輸出,輸出得靠html和css。這時就用到轉發,既servlet產生的數據轉發到 jsp,由 jsp 負責輸出數據,這時將大量使用到轉發。

例子,ServletDemo10產生的數據轉發到1.jsp

// 經過 ServletContext 實現請求轉發
public class ServletDemo10 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        String data = "aaaaaaaaa";

        // 把數據帶給1.jsp(只是演示,正常的話不能用context域,要經過request域)
        this.getServletContext().setAttribute("data", data);

        RequestDispatcher rd = this.getServletContext().getRequestDispatcher("/1.jsp");
        rd.forward(request, response);
    }
}
<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%>
<%
    String path = request.getContextPath();
    String basePath = request.getScheme() + "://"
            + request.getServerName() + ":" + request.getServerPort()
            + path + "/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">

<title>My JSP '1.jsp' starting page</title>

<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
    <link rel="stylesheet" type="text/css" href="styles.css">
    -->

</head>

<body>
    <!-- 這裏開始寫代碼 -->
    <h1>
        <font color="red">
        <%
            String data = (String)application.getAttribute("data");
            out.write(data);
        %>
        </font>
    </h1>
    <!-- 結束 -->
</body>
</html>

打開瀏覽器輸入http://localhost:8080/day05/servlet/ServletDemo10查看結果。

8.4 ServletContext 域 - 讀取 Web 應用資源文件 - .properties 屬性文件

通常無論理servlet程序,管理的是 web 應用的其餘資源文件。

實際應用場景:
衆多servlet程序要訪問數據庫,則通常來講,數據庫相關信息要用資源文件保存。
有兩種文件用來保存數據庫配置信息:
.properties文件(信息無關聯用這個)和.xml文件(信息有關聯用這個)
鏈接數據庫的這種信息,認爲是平行信息(無關聯信息),故應用.properties文件保存。

試驗,涉及2文件,ServletDemo11db.properties(該文件在/src目錄下):

db.properties內容以下:

url=jdbc:mysql://localhost:3306/test
username=root
password=root

ServletDemo11內容以下:
注意:
讀取.properties文件是重點,且不要用FileInputStream對象讀,由於它默認的相對路徑起始處爲 java 虛擬機所在的位置。

注意觀察路徑:/WEB-INF/classes/db.properties
ServletDemo11執行時,工程裏的src/db.properties文件早已部署到/WEB-INF/classes/db.properties了,因此應寫這個路徑!

// 經過 ServletContext 讀取資源文件 方法1
public class ServletDemo11 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // FileInputStream 這麼用是不行滴
        // FileInputStream in = new
        // FileInputStream("/WEB-INF/classes/db.properties");
        // 此處最好不要用 FileInputStream 對象,需好用 ServletContext 去讀
        InputStream in = this.getServletContext().getResourceAsStream(
                "/WEB-INF/classes/db.properties");

        Properties props = new Properties(); // map
        props.load(in);

        String url = props.getProperty("url");
        String username = props.getProperty("username");
        String password = props.getProperty("password");

        System.out.println(url);
        System.out.println(username);
        System.out.println(password);
    }
}

瀏覽器輸入http://localhost:8080/day05/servlet/ServletDemo11,則控制檯輸出結果爲:

jdbc:mysql://localhost:3306/test
root
root

也能夠這樣讀取:

// 經過 ServletContext 讀取資源文件 方法2
public class ServletDemo11 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 獲取資源絕對路徑後,可用 FileInputStream
        String path = this.getServletContext().getRealPath(
                "/WEB-INF/classes/db.properties");
        // 獲取文件名
        String filename = path.substring(path.lastIndexOf("\\") + 1);
        System.out.println("當前讀取到的資源名稱是: " + filename);

        FileInputStream in = new FileInputStream(path);
        Properties props = new Properties(); // map
        props.load(in);

        String url = props.getProperty("url");
        String username = props.getProperty("username");
        String password = props.getProperty("password");

        System.out.println("當前讀取到的資源數據是:");
        System.out.println(url);
        System.out.println(username);
        System.out.println(password);
    }
}

9. Web 應用中普通 Java 程序如何讀取資源文件? - 經過類裝載器

故事:若 servlet 程序想訪問數據庫,正常狀況應經過 dao 層訪問,而不是直接訪問。而 dao 層的類基本上是普通 java 程序,而不是 servlet 程序。問題是:普通的 java 程序,如何訪問當前 web 應用中的某個 web 資源(如:想訪問 src/db.properties)?

這就要經過類裝載器來完成。
類裝載器:當前web應用有一個類裝載器,裝載了全部當前web應用下的類的字節碼文件。可經過該web應用下的任意java程序,得到這個類裝載器。經過這個類裝載器,找到要訪問的資源,得到對應的輸入流對象!

試驗,涉及3個文件:要訪問的資源src/db.properties(與上節內容相同),ServletDemo12.java和一個普通 java 程序UserDao.java

ServletDemo12.java以下:

// servlet調用其餘普通java程序,這個普通java程序需經過 類加載器 讀取web資源文件
public class ServletDemo12 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        UserDao dao = new UserDao();
        dao.update();
    }
}

UserDao.java位於cn.wk.dao包裏,涉及到靜態代碼塊和類加載器,代碼以下:

package cn.wk.dao;

import java.io.InputStream;
import java.util.Properties;

public class UserDao {

    private static Properties dbconfig = new Properties();
    // 類加載時 就得到資源
    static {
        try {
            // 類加載器
            InputStream in = UserDao.class.getClassLoader()
                    .getResourceAsStream("db.properties");

            dbconfig.load(in);

        } catch (Exception e) {
            // 認爲訪問不到資源,是重大錯誤,而不是異常,故拋出錯誤
            throw new ExceptionInInitializerError(e);
        }
    }

    public void update() {
        System.out.println(dbconfig.getProperty("url")); // map
    }
}

瀏覽器輸入http://localhost:8080/day05/servlet/ServletDemo12,而後在控制檯查看結果。

9.1 經過類加載器獲取更新後的資源

故事:服務器開啓後,當有人修改了db.properties資源文件後,再經過類裝載器直接得到資源時,沒法得到最新的,緣由是類裝載器裏的字節碼文件隨服務器啓動後只加載1次。以下代碼:

public class UserDao {
    public void update() throws IOException {

        // 如下代碼雖然能夠讀取資源文件的數據,但沒法獲取更新後的數據
        Properties dbconfig = new Properties();
        InputStream in = UserDao.class.getClassLoader().getResourceAsStream(
                "db.properties");
        dbconfig.load(in);
        System.out.println(dbconfig.getProperty("url")); // map
    }
}

正確姿式:

public class UserDao {
    public void update() throws IOException {

        // 經過類裝載的方式獲得資源文件的位置,再經過傳統方式讀取資源文件的數據
        // 這樣能夠讀取資源更新後的數據
        String path = UserDao.class.getClassLoader()
                .getResource("db.properties").getPath();
        FileInputStream in = new FileInputStream(path);
        Properties dbconfig = new Properties();
        dbconfig.load(in);
        System.out.println(dbconfig.getProperty("url")); // map
    }
}
相關文章
相關標籤/搜索