13161216443

您所在位置: 首頁> 學習課程> java培訓 | Java基礎專題:反射機制

java培訓 | Java基礎專題:反射機制

發布百知教育 來源:學習課程 2019-11-25

類初始化概述


類的初始化指的是將我們編寫的類文件編譯之后生成的字節碼文件進行加載、連接、初始化的過程。其中字節碼文件的加載由類加載器完成,通過不同的類加載器,可以對不同來源的字節碼文件進行加載,例如:本地文件系統、網路、動態編譯動態加載等。當一個類被加載成功之后會生成一個Class對象,接著將會進入類的連接階段,連接階段主要完成了類內部結構的驗證、類成員內存的分配及初始值設置和將符號引用替換成直接引用。經過類的連接階段后就可以初始化類了,類的初始化主要是對類的普通成員變量和類的靜態成員變量進行初始化。那么什么情況下類才會被初始化呢?

1、對象的創建,無論使用new關鍵字還是反射創建對象,都必須保證對應的類被初始化。

2、靜態方法調用,當調用一個靜態方法時,類名稱作為主調,所以必須保證該類被初始化。

3、反射創建類或接口的Class對象時,必須保證類或接口被初始化。

4、當初始化一個類的子類時,必須保證父類及所有引用的類被初始化。

5、運行一個類的主方法時,必須保證主方法所在的類被初始化。

需要注意的是,對于一個被final關鍵字修飾的變量被使用時,如果此變量的值在編譯時就已經確定了,則在編譯的過程中所有使用此變量的地方,都將被變量所對應的值替換,所以使用final關鍵字修飾的有確定值的變量時,變量所在的類不會被初始化。


類加載器的概述


顧名思義,所謂類加載器是Java提供的加載類字節碼文件的工具類,類加載器的工作機制是:一旦一個類被加載時,JVM中相同的類不會被再次加載,JVM識別一個類是否被加載過的標識是檢查此類所對應的全限定名稱(包.類名稱)是否存在。Java中根據類加載職能的不同,將類加載器分為以下三種:

根加載器:BootStrap ClassLoader,主要負責加載Java的核心類庫。

擴展加載器:Extension ClassCloader,主要負責加載Java擴展類庫,通常是加載系統屬性java.ext.dirs下的類庫。

系統加載器:System ClassCloader,主要加載-classpath參數 指定路徑下或者環境變量CLASSPATH對應路徑下的類庫。

Java中類加載器的加載機制是父類委托機制:即當類加載器加載一個類文件時,如果類加載器存在父級加載器,父級加載器還存在父級加載器,則依次向上級委托,直到最頂級加載器,如果頂級加載器加載不成功,則由當前加載器去加載,典型的啃老式加載過程,除非顯示指定類加載器。

類加載器的級別如下:


--> 自定義類加載器 --> 系統類加載器 --> 擴展類加載器 --> 根加載器



除了上述三種Java提供的類加載器外,開發人員可以繼承ClassLoader類實現自己的類加載,自己實現的類加載器級別最低。


