類加載機制與反射

一. 類的加載,鏈接,初始化

  1.1. JVM和類

當調用Java命令運行某個Java程序時,該命令將會啓動一個Java虛擬機進程。無論Java程序多麼複雜,啓動多少個線程,它們都處於該Java虛擬機進程裏,都是使用同一個Java進程內存區。java

JVM程序終止的方式:mysql

  • 程序運行到最後正常結束
  • 程序運行到使用System.exit()或Runtime.getRuntime().exit()代碼處結束程序
  • 程序執行過程當中遇到未捕獲的異常或錯誤而結束
  • 程序所在平臺強制結束了JVM進程

JVM進程結束,該進程所在內存中的狀態將會丟失sql

  1.2 類的加載

當程序主動使用某個類時,若是該類還未被加載到內存中,則系統會經過加載、鏈接、初始化三個步驟來對該類進行初始化。數據庫

類的加載時將該類的class文件讀入內存,併爲之建立一個java.lang.Class對象,也就是說,當程序使用任何類時,系統都會爲之創建一個java.lang.Class對象。編程

系統中全部的類實際上也是實例,它們都是java.lang.Class的實例api

類的加載經過JVM提供的類加載器完成,類加載器時程序運行的基礎,JVM提供的類加載器被稱爲系統類加載器。除此以外,開發者能夠經過繼承ClassLoader基類來建立本身的類加載器。數組

經過使用不一樣的類加載器,能夠從不一樣來源加載類的二進制數據,一般有以下幾種來源。緩存

  1. 從本地文件系統加載class文件,這是前面絕大部分實例程序的類加載方式
  2. 從jar包加載class文件,這種方式也是很常見的,jdbc編程所用的驅動類就放在jar文件中,JVM能夠直接從jar文件中加載該class文件。
  3. 經過網絡加載class文件
  4. 把一個Java源文件動態編譯,並執行加載

類加載器一般無需等到首次使用該類時才加載該類,Java虛擬機規範容許系統預先加載某些類。安全

  1.3 類的鏈接網絡

當類被加載後,系統會爲之生成一個對應的Class對象,接着會進入鏈接階段,鏈接階段負責把類的二進制數據合併到JRE中。類的連接可分爲以下三個階段。

  1. 驗證:驗證階段用於檢驗被加載的類是否有正確的內部結構,並和其餘類協調一致
  2. 準備:類準備階段則負責爲類的類變量分配內存,並設置默認初始值
  3. 解釋:將類的二進制數據中的變量進行符號引用替換成直接引用

  1.4 類的初始化

再累溫馨化階段,虛擬機負責對類進行初始化,主要就是對類變量進行初始化。在Java類中對類變量指定初始值有兩種方式:①聲明類變量時指定初始值;②使用靜態初始化塊爲類變量指定初始值。

JVM初始化一個類包含以下步驟

  1. 加載並鏈接該類
  2. 先初始化其直接父類
  3. 依次執行初始化語句

當執行第2步時,系統對直接父類的初始化也遵循1~3,以此類推

  1.5 類初始化時機

當Java程序首次經過下面6種方式使用某個類或接口時,系統會初始化該類或接口

  • 建立類的實例。建立類的實例包括new操做符來建立實例,經過反射來建立實例,經過反射實例化建立實例
  • 調用某個類的類方法(靜態方法)
  • 訪問某個類或接口的類變量或爲該類變量賦值
  • 使用反射方式來強制來建立某個類或接口的java.lang.Class對象。例如代碼「Class.forname("Person")」,若是系統還未初始化Person類,則這行代碼會致使Person類被初始化,並返回person類的java.lang.Class對象
  • 初始化某個類的子類
  • 使用java.exe命令來運行某個主類。當運行某個主類時,程序會初始化該主類

二. 類加載器

  2.1類加載器介紹

  類加載器負責將.class文件加載到內存中,併爲之生成對應的java.lang.Class對象。

