Java高新技術—類的加載和初始化

如要轉載請標明作者zjrodger和出處:http://blog.csdn.net/zjrodger/,謝謝微笑

                                                                      筆記目錄

(·)類的加載和初始化的過程

    1.類的加載。

    2.類的連接。

    3.類的初始化。

(·)深入學習類加載器專題

    1. 類的加載器的作用和類別

    2. 類的加載器的委託機制

(·) 使用加載器讀取資源文件重要

    1.爲什麼要使用類加載器來讀取資源配置文件(如dbinfo.properties)? 
    2. 如何使用類加載器讀取資源文件。
        2.1)方式一類加載器去加載資源文件
        2.2)方式二:用類本身提供的方法(而不是類加載器)去讀取資源文件。



(·)類的加載和初始化的過程

    當程序第一次主動使用某個類時,如果該類還沒有被加載到內存中,系統會通過“加載”“連接”“初始化”,這三個步驟來對該類實現初始化,如果沒有意外,JVM會連續完成這三個步驟,所以有時也把這三個步驟統稱爲“類加載”和“類初始化”。

1.類的加載

(1) 類加載的作用

    將類的“.class”文件從硬盤讀入到內存中,併爲該類創建一個java.lang.Class對象類的加載要通過JVM提供的“類加載器”來完成。當類被加載後,系統爲之生成一個相應的Class對象,接着將會進入到連接階段。

2.類的連接

    當類被加載後,系統爲之生成一個對應的class對象,接着將會進入連接階段。在連接階段,JVM負責把類的二進制數據合併到JRE中。

(1)連接的作用:負責把類的二進制數據合併到JRE中。

(2)類連接又可以分爲三個階段:

    驗證:驗證階段用於檢驗被加載的類是否有正確的內部結構,並且和其他的類是否協調一致。

    準備:準備階段則負責爲類的靜屬性分配內存,並設置默認初始值。

    解析:解析階段將類的二進制數據中的“符號引用”替換成“直接引用”。

3.類的初始化

(1)在此階段,JVM負責對類進行初始化,主要是對靜態屬性進行初始化

(2)Java類中,對靜態屬性指定初始值有兩種方式:

    聲明靜態屬性時指定初始值。

    使用靜態初始化塊對靜態屬性指定初始值。



(·)深入學習類加載器專題

1. 類的加載器的作用和類別

(1)類加載器的作用:負責將“.class文件”(可能在硬盤上,也可能在網絡上)加載到內存中,併爲之生成對應的java.lang.Class對象。

(2)類加載器由JVM提供,JVM提供的一些類加載器通常被稱爲系統類加載器。除此之外,

(3)系統默認的有哪幾個類加載器?

    JVM啓動時,會形成由三個類加載器組成的初始類加載器層次結構,每個類加載器負責加載不同位置的類。

    A.BootStrap該類是根類加載器,不是java,是用C++編寫的,我們在Java中看不到它,null。它是嵌套在虛擬機內核中的,它負責加載java中的核心類。BootStrap類加載負責加載jre/lib/rt.jar中的類,我們平時用的jdk中的類都位於rt.jar中。

    B.ExtClassLoader:用來加載擴展類。

    C.AppClassLoader:也稱作System ClassLoader,是加載Classpath路徑的,它負責在JVM啓動時,加載來自java命令的-classpath選項、java.class.path系統屬性,或者CLASSPATH環境變量所指定的JAR包和類路徑。

        程序可以通過ClassLoader的靜態方法getSystemClassLoader()來獲取系統類加載器。

(4)類加載器之間的父子關係和管轄範圍圖



(5)示例程序

①示例程序01

程序功能

    下程序展示了BootStrap類加載器加載了哪些核心類庫

//下程序展示了BootStrap類加載器加載了哪些核心類庫
package zjrodger;
import java.net.*;
import sun.misc.*;
public class BootStrapClassLoaderTest{
    public static void main(String[] args) throws ClassNotFoundException {
        URL[] urls =Launcher.getBootstrapClassPath().getURLs();
        for(int i=0; i<urls.length; i++){
            System.out.println(urls[i].toExternalForm());
        }    }    }
【結果演示】

file:/C:/Program%20Files/Java/jdk1.7.0_15/jre/lib/resources.jar

file:/C:/Program%20Files/Java/jdk1.7.0_15/jre/lib/rt.jar

file:/C:/Program%20Files/Java/jdk1.7.0_15/jre/lib/sunrsasign.jar

file:/C:/Program%20Files/Java/jdk1.7.0_15/jre/lib/jsse.jar

file:/C:/Program%20Files/Java/jdk1.7.0_15/jre/lib/jce.jar

file:/C:/Program%20Files/Java/jdk1.7.0_15/jre/lib/charsets.jar