package com.icypt.classloader;import java.io.Closeable;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Method;import java.util.regex.Matcher;import java.util.regex.Pattern;
public class CustomClassLoader extends ClassLoader{  public static final String  BASE_PATH = "E:\\個人文件\\公眾號\\文章底稿及代碼\\Java基礎\\代碼\\java-base-topic\\Topic28\\";  public static void main(String[] args) throws Exception{    if(args.length < 1) {      throw new RuntimeException("參數錯誤:" +args);    }    String name = args[0];    String targetMethodArgs =  args[1];    CustomClassLoader cc = new CustomClassLoader();    Class<?> clazz = cc.findClass(name);    Method method = clazz.getMethod("printMsg", String.class);    method.invoke(null, targetMethodArgs);  }
 @Override  protected Class<?> findClass(String name) throws ClassNotFoundException {    Class<?> clazz = null;        //獲得模式對象    Pattern pattern  =  Pattern.compile("([a-z]+[.])*[A-Z][a-zA-Z]+");        //獲得匹配對象    Matcher matcher = pattern.matcher(name);    //匹配不成功    if(!matcher.matches()) {      throw new  RuntimeException("包名稱不正確");    }    //生成類文件名稱和字節碼文件名稱    String srcFileName = BASE_PATH + "src\\" + name.replaceAll("\\.", "\\\\").concat(".java");    String classFileName = BASE_PATH + "target\\" + name.replaceAll("\\.", "\\\\").concat(".class");    File srcFile = new File(srcFileName);    File classFile = new File(classFileName);    //如果Class文件不存在或者字節碼文件的修改時間小于源碼文件時間,需要重新編譯源碼文件    if(!classFile.exists() || (classFile.lastModified() < srcFile.lastModified())) {      //編譯文件      this.excuteCompile(srcFileName);    }    if(classFile.exists()) {      byte[] data = this.getClassFileData(classFileName);      clazz = defineClass(name, data, 0, data.length);      if(clazz == null) {        throw new RuntimeException("類加載失?。?quot; + name);      }    }    return clazz;  }  /**   * 獲取字節碼文件   * @param fileName 字節碼文件名稱   * @return 字節數組   */  public byte[] getClassFileData(String fileName) {    byte[] fileData = null;    File file = new File(fileName);    fileData = new byte[(int) file.length()];    InputStream is = null;    try {      is = new FileInputStream(file);      int len = is.read(fileData);      if(len != file.length()) {        throw new RuntimeException(fileName+"文件讀取不完整");      }    } catch (IOException e) {      System.out.print("字節碼文件加載失敗");      e.printStackTrace();    } finally {      close(is);    }    return fileData;  }  /**   * 編譯源文件   * @param sourceName 源文件名稱   */  public boolean excuteCompile(String sourceName) {    Process p = null;    try {      //執行編譯命令      p = Runtime.getRuntime().exec("javac " + sourceName + " -d " + BASE_PATH  + "target" + " -encoding utf-8");      //阻塞其他線程的執行,直到當前線程執行完成      p.waitFor();    } catch (IOException e) {      System.out.println("執行編譯失敗");      e.printStackTrace();    } catch (InterruptedException e) {      System.out.println("編譯過程被打斷");      e.printStackTrace();    }    //0表示線程正常退出    return p.exitValue() == 0;  }  /**   * 統一關閉入口   * @param t   */  public <T extends Closeable> void close(T t) {    try {      if(t != null){        t.close();      }    } catch (IOException e) {      e.printStackTrace();    }  }}



栗子,被加載的Test類



java培訓班



上栗中的自定義類加載器繼承了ClassLoader類,并重寫了其findClass方法,完成了自動編譯、加載類的功能。ClassLoasder還有一個關鍵的方法loadClass。loadClass的工作原理是先調用findLoadedClass方法檢查類是否已經被加載,如果已經被加載則直接返回,否則執行委托加載機制進行類加載。而findClass方法是類加載器直接加載類文件的方法,他可以避免從緩存或者使用父類委托機制加載類。


反射概述


所謂反射機制,就是指在Java程序運行期間,對于任意一個類,都可以通過該機制獲取類的相關信息,例如類擁有的成員變量、方法、內部類、繼承的父類、實現的接口、使用的注解信息等等。從面向對象的角度來看,反射機制是對一個java類的高度抽象。


反射常用方法


反射類對象提供的方法很有規律,大致分為兩大類:獲取的目標被public訪問權限修飾以及所有訪問權限修飾,所有訪問權限修飾的獲取方法都以getDeclare開頭,同時這些方法都是成對出現的,目的是獲取單個信息和全部信息,需要注意的是獲取注解信息時,多出了一對兒以Type結尾的方法,這是在JDK1.8,之后新增的,因為JDK1.8之后支持重復注解的使用,依據此方法,可以獲取指定類型的一個或者多個注解信息。還有就是對于接口和父類的獲取分別只有一個方法,對于接口獲取的是多個,對于父類獲取的是一個,因為單繼承局限。以上描述的是其反射對象獲取類信息的方法,除了這些方法之外還有一些其他的方法,例如:獲取包名稱、類檢查、類名稱以及判斷是否為注解、匿名內部類、數組、接口等等,下面將常用方法列舉出來,供大家學習使用。


自定義類加載器




package com.icypt.classloader;import java.io.Closeable;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Method;import java.util.regex.Matcher;import java.util.regex.Pattern;
public class CustomClassLoader extends ClassLoader{  public static final String  BASE_PATH = "E:\\個人文件\\公眾號\\文章底稿及代碼\\Java基礎\\代碼\\java-base-topic\\Topic28\\";  public static void main(String[] args) throws Exception{    if(args.length < 1) {      throw new RuntimeException("參數錯誤:" +args);    }    String name = args[0];    String targetMethodArgs =  args[1];    CustomClassLoader cc = new CustomClassLoader();    Class<?> clazz = cc.findClass(name);    Method method = clazz.getMethod("printMsg", String.class);    method.invoke(null, targetMethodArgs);  }
 @Override  protected Class<?> findClass(String name) throws ClassNotFoundException {    Class<?> clazz = null;        //獲得模式對象    Pattern pattern  =  Pattern.compile("([a-z]+[.])*[A-Z][a-zA-Z]+");        //獲得匹配對象    Matcher matcher = pattern.matcher(name);    //匹配不成功    if(!matcher.matches()) {      throw new  RuntimeException("包名稱不正確");    }    //生成類文件名稱和字節碼文件名稱    String srcFileName = BASE_PATH + "src\\" + name.replaceAll("\\.", "\\\\").concat(".java");    String classFileName = BASE_PATH + "target\\" + name.replaceAll("\\.", "\\\\").concat(".class");    File srcFile = new File(srcFileName);    File classFile = new File(classFileName);    //如果Class文件不存在或者字節碼文件的修改時間小于源碼文件時間,需要重新編譯源碼文件    if(!classFile.exists() || (classFile.lastModified() < srcFile.lastModified())) {      //編譯文件      this.excuteCompile(srcFileName);    }    if(classFile.exists()) {      byte[] data = this.getClassFileData(classFileName);      clazz = defineClass(name, data, 0, data.length);      if(clazz == null) {        throw new RuntimeException("類加載失?。?quot; + name);      }    }    return clazz;  }  /**   * 獲取字節碼文件   * @param fileName 字節碼文件名稱   * @return 字節數組   */  public byte[] getClassFileData(String fileName) {    byte[] fileData = null;    File file = new File(fileName);    fileData = new byte[(int) file.length()];    InputStream is = null;    try {      is = new FileInputStream(file);      int len = is.read(fileData);      if(len != file.length()) {        throw new RuntimeException(fileName+"文件讀取不完整");      }    } catch (IOException e) {      System.out.print("字節碼文件加載失敗");      e.printStackTrace();    } finally {      close(is);    }    return fileData;  }  /**   * 編譯源文件   * @param sourceName 源文件名稱   */  public boolean excuteCompile(String sourceName) {    Process p = null;    try {      //執行編譯命令      p = Runtime.getRuntime().exec("javac " + sourceName + " -d " + BASE_PATH  + "target" + " -encoding utf-8");      //阻塞其他線程的執行,直到當前線程執行完成      p.waitFor();    } catch (IOException e) {      System.out.println("執行編譯失敗");      e.printStackTrace();    } catch (InterruptedException e) {      System.out.println("編譯過程被打斷");      e.printStackTrace();    }    //0表示線程正常退出    return p.exitValue() == 0;  }  /**   * 統一關閉入口   * @param t   */  public <T extends Closeable> void close(T t) {    try {      if(t != null){        t.close();      }    } catch (IOException e) {      e.printStackTrace();    }  }}


被加載的Test類



java培訓班




上栗中的自定義類加載器繼承了ClassLoader類,并重寫了其findClass方法,完成了自動編譯、加載類的功能。ClassLoasder還有一個關鍵的方法loadClass。loadClass的工作原理是先調用findLoadedClass方法檢查類是否已經被加載,如果已經被加載則直接返回,否則執行委托加載機制進行類加載。而findClass方法是類加載器直接加載類文件的方法,他可以避免從緩存或者使用父類委托機制加載類。


反射概述


所謂反射機制,就是指在Java程序運行期間,對于任意一個類,都可以通過該機制獲取類的相關信息,例如類擁有的成員變量、方法、內部類、繼承的父類、實現的接口、使用的注解信息等等。從面向對象的角度來看,反射機制是對一個java類的高度抽象。


反射常用方法


反射類對象提供的方法很有規律,大致分為兩大類:獲取的目標被public訪問權限修飾以及所有訪問權限修飾,所有訪問權限修飾的獲取方法都以getDeclare開頭,同時這些方法都是成對出現的,目的是獲取單個信息和全部信息,需要注意的是獲取注解信息時,多出了一對兒以Type結尾的方法,這是在JDK1.8,之后新增的,因為JDK1.8之后支持重復注解的使用,依據此方法,可以獲取指定類型的一個或者多個注解信息。還有就是對于接口和父類的獲取分別只有一個方法,對于接口獲取的是多個,對于父類獲取的是一個,因為單繼承局限。以上描述的是其反射對象獲取類信息的方法,除了這些方法之外還有一些其他的方法,例如:獲取包名稱、類檢查、類名稱以及判斷是否為注解、匿名內部類、數組、接口等等,下面將常用方法列舉出來,供大家學習使用。


/* *獲取類的構造方法 *///獲取指定參數類型的構造方法public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)throws NoSuchMethodException, SecurityException {}//獲取指定參數類型的public構造方法public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException {}//獲取所有的構造方法public Constructor<?>[] getDeclaredConstructors() throws SecurityException{}//獲取所有被public修飾符修飾的構造方法public Constructor<?>[] getConstructors() throws SecurityException{}
/* *獲取類的普通方法 *///獲取指定方法名稱的成員方法,無訪問修飾符限制public Method getDeclaredMethod(String name, Class<?>... parameterTypes)throws NoSuchMethodException, SecurityException {}//獲取指定方法名稱的修飾符為public的成員方法public Method getMethod(String name, Class<?>... parameterTypes)throws NoSuchMethodException, SecurityException{}//獲取所有的成員方法,無訪問修飾符限制public Method[] getDeclaredMethods() throws SecurityException {}//獲取所有的public修飾的成員方法public Method[] getMethods() throws SecurityException {}
/* *獲取類的成員變量 *///獲取指定變量名稱的成員變量,無訪問修飾符限制public Field getDeclaredField(String name)throws NoSuchFieldException, SecurityException{}//獲取指定變量名稱的public修飾的成員變量public Field getField(String name)throws NoSuchFieldException, SecurityException{}//獲取所有的成員變量,無訪問修飾限制public Field[] getDeclaredFields() throws SecurityException{}//獲取所有的public修飾的成員變量public Field[] getFields() throws SecurityException{}
/* *獲取類的注解信息 *///獲取直接修飾該類的指定類型的注解public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass) {}//獲取修飾該類的指定類型的注解(包括繼承而來的注解)public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}//獲取直接修飾該類的多個注解public Annotation[] getDeclaredAnnotations(){}//獲取修飾該類的多個注解(包括繼承而來的注解)public Annotation[] getAnnotations() {}//獲取直接修飾該類的指定類型的多個注解public <A extends Annotation> A[] getDeclaredAnnotationsByType(Class<A> annotationClass) {}//獲取修飾該類的指定類型的多個注解(包括繼承而來的注解)public <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass) {}
//獲取實現的所有接口public Class<?>[] getInterfaces() {}//獲取繼承的父類public native Class<? super T> getSuperclass();//獲取對應的內部類public Class<?>[] getDeclaredClasses() throws SecurityException {}//h獲取對應的外部類public Class<?> getDeclaringClass() throws SecurityException{}
/* *判斷方法 *///判斷是不是注解public boolean isAnnotation(){}//判斷是不是匿名內部類public boolean isAnonymousClass(){}//判斷是不是數組public boolean isArray(){}//判斷是不是枚舉類public boolean isEnum(){}//判斷是不是接口public boolean isInterface(){}//判斷obj是不是該接口對象public boolean isInterface(Object obj){}


反射的的使用


package com.icypt.reflect;
import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Inherited;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.Arrays;import java.util.List;
/** * 運行結果 * ClassA普通方法-argsA-[arr2, arry2]-[arr2, arry2] * 重寫A接口的方法 * 重寫B接口的方法 * 方法列表: * 1,方法名稱:interB * 2,方法名稱:interA * 3,方法名稱:classA * ClassA普通方法-args1-[arr2, arry2]-[arr2, arry2] * @Flag value:true * fieldA-->成員變量A*/public class Test {  public static void main(String[] args) throws Exception{    String[] strArray = new String[] {"arr2", "arry2"};    /**     * 通過new關鍵字實例化ClassA對象,并執行成員方法和重寫方法     */    ClassA classA = new ClassA();    classA.classA("argsA", strArray, Arrays.asList(strArray));    classA.interA();    classA.interB();    /**     * 通過反射獲取類信息,實例化對象并執行方法     */    //三種獲取Class對象的方式    //Class<?> clazz = classA.getClass();    //Class<?> clazz = ClassA.class;    Class<?> clazz = Class.forName("com.icypt.reflect.ClassA");    Method[] methodArray = clazz.getDeclaredMethods();    System.out.println("方法列表:");    int i = 0;    for(Method method : methodArray) {      System.out.println((++i) + ",方法名稱:" + method.getName());    }    //實例化對象    Object obj = clazz.newInstance();    //獲取指定方法名稱、指定參數類型的方法    Method method = clazz.getMethod("classA", String.class, String[].class, List.class);    //通過反射執行方法    method.invoke(obj, "args1", strArray, Arrays.asList(strArray));    //獲取方法的注解    Flag ann =  method.getAnnotation(Flag.class);    System.out.println("@Flag value:" + ann.value());    //獲取普通成員變量    Field field = clazz.getDeclaredField("fieldA");    field.setAccessible(true);    System.out.println("fieldA-->" + field.get(obj));  }}
interface IterA{  public void interA();}
interface InterB{  public void interB();}
class ClassA implements IterA, InterB{  private String fieldA = "成員變量A";  @Flag  public void classA(String args1, String[] args2, List<String> args3) {    System.out.println("ClassA普通方法-"+ args1 + "-" + Arrays.asList(args2) + "-" + args3);  }
 @Override  public void interA() {    System.out.println("重寫A接口的方法");    }
 @Override  public void interB() {    System.out.println("重寫B接口的方法");    }}@Target(ElementType.METHOD)@Documented@Inherited@Retention(value= RetentionPolicy.RUNTIME)@interface Flag {  public boolean value() default true;}


演示了反射的基本使用姿勢,主要體現了反射創建對象、獲取方法、執行方法、獲取注解信息、獲取成員變量等常用方式。


代理類概述


代理設計模式:為其他對象提供一種代理來控制這個對象的訪問,比如:公司現在要去談一個業務,這個業務的核心功能都是由A員工負責的,但是A不想去接觸一些與核心功能無關的事情,所以就將一些輔助工作交由B員工來做,當具體到核心業務時,由B向A轉達完成核心功能,那么B就是A的代理。代理又分為靜態代理和動態代理,靜態代理和動態代理最大的區別是靜態代理的代理對象在做事前是已知的,動態代理的代理對象是在做事前動態創建的。關于動態代理Java專門提供了兩個類:Proxy和InvocationHandler。Proxy類為我們提供了兩個方法方便我們創建動態代理類以及對象:


Java培訓班


通過以上兩個方法我們就可以實現Java提供的動態代理功能了。

栗子,動態代理實現


package com.icypt.proxy;
import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;/** * 運行結果 * ******代理**********開啟事務**************** * 執行插入方法! * ******代理**********提交事務**************** * 執行查詢方法!*/public class Test {  public static void main(String[] args) throws Exception{    ProxyFactoryBaseJdk<IterA> factory = new ProxyFactoryBaseJdk<>(new ClassA());    IterA iterA = factory.getProxyInstance();    iterA.insert();    iterA.query();  }}/** * 動態代理工廠 */class ProxyFactoryBaseJdk<T> {    private T target;    public ProxyFactoryBaseJdk(T target) {        this.target = target;    }    /**     * 獲取代理對象     * @return     */  @SuppressWarnings("unchecked")    public T getProxyInstance() {        Class<?> clazz = target.getClass();        Object proxy = Proxy.newProxyInstance(                clazz.getClassLoader(), //獲取目標類的類加載器                clazz.getInterfaces(), //獲取目標類所實現的所有接口                new InvocationHandler() //執行代理對象方法時觸發                {            @Override            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                //執行結果                Object result = null;                //如果是新增需要處理事務                if(method.getName().contains("insert")) {                    System.out.println("******代理**********開啟事務****************");                    result = method.invoke(target, args);                    System.out.println("******代理**********提交事務****************");                } else {                    //直接執行                    result = method.invoke(target, args);                }                return result;            }        });        return (T)proxy;    }}

interface IterA{  public void insert();  public void query(); }
class ClassA implements IterA {  @Override  public void insert() {    System.out.println("執行插入方法!");  }  @Override  public void query() {    System.out.println("執行查詢方法!");  }}


關于動態代理還有其他的實現方式,我會在后期框架的學習中繼續介紹,例如Spring框架中支持CGLIB實現動態代理。


至此關于Java中反射的使用就學習到這里, 可能在平時的學習或者開發中用到反射的幾率比較少, 但是隨著學習的深入會發現處處存在反射, 反射機制是java的靈魂。下篇我們來學習Java的網絡編程。


java培訓班:http://www.akpsimsu.com/java2019


上一篇:UI設計培訓 | 游戲ui設計有哪些設計元素?

下一篇:應屆生去公司找個Java程序員的職位需要什么技能?

相關推薦

www.akpsimsu.com

有位老師想和您聊一聊

關閉

立即申請