一個載入JVM的類有一個惟一的標識。在Java中,一個類使用全限定類名(包括包名和類名)做爲標識;但在JVM中,一個類使用全限定類名和其類加載器做爲惟一標識。

當JVM啓動時,會造成由三個類加載器組成的初始類加載器層次結構

  • Bootstrap ClassLoader:跟類加載器
  • Extension ClassLoader:擴展類加載器
  • System ClassLoader:系統類加載器

Bootrap ClassLoader被稱爲引導(也稱爲原始或跟)類加載器,它負責加載Java的核心類。跟類加載器不是java.lang.ClassLoader的子類,而是JVM自身實現的。

Extension ClassLoader負責加載JRE拓展目錄中的JAR包的類,它的父類加載器是跟類加載器

System ClassLoader,它負責在JVM啓動時加載來自Java命令的-classpath選項、java.class,path系統屬性,或CLASSPATH指定的jar包和類歷經。系統可經過ClassLoader的靜態方法或區該系統類加載器。若是沒有特別指定,則用戶自定義的類加載器都已類加載器做爲父加載器

  2.2 類加載機制

JVM類加載機制主要有三種

  • 全盤負責。就是當類加載器負責加載某個Class時,該Class所依賴的和所引用的其餘Class也將由該類加載器負責載入,除非顯式使用另一個類加載器來載入
  • 父類委託。所謂父類委託,就是先讓父類加載器試圖加載該Class。只有在父類加載器沒法加載該類時才嘗試從本身的類路徑中加載該類
  • 緩存機制。緩存機制將會保證全部加載過的Class都會被緩存,當程序須要使用時,先從緩存中搜索該Class,當緩存中不存在該Class,系統菜纔讀取該類對應的二進制數據,並將其轉爲Class對象,存入緩存區中。這就是爲何修改了Class後,必須從新啓動JVM,程序所作的修改纔會生效的緣由。

類加載器加載Class大體通過8個步驟

  1. 檢測此Class是否載入過(即緩存區中是否有此Class),若是有則直接進入第8步,否者接着第2步
  2. 若是父類加載器(父類      gt+ 加載器,要麼Parent必定是跟類加載器,要麼自己就是跟類加載器)不存在,則調到第4步執行
  3. 請求使用父類加載器載入目標類,若是成功載入調到第8步
  4. 請求使用跟類加載器來載入目標類
  5. 當前類加載器嘗試尋找Class文件(從與此ClassLoader相關的類路徑中尋找),若是找到則執行第6步,若是找不到執行第7步
  6. 從文件中載入Class,成功載入調到第8步
  7. 拋出ClassNotFoundException異常
  8. 返回對應的java.lang.Class對象

其中,第五、6步容許重寫ClassLoader的findClass()方法來實現本身的載入策略,甚至重寫loadClass()方法來實現本身的載入過程。

  2.3 建立並使用自定義的類加載器

JVM除跟類加載器以外的全部類加載器都是ClassLoader子類的實例,開發者能夠經過拓展ClassLoader的子類,並重寫該ClassLoader所包含的方法實現自定義的類加載器。ClassLoader有以下兩個關鍵方法。

  • loadClass(String name,boolean resolve):該方法爲ClassLoader的入口點,根據指定名稱來加載類,系統就是調用ClassLoader的該方法來獲取指定類的class對象
  • findClass(String name):根據指定名稱來查找類

若是須要是實現自定義的ClassLoader,則能夠經過重寫以上兩個方法來實現,一般推薦重寫findClass()方法而不是loadClass()方法。

classLoader()方法的執行步驟:

  1. findLoadedClass():來檢查是否加載類,若是加載直接返回。
  2. 父類加載器上調用loadClass()方法。若是父類加載器爲null,則使用跟類加載器加載。
  3. 調用findClass(String)方法查找類

從上面看出,重寫findClass()方法能夠避免覆蓋默認類加載器的父類委託,緩衝機制兩種策略;若是重寫loadClass()方法,則實現邏輯更爲複雜。