file:/C:/Program%20Files/Java/jdk1.7.0_15/jre/lib/jfr.jar

file:/C:/Program%20Files/Java/jdk1.7.0_15/jre/classes



②示例程序02

程序功能

    演示類加載器的樹狀結構的代碼。

【示例代碼】

package com.zjrodger;
/**
 * 程序功能: 演示類加載器的樹狀結構。
 * 最頂層的是BootStrap,其次是ExtClassLoader,對底層的是AppClassLoader
 * **/
public class ClassLoaderTest {
   
    public static void main(String[] args) {
       ClassLoader loader = ClassLoaderTest.class.getClassLoader();
       while(loader != null){
           System.out.println("類加載器的名稱: "+loader.getClass().getName());
           loader = loader.getParent();
       }
       if(loader == null){
           System.out.println("最頂層的類加載器:BootStrap。");
       }                
    }
}
【結果演示】

類加載器的名稱: sun.misc.Launcher$AppClassLoader
類加載器的名稱: sun.misc.Launcher$ExtClassLoader
最頂層的類加載器:BootStrap。

2. 類的加載器的委託機制

(1)當Java虛擬機要加載一個類時,到底要派出哪個類加載器去加載呢?

首先,當前線程的類加載器去加載線程中的第一個類。

如果類A中引用了類B,Java虛擬機將使用加載類A的類加載器來加載類B。

還可以直接調用ClassLoader.loadClass()方法來指定某個類加載器去加載某個類。

(2)每個類加載器加載類時,又先委託給其上級類加載器。

    當所有祖宗類加載器沒有加載到類,回到發起者類加載器,還加載不了,則會拋出異常ClassNotFoundException,而不是再去

找發起者類加載器的兒子,因爲沒有getChild()方法,即使有,那有多少個兒子,找哪一個呢?

(3)類加載器的委託機制

    ①全盤負責所謂全陪負責,就是當一個類加載器負責加載某個Class時,該Class所依賴的和引用的其他Class也將由該類加載器負責載入,除非顯示使用另一個類加載器來載入。 

    ②父類委託所謂父類委託就是先讓父類加載器試圖加載該Class,只有在父類加載器無法加載該類時才嘗試從自己的類路徑中加載該類。   

    ③緩存機制緩存機制保證所有被加載過的Class都會被緩存。當程序中需要使用某個Class時,類加載器先從緩存中搜索該Class,只有當緩存中不存在該Class對象時,系統纔會重新讀取該類所對應的二進制數據,並將其轉化成Class對象,並且存入Cache。這就是爲什麼當修改了Class後,程序必須重新啓動JVM,程序所做的修改才能生效。

(4)委託機制的好處:

    不會導致多分字節碼出現的情況。每次加載一個類時,會先去調用父類,判斷是否已經加載過該類,若沒有,則進行加載操作;若父類已經加載過了,則直接調用已經存在的類的字節碼,這樣就不會導致多分字節碼出現的情況。

    一道面試題:“能否自己寫一個類加載器來加載java.lang.System”類?

    答案:不能,因爲,根據Java類加載器的委託機制,System類會由BootStrap類加載器加載。



(·) 使用加載器讀取資源文件重要

1.爲什麼要使用類加載器來讀取資源配置文件(如dbinfo.properties)? 
答:類加載器可以將編譯好的Class文件從磁盤上加載到內存中,利用此特性,類加載器也可以將普通的文件(或者是配置文件)從磁盤上加載到內存中。因此,利用類加載器來加載資源文件,是最常用的做法。