ClassLoader的一些方法:

  • Class defineClass(String name,byte[] b,int off,int len):負責將字節碼分析成運行時數據結構,並檢驗有效性
  • findSystemClass(String name):從本地文件系統裝入文件。
  • static getSystemClassLoader():返回系統類加載器
  • getParent():獲取該類加載器的父類加載器
  • resolveClass(Class<?> c):連接指定的類
  • findClassLoader(String name):若是加載器加載了名爲name的類,則返回該類對用的Class實例,不然返回null。該方法是類加載緩存機制的體現。

下面程序開發了一個自定義的ClassLoader。該classLoader經過重寫findClass()方法來實現自定義的類加載機制。這個ClassLoader能夠在加載類以前先編譯該類的源文件,從而實現運行Java以前先編譯該程序的目標,這樣便可經過該classLoader運行Java源文件。

 

package com.gdut.basic;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;

public class CompileClassLoader extends ClassLoader {
private byte[] getBytes(String fileName) {
    File file = new File(fileName);
    Long len = file.length();
    byte[] raw = new byte[(int)len];
    
        FileInputStream fin = new FileInputStream(file);
        //一次讀取class文件的二進制數據
        int r = fin.read(raw);
        if(r != len) {
            throw new IOException("沒法讀取文件"+r+"!="+raw);
        
    
    return null;
        }
}
    private boolean compile(String javaFile) throws IOException {
        System.out.println("正在編譯"+javaFile+"...");
        Process p = Runtime.getRuntime().exec("javac"+javaFile);
        try {
            //其餘線程都等待這線程完成
            p.waitFor();
        }catch(InterruptedException ie) {
            System.out.println(ie);
        }
        int ret = p.exitValue();
        return ret == 0;
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class clazz = null;
        String findStub = name.replace(".", "/");
        String javaFileName = findStub+".java";
        String classFileName = findStub+".class";
        File javaFile = new File(javaFileName);
        File classFile = new File(classFileName);
        
        //但指定Java源文件存在,class文件不存在,或者Java源文件的修改時間比class文件修改的時間更晚時,從新編譯
        if(javaFile.exists() && classFile.exists()
                || javaFile.lastModified() > classFile.lastModified()) {
            try {
            if(!compile(javaFileName)|| !classFile.exists()) {
                throw new ClassNotFoundException("ClassNotFoundExcetion"+javaFileName);
            }
            }catch(IOException ie) {
                ie.printStackTrace();
            }
        }
        if(classFile.exists()) {
            
                byte[] raw = getBytes(classFileName);
                
                clazz = defineClass(name,raw,0,raw.length);
        }
        //若是clazz爲null,代表加載失敗,則拋出異常
        if(clazz == null) {
            throw new ClassNotFoundException(name);
        }
        return clazz;
    }
    
    public static void main(String[] args) throws Exception {
        //若是運行該程序時沒有參數,即沒有目標類
        if (args.length<1) {
            System.out.println("缺乏目標類,請按以下格式運行Java源文件:");
            System.out.println("java CompileClassLoader ClassName");
        }
        
        //第一個參數是須要運行的類
        String progClass = args[0];
        
        //剩下的參數將做爲運行目標類時的參數,將這些參數複製到一個新數組中
        String[] progArgs = new String[args.length - 1];
        System.arraycopy(args, 1,progArgs,0, progArgs.length);
        
        CompileClassLoader ccl = new CompileClassLoader();
        //加載須要運行的類
        Class<?> clazz = ccl.loadClass(progClass);
        //獲取運行時的類的主方法
        Method main = clazz.getMethod("main", (new String[0]).getClass());
        Object argsArray[] = {progArgs};
        main.invoke(null, argsArray);
        
    }
}

接下來能夠提供任意一個簡單的主類,該主類無需編譯就可使用上面的CompileClassLoader來運行他

package com.gdut.basic;

public class Hello {

    public static void main(String[] args) {
        for(String arg:args) {
            System.out.println("運行Hello的參數:"+arg);
        }

    }

}

無需編譯該Hello.java,能夠直接運行下面命令來運行該Hello.java程序

java CompileClassLoader hello 瘋狂Java講義

運行結果以下:

CompileClassLoader:正常編譯 Hello.java...
運行hello的參數:瘋狂Java講義

 

使用自定義的類加載器,能夠實現以下功能

  1. 執行代碼前自動驗證數字簽名
  2. 根據用戶提供的密碼解密代碼,從而能夠實現代碼混淆器來避免反編譯*.class文件
  3. 根據應用需求把其餘數據以字節碼的形式加載到應用中。

    2.4 URLClassLoader類

該類時系統類加載器和拓展類加載器的父類(此處的父類,是指類與類之間的的繼承關係)。URLClassLoader功能比較強大,它能夠從本地文件系統獲取二進制文件來加載類,也能夠從遠程主機獲取二進制文件加載類。

該類提供兩個構造器

  • URLClassLoader(URL[] urls):使用默認的父類加載器建立一個ClassLoader對象,該對象將從urls所指定的路徑來查詢並加載類
  • URLClassLoader(URL[] urls,ClassLoader prarent):使用指定的父類加載器建立一個ClassLoader對象,該對象將從urls所指定的路徑來查詢並加載類。

下面程序示範瞭如何從文件系統中加載MySQL驅動,並使用該驅動獲取數據庫鏈接。經過這種方式來獲取數據庫鏈接,無需將MySQL驅動添加到CLASSPATH中。

package java.gdut;

import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;
import java.sql.Driver;
import java.util.Properties;

public class URLClassLoaderTest {
    private static Connection conn;

    public static Connection getConn(String url,String user,String pass)throws Exception{
        if(conn == null){
            URL[] urls = {new URL("file:mysql-connection-java-5.1.46-bin.jar")};
            URLClassLoader myClassLoader = new URLClassLoader(urls);
            //加載MySQL,並建立實例
            Driver driver = (Driver)myClassLoader.loadClass("com.mysql.jdbc.Driveer").newInstance();

            Properties properties = new Properties();
            properties.setProperty("user",user);
            properties.setProperty("pass",pass);
            //調用driver的connect方法來取得數據庫鏈接
            conn = driver.connect(url,properties);
        }
        return conn;
    }

    public static void main(String[] args) throws Exception {
        System.out.println(getConn("jdbc:mysql://localhost:3306/tb_test","sherman","a123"));
    }
}

本程序類加載器的加載路徑是當前路徑下的mysql-connection-java-5.1.46-bin.jar文件,將MySQL驅動複製到該路徑下,這樣保證ClassLoader能夠正常加載到驅動類

三. 經過反射查看類信息

Java程序中的許多對象在運行時都會出現收到外部傳入的一個對象,該對象編譯時類型是Object,但程序又須要調用該對象運行時的方法。

  • 第一種作法是假設編譯時和運行時都知道該對象的的類型的具體信息,這種狀況下,能夠先用instanceof()運算符進行判斷,再利用強制類型轉換將其轉換成運行時類型的變量便可
  • 第二種作法是編譯時根本沒法知道該對象和類可能屬於那些類,程序只依靠運行時信息來發現該對象和類的真實信息,這就必須使用反射

  3.1 得到class對象

每一個類被加載後,系統會爲該類生成一個對應的Class對象,經過該Class對象能夠訪問到JVM中的這個類。得到Class對象一般三種方式

  1. 使用Class類的forName(String clazz)靜態方法。字符串參數傳入全限定類名(必須添加包名),可能會拋出ClassNotFoundexception異常。
  2. 調用某個類的class屬性來獲取該類的的Class對象。
  3. 調用某個對象的getClass()方法,該方法是Object類的一個方法。