2. 如何使用類加載器讀取資源文件。
2.1)方式一類加載器去加載資源文件
(1) 參考代碼:XXX.class.getClassLoader().getResourceAsStream("dbinfo.properties");
(2) 思路:獲得Class對象-------->得到ClassLoader-------->getResourceAsStream-------->得到資源的InputStream。
(3) 資源路徑名的書寫(重要
    用類加載器去讀取資源文件時,類加載器默認的主目錄是src
    ① 若資源文件位於src主目錄下邊,且沒有位於src目錄下任何的包中,則讀取資源文件的代碼爲:
InputStream ips =XXXClass.class.getClassLoader().getResourceAsStream("dbinfo.properties");
//其中,dbinfo.properties爲資源文件的名字。
    ② 若資源文件位於src主目錄下邊的某個包中,比如"com/zjrodger/dao"中,則讀取資源文件的代碼爲:

InputStream ips =XXXClass.class.getClassLoader().getResourceAsStream("com/zjrodger/dao/dbinfo.properties");
//注意:“com之前不能加/”


(4) 普通的Java項目中的FileInputStream對象並不能讀取src下邊的配置文件,該對象讀取的一般位置是在"%CATALINA_HOME%/bin目錄"下的。而用類加載器去讀取資源文件時,類加載器默認的主目錄是src。因此,對於Java Web項目,使用類加載器可以實現更方便的讀取資源配置文件。
代碼示例:
InputStream ips =XXXClass.class.getClassLoader().getResourceAsStream("dbinfo.properties");

(5) 應用舉例

【參考代碼】

package com.zjrodger.dao;

import java.io.*;
import java.sql.*;
import java.util.Properties;

public class SqlHelper {
	
	//定義需要的變量
	private static Connection conn = null;
	//大多數情況下,我們使用的是PreparedStatement來代替Statement,這樣可以防止SQL注入。
	private static PreparedStatement pstmt = null;
	private static ResultSet rs = null;
	private static CallableStatement cs = null;
	
	//連接數據庫的初始化參數。
	private static String url = "jdbc:mysql://localhost:3306/dbusermanagesys";
	private static String user = "root";
	private static String driverClass = "com.mysql.jdbc.Driver";
	private static String password = "zjrodger";
	
	//Properties的作用:讀取配置文件。
	private static Properties pp = null;
	//當我們做普通的Java項目時,可以使用FileInputStream類來讀取配置文件。
//	private static FileInputStream fis = null;
	
	//當我們做Java Web項目時,爲了能夠成功地讀取配置文件,就要使用"類加載器"中的方法。
	//而該方法返回的是一個InputStream類。
	private static InputStream inputStream = null;

	
	//在靜態初始化塊兒中加載JDBC驅動。
	//爲什麼將JDBC驅動加載放在靜態初始化塊兒中—————只需一次,不用反覆加載。
	static{
		try {					
			//從dbinfo.properties文件中讀取driverClass, url, user和password等相關信息。
			pp = new Properties();
			
			//當我們做普通的Java項目時,可以使用FileInputStream類來讀取配置文件。
			//FileInputStream並不能讀取src下邊的配置文件,其讀取位置是在"%CATALINA_HOME%/bin目錄"下的。
//			inputStream = new FileInputStream("dbinfo.properties"); //===>tomcat的主目錄
			
			/**
			 *  1.當我們做Java Web項目時,爲了能夠成功地讀取"dbinfo.properties"配置文件,就要使用"類加載器"。
			 *    可以使用本類SqlHelper的類加載器。
			 *    
			 *  2.爲什麼要使用類加載器來讀取資源配置文件(如dbinfo.properties)?(重點)
			 *    答:對於Java Web項目,使用類加載器可以實現更方便的讀取資源配置文件。
			 *    (1) 如果資源配置文件(如dbinfo.properties)的存放位置是位於src目錄下的,
			 *        普通的Java項目中的FileInputStream並不能讀取src下邊的配置文件,其讀取位置是在"%CATALINA_HOME%/bin目錄"下的。
			 *        而用類加載器去讀取資源時,默認的主目錄是src。
			 *        如代碼:
			 *        SqlHelper.inputStream =SqlHelper.class.getClassLoader().getResourceAsStream("dbinfo.properties");
			 *    
			 *    (2) 若資源配置文件(如dbinfo.properties)的存放位置是位於arc目錄下某個包中(com.zjrodger.dao),
			 *        則使用類加載器來讀取該資源文件的代碼:
			 *        SqlHelper.inputStream =SqlHelper.class.getClassLoader().getResourceAsStream("com/zjrodger/dao/dbinfo.properties");
			 *    				     
			 * **/			
			SqlHelper.inputStream =SqlHelper.class.getClassLoader().getResourceAsStream("com/zjrodger/dao/dbinfo.properties");
						
//			pp.load(inputStream);
			pp.load(SqlHelper.inputStream);
			
			SqlHelper.url = pp.getProperty("url");
			SqlHelper.user = pp.getProperty("user");
			SqlHelper.driverClass = pp.getProperty("driverClass");
			SqlHelper.password = pp.getProperty("password");
			
			Class.forName(SqlHelper.driverClass);						
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("在SqlHelper類中的靜態初始化塊兒中,數據庫驅動加載失敗!!");
		} finally{
			try {
				inputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			inputStream = null;
		}
	}	
	
	//獲取數據庫連接
	private static Connection getConnection(){
		try {
			SqlHelper.conn = DriverManager.getConnection(url, user, password);
		} catch (SQLException e) {
			e.printStackTrace();
			System.out.println("在SqlHelper類中的getConnection()方法中,數據庫連接獲取失敗。");
			return null;
		}
		return SqlHelper.conn;
	}
	
	//對外,使得用戶獲得數據庫的Connection鏈接。
	public static Connection getConn(){
		if(SqlHelper.conn == null){
			return SqlHelper.getConnection();
		}else{
			return SqlHelper.conn;
		}
	}
	
	
	//執行查詢
	//統一的select,僅僅是查詢
	// ResultSet --->  ArrayList
	public static ResultSet executeQuerySqlHelper(String sqlStr, String[] arguments){
		
		if((SqlHelper.conn = SqlHelper.getConnection()) != null){
			try {
				SqlHelper.pstmt = SqlHelper.conn.prepareStatement(sqlStr);
				if(arguments != null){
					for(int index=0; index < arguments.length; index++){
						SqlHelper.pstmt.setString((index+1), arguments[index]);
					}					
				}

				if((SqlHelper.rs = SqlHelper.pstmt.executeQuery()) != null){
					return SqlHelper.rs;
				}else{
					return null;
				}
				
			} catch (SQLException e) {
				e.printStackTrace();
				System.out.println("在SqlHelper類中的executeQuery2()方法中,該方法發生異常。");
			} finally{
				// 此處的數據庫資源不能關閉。
				// 1.因爲若關閉ResutlSet對象,就會導致executeQuerySqlHelper()函數不能返回結果,
				// 從而使得調用該函數的方法出現異常。
				// 2.若關閉Connection對象,即使用戶不手動關閉ResultSet資源,
				// 當Connection資源關閉時,位於Connection資源中的ResultSet資源也會自動被關閉。
			}			
		}
		return null;
	}
	
	
	//執行多個 DML(insert/delete/update)語句,需要"考慮事務"
	public static void executeUpdateSqlHelper(String[] sqlArray, String[][] arguments){
		
	}
	
	//執行多個 DML(insert/delete/update)語句,不需要"考慮事務"
	public static void executeUpdate3(String[] sqlArray, String[][] arguments){
		
	}
	
	//分頁問題
	public static ResultSet executeQuery(){
		return null;
	}
	
	//調用存儲過程,返回ResultSet
	public static CallableStatement callStoredProcedure(
			String sql,
			String[] inParameters,
			Integer[] outParameters){
		try {
			SqlHelper.conn = SqlHelper.getConnection();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	//關閉數據庫佔用的資源
	public static void closeDBResources(){
		if(SqlHelper.rs != null){
			try {
				SqlHelper.rs.close();
				System.out.println("在SqlHelper類中的closeDBResources()發中,ResultSet對象成功關閉。");
			} catch (SQLException e) {
				e.printStackTrace();
				System.out.println("在SqlHelper類中的closeDBResources()發中,關閉ResultSet對象失敗。");
			}
			SqlHelper.rs = null;
		}
		
		if(SqlHelper.pstmt != null){
			try {
				SqlHelper.pstmt.close();
				System.out.println("在SqlHelper類中的closeDBResources()發中,PreparedStatement對象成功關閉。");
			} catch (SQLException e) {
				e.printStackTrace();
				System.out.println("在SqlHelper類中的closeDBResources()方法中,關閉PreparedStatement對象失敗。");
			}
			SqlHelper.pstmt = null;
		}

		if(SqlHelper.conn != null){
			try {
				SqlHelper.conn.close();
				System.out.println("在SqlHelper類中的closeDBResources()方法中,Connection對象成功關閉。");
			} catch (SQLException e) {
				e.printStackTrace();
				System.out.println("在SqlHelper類中的closeDBResources()方法中,關閉Connection對象失敗。");
			}
			SqlHelper.conn = null;
		}
	}
}

2.2)方式二:用類本身提供的方法(而不是類加載器)去讀取資源文件。
(1) 參考代碼:XXX.class.getResourceAsStream("資源路徑名");
      改代碼底層調用了ClassLoader
(2) 思路:獲得Class對象-------->getResourceAsStream-------->得到資源的InputStream。
      思路起源:賣可樂的例子。
(3) 資源路徑名的書寫(重要
     用類加載器去讀取資源文件時,類加載器默認的主目錄是src
    ① 若資源文件config.properties位於src主目錄下邊,且與要調用資源文件的那個類位於同一個包中,則讀取資源文件的代碼爲:
 InputStream inputStream = XXX .class.getResourceAsStream("config.properties");
     相對路徑 
    若資源文件config.properties位於src主目錄下邊,且位於資源調用類同一個包下的子包中,比如com.zjrodger.resource中,則讀取資源文件的代碼爲:
//調用資源文件的類路徑:com.zjrodger.XXXClass
//調用資源文件路徑:com.zjrodger.resource.config.properties
InputStream inputStream = XXXClass.class.getResourceAsStream("resource/config.properties");<span style="background-color: inherit; font-family: 微軟雅黑;">   </span>
    ③ 若資源文件config.properties位於src主目錄下邊,且與要使用資源文件的那個類位於不同的包中,例如位於包com/another/package之下,則讀取資源文件的代碼爲:
InputStream inputStream = XXX .class.getResourceAsStream("com/another/package/config.properties");



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章