對於第一種方式,第二種的優點:

  • 代碼更安全。程序在編譯階段就能夠檢查須要訪問的Class對象是否存在。
  • 程序性能更好。這的種方式無需調用方法,因此性能更好。

  3.2 從Class中獲取信息

Class類提供了大量的實例方法獲取該Class對象所對應類的詳細信息

下面4個方法用於獲取Class對象對應類的構造器

  • ConStructor<T> getConStructor(Class<?> parameterTypes):返回Class對象對應類的,帶指定參數列表的public構造器
  • ConStructor<?>[] getConStructor():返回此Class對象對應類的全部public構造器
  • ConStructor<T> getDeclaredConStructor(Class<?>... parameterTypes):返回此Class對象對應類的、帶指定參數列表的構造器,與構造器的訪問權限無關
  • ConStructor<?>[] getDeclaredConStructor():返回此Class對象對應類的全部構造器,與構造器的訪問權限無關

下面四個方法獲取Class對象對應類所包含方法。

  • Method getMethod(String name,Class<?> parameterTypes):返回Class對象對應類的,帶指定形參列表的public方法
  • Method[] getMethods():返回Class對象對應類的全部public方法
  • Method getDeclaredMethod(String name,Class<?> parameterTypes):返回Class對象對應類的,帶指定形參列表的方法,與訪問權限無關
  • Method[] getDeclaredMethods():返回Class對象對應類的全部所有方法,與方法的訪問權限無關

下面四個方法獲取Class對象對應類所包含的成員變量。

  • Field getField(String name):返回Class對象對應類的,指定名稱的public成員變量
  • Field[] getFIelds():返回Class對象對應類的全部public成員變量
  • Field getDeclaredField(String name):返回Class對象對應類的,指定名稱的成員變量,與成員的訪問權限無關
  • Field[] getFIelds():返回Class對象對應類的全部成員變量,與成員的訪問權限無關

以下幾個方法用於訪問Class對應類的上所包含的Annotation.

  • <A extends Annotation>A getAnnotation(Class<A> annotationClass):嘗試獲取該Class對象對應類存在的,指定類型的Annotation;若是該類型的註解不存在,則返回null。
  • <A extends Annotation>A getDeclaredAnnotation(Class<A> annotationClass):Java 8新增方法,嘗試獲取直接修飾該Class對象對應類存在的,指定類型的Annotation;若是該類型的註解不存在,則返回null。
  • Annotation[] getAnnotations():獲取該Class對象對應類存在的全部Annotation
  • Annotation[] getDiclaredAnnotations():獲取直接修飾該Class對象對應類存在的全部Annotation
  • <A extends Annotation>A[] getAnnotationByType(Class<A> annotationClass):因爲Java 8的新增了重複註解功能,所以須要使用該方法獲取修飾該Class對象對應類,指定類型的多個Annotation
  • <A extends Annotation>A[] getDeclaredAnnotationByType(Class<A> annotationClass):因爲Java 8的新增了重複註解功能,所以須要使用該方法獲取直接修飾該類的,指定類型的多個Annotation

以下方法用於訪問Class對應類的內部類

  • Class<?>[] getDeclaredClass():返回該Class對象對應類裏包含的內部類

以下方法用於訪問Class對應類的所在的外部類

  • Class<?>[] getDeclaringClass():返回該Class對象對應類所在的外部類

以下方法用於訪問Class對應類的所實現的接口

  • Class<?>[] getInterfaces():返回該Class對象對應類的所實現的接口

以下方法用於訪問Class對應類的所繼承的父類

  • Class<? super T> getSuperClass():返回該Class對象對應類的超類的Class對象

以下方法用於訪問Class對應類的修飾符,所在包,類名等基本信息

  • int getModifiers():返回此類或接口的全部修飾符對應的常量,返回的整數須要Modifier工具類的方法來解碼,才能夠獲取真正的修飾符
  • Package getPackage():獲取此類的包
  • String getName():以字符串的形式返回該Class對象對應類的類名
  • String getSimpleName():以字符串的形式返回該Class對象對應類的簡稱

如下幾個方法來判斷該類是否爲接口、枚舉、註解類型

  • boolean isAnnotation():返回此Class對象是否表示一個註解類型(有@interface定義)
  • boolean isAnnotationPresent(Class<? extends Annotation>annotationClass):判斷此Class對象是否使用了註解修飾
  • boolean isAnonymousClass():返回此Class對象是否爲匿名類
  • boolean isArray():返回此Class對象是否爲數組類
  • boolean isEnum():返回此Class對象是否爲枚舉類
  • boolean isInterface():返回此Class對象是否爲接口
  • boolean isInstance(Object obj):判斷obj是否爲該Class對象的實例,該方法能夠替代instanceof操做符

以上getMethod()方法和getConStructor()方法中,都須要傳入多個類型爲Class<?>的參數,用於獲取指定的方法和構造器。要肯定一個方法應該由方法名和形參列表肯定。例以下面代碼獲取clazz對應類的帶一個String參數的info方法:

clazz.getMethods("info",String.class)

  若要獲取clazz對應類的帶一個String參數,一個Integer參數的info方法

clazz.getMethods("info",String.class,Integer.class)

  3.3 Java 8新增長的方法參數反射

Java 8新增了一個Executable抽象基類,該對象表明可執行的類成員,該類派生了Constructor和Method兩個子類。

Executable抽象基類提供了大量方法來獲取修飾該方法或構造器的註解信息;還提供了is VarArgs()方法用於判斷該方法或構造器是否包含數量可變的形參,以及經過getModifiers()方法獲取該方法或構造器的修飾符。除此以外,還提供以下兩個方法

  • int getParameterCount():獲取該構造器或方法的形參個數
  • Parameter[] getParameters():獲取該構造器或方法的全部形參

Parameter類是Java 8新增的api,提供了大量方法來獲取聲明該方法或參數個數的泛型信息,還提供了以下方法獲取參數信息

  • getModifiers():獲取修飾該形參的修飾符
  • String getName():獲取形參名
  • Type getParameterizedType():獲取帶泛型的形參類型
  • Class<?> getType():獲取形參類型
  • boolean isNamePresent():該方法返回該類的class文件中是否包含了方法的形參名信息
  • boolean isVarArgs():判斷該參數是否爲個數可變的形參

須要指出的是,使用javac命令編譯Java源文件時,默認生成的class文件並不包含方法的形參名信息,所以調用isNamePresent()將返回false,調用getName()也不能獲得該參數的形參名。須要編譯時保留形參信息,則須要該命令指定-parameter選項。

下面示範了Java 8的參數反射功能

public class MethodParameterTest {
    public static void main(String[] args) throws Exception {
        Class<Test> clazz = Test.class;
        Method replace = clazz.getMethod("replace",String.class,List.class);
        System.out.println("replace方法的參數個數爲:"+replace.getParameterCount());

        Parameter[] parameters = replace.getParameters();
        int index = 1;
        for(Parameter parameter:parameters){
            if(!parameter.isNamePresent()){
                System.out.println("-----第"+index+"行的參數信息-----");
                System.out.println("參數名:"+parameter.getName());
                System.out.println("形參類型:"+parameter.getType());
                System.out.println("泛型類型:"+parameter.getParameterizedType());
            }
        }
    }
}

  3.4 利用反射生成並操做對象

Class對象能夠得到該類的方法,構造器,成員變量。程序能夠經過Method對象來執行對應的方法,經過ConStructor對象調用對應的構造器建立實例,能經過Field對象直接訪問並修改對象的成員變量值。

3.4.1 建立對象

經過反射生成對象有兩種方式。

  • 使用Class對象的newInstance()方法來建立該Class對象對應類的實例,這種方式要求該Class對象的對應類有默認構造器。
  • 先使用Class對象獲取指定的Constructor對象,在調用Constructor對象的newInstance()方法來建立該Class對象對應類的實例。

3.4.2 調用方法

能夠經過Class對象的getMethods()方法和getMethod()方法來獲取所有方法和指定方法。

每一個Method對象對應一個方法,能夠經過它調用對應的方法,在Method裏包含一個invoke()方法,該方法的簽名以下。

  • Object invoke(Object obj,Object... args):該方法中的obj是執行該方法的主調,後面的args是執行該方法時傳入該方法的實參。

下面程序是對象池工廠增強版,它容許在配置文件中增長配置對象的成員變量的值,對象池工廠會讀取爲該對象配置的成員變量值,並利用該對象的Setter方法設置成員變量的值。

package com.gdut.test0516;

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class ExtendedObjectPoolFactory {
    //定義一個對象池,前面是對象名,後面是實際對象
    private Map<String,Object> objectPool = new HashMap<>();
    private Properties config = new Properties();

    public void init(String fileName)
    {
        try(FileInputStream fis = new FileInputStream(fileName))
        {
            config.load(fis);
        }catch(IOException ex){
            System.out.println("讀取"+fileName+"異常");
        }
    }

   private Object createObject(String clazzName)throws ClassNotFoundException,
           InstantiationException,IllegalAccessException{
        Class<?> clazz = Class.forName(clazzName);
        //使用clazz默認構造器建立實例
        return clazz.newInstance();
   }


   public void initPool()throws ClassNotFoundException,
           InstantiationException,IllegalAccessException{
       for (String name:config.stringPropertyNames())
       {
           //沒取出一個key-value對。若是key中不包含百分號(%),便可認爲該key用於
           // 控制調用對象的setter方法設置值,%前半爲對象名字,後半控制setter方法名
       if( !name.contains("%")){
           objectPool.put(name,createObject(config.getProperty(name)));
       }
       }
   }
   public Object getObject(String name){
        return objectPool.get(name);
   }

   public void initProperty()throws NoSuchMethodException,
   IllegalAccessException,InvocationTargetException {
       for (String name:config.stringPropertyNames()) {
           if(name.contains("%")){
               String[] objAndProp = name.split("%");
               Object target = getObject(objAndProp[0]);
               String mtdName = "set"+objAndProp[1].substring(1);
               Class<?> targetClass = target.getClass();
               Method mtd = targetClass.getMethod(mtdName);
               mtd.invoke(target,config.getProperty(name));
           }
       }
   }

    public static void main(String[] args)throws Exception {
        ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory();
        epf.init("com/gdut/test0516/extObj.txt");
        epf.initPool();
        epf.initProperty();
        System.out.println(epf.getObject("a"));
    }
}

  3.4.3 訪問成員變量

經過Class對象的getFields()方法和getField()方法能夠獲取該類包含的全部成員變量和指定成員變量。Field提供以下方法讀取或設置成員變量值

  • getXxx(Object obj):獲取Object對象的成員變量值。此處的Xxx對應8種基本類型,若是該成員變量類型時引用類型,則取消get後面的Xxx。
  • setXxx(Object obj,Xxx val):將obj對象的該成員變量設置成val值。此處的Xxx對應8種基本類型,若是該成員變量類型時引用類型,則取消set後面的Xxx。

3.4.4 操做數組

在java.lang.reflect包下還提供了一個Array類,Array對象能夠表明全部的數組。程序能夠經過使用該類來建立數組,操做數組元素等。

Array提供以下方法

  • static Object newInstance(Class<?>ComponentType,int... length):建立一個具備指定的元素類型,指定維度的新數組
  • static xxx getXxx(Object array,int index):返回數組array的第index個元素。此處的xxx對應8種基本類型,若是數組元素是引用類型,則該方法變爲get(Object array,int index)。
  • static void setXxx(Object array,int index,Object val):將數組array的第index個元素設置爲val。此處的xxx對應8種基本類型,若是數組元素是引用類型,則該方法變爲set(Object array,int index,Object val)。
相關文章
相關標籤/搜索