Java反射、動態代理詳解,看這篇文章就夠了

要了解Java反射機制,先來看看Java類的加載過程。

一、類的加載

類的加載過程

類的加載過程

1.加載

加載過程主要完成三件事情:
(1)通過類的全限定名來獲取定義此類的二進制字節流
(2)將這個類字節流代表的靜態存儲結構轉爲方法區的運行時數據結構
(3)在堆中生成一個代表此類的java.lang.Class對象,作爲訪問方法區這些數據結構的入口。

2.連接

(1)校驗:其中一項看字節碼的數據是否以“魔數cafe”以及當前的JVM的運行JDK版本是否可以運行。向下兼容,反過來不行。
(2)準備:給成員變量(類變量/靜態變量給默認值),把常量(final)等值在方法區的常量池準備好
(3)解析:理解爲把類中的對應類型名換成該類型的class對象的地址
String --》String類型對應的Class地址

3.初始化

包含以下兩個部分
(1)靜態變量的顯式初始化代碼,賦值代碼
(2)靜態代碼塊

哪些操作會導致類的初始化?
這句話的意思是,類的加載不一定會發生類初始化。雖然大多數時候都會初始化。
(1)main方法所在的類在加載時,直接初始化。
(2)new一個類的對象
(3)調用該類的靜態變量(final常量除外)和靜態方法
(4)使用java.lang.reflect包的方法對類進行反射調用
(5)初始化一個類,如果其父類沒有被初始化,則會先初始化其父類

哪些操作不會導致類的初始化?
(1)引用靜態常量時(final)不會觸發此類的初始化
(2)當訪問一個靜態域時,只有真正聲明這個域的類纔會初始化,換句話說,通過子類訪問父類的靜態域時,只會初始化父類,不會初始化子類
(3)通過數組定義類引用,不會觸發類初始化
無論如何,類的加載結果:在方法區有一個唯一的Class對象來代表一個類型

類加載器

1.類加載器是負責加載類的對象。
2.每個 Class 對象都包含一個對定義它的 ClassLoader 的引用。
共有四種類加載器:
(1)引導類加載器:用C++編寫的,是JVM自帶的類裝載器,負責Java平臺核心庫,用來裝載核心類庫。該加載器無法直接獲取
(2)擴展類加載器:負責jre/lib/ext目錄下的jar包或 –D java.ext.dirs 指定目錄下的jar包裝入工作庫
(3)應用程序類加載器:負責java –classpath 或 –D java.class.path所指的目錄下的類與jar包裝入工作 ,是最常用的加載器
(4)自定義類加載器(一般兩種情況會用到):

  • 字節文件需要加密解密
  • 加載特定目錄下的類
    關係:4,3,2,1 4認3爲parent加載器,但不是繼承關係
雙親委派機制

雙親委派

當“應用程序類加載器"接到一個加載任務時
(1)先搜素內存中是否已經加載過了,如果加載過了,就可以找到對應的C1ass對象,那麼就不加載了
(2)如果沒有找到,把這個任務先提交給 parent",父加載器接到任務時,也是重複(1)(2)
3)直到傳給了根加載器,如果根加載器可以加載,就完成了,如果不能加戟,往回傳,依次每個加載器嘗試在自己員責的路徑下搜素,如果找到就直接返回Class對象,如果一直回傳到“應用程序類加載器”,還是沒有找到就會報ClassnotFoundException
爲什麼要使用這種機制呢?

  • 爲了安全,防止你寫一個核心類。
  • 每一個加載器只負責自己路徑下的東西。
    類加載器的作用
    1、最主要的作用:加載類
    2、輔助的作用:可以用它來加載“類路徑下”的資源文件
    例如:bin中src下文件ー->bin目錄下Properties.Properties類表示了一個持久的屬性集, Properties可保存在流中或從中加載屬性列表中每個鍵

二、Java反射

正射:

之前寫代碼都是正射,先寫類–>用類創建對象–>通過對象操作

反射

原來是:類–>對象
現在是:從這個類的Class對象–>類的所有的操作
其中一種例如Tomcat,spring,mybatis等各種框架
(1)先創建對象
(2)通過對象操作
(3)再寫類 通過XML等文件告知框架類名

第二種:
(1)先寫類
(2)通過4種方式獲取到該類的Class對象
(3)用Class對象創建這個類的對象
(4)再通過Class對象操作

4種獲取Class對象的方法:
1)前提:若已知具體的類,通過類的class屬性獲取,該方法
最爲安全可靠,程序性能最高
實例:

Class clazz = String.class;

2)前提:已知某個類的實例,調用該實例的getClass()方法獲
取Class對象
實例:

Class clazz = “www.atguigu.com”.getClass();

3)前提:已知一個類的全類名,且該類在類路徑下,可通過
Class類的靜態方法forName()獲取,可能拋ClassNotFoundException
實例:

Class clazz = Class.forName(“java.lang.String”);

4)其他方式

ClassLoader cl = this.getClass().getClassLoader();
Class clazz4 = cl.loadClass(“類的全類名”);

反射在開發的應用有如下幾個方面:

一、在運行期間:動態得獲取各個類的詳細信息

1.獲取某個類型的Class對象
2.使用Class和java.lang.reflect包下面的其他類型的API

Class cls = Class.forName("com.jdk"); 
//返回true 
System.out.print(cls.isInstance(new jdk())); 
二、在運行期間:動態創建任意類型的對象

1.Class對象.newInstance()
前提:這個類必須有無參構造
步驟:(1)獲取Class對象
(2)直接調用Class對象.newInstance()

Class cls = Class.forName("com.jdk"); 
jdk jdkobj = cls.newInstance(); 

2.構造器來創建對象
步驟:(1)獲取Class對象
(2)獲取構造器對象
(3)用構造器創建對象

Constructor<T> getDeclaredConstructors(Class<?>... parameterTypes)
//構造器可以重載有很多個,如何確定需要哪一個?
通過形參列表
三、在運行期間:動態的爲對象的屬性賦值

步驟:
1.獲取Class對象
2.獲取Field屬性對象
3.創建實例對象,Class代表的類型的實例對象
4.調用Field對象.set(實例對象,屬性值)
調用Field對象. get(實例對象
如果屬性是私有的,可以用
Field對象.setAccessible(true)

Class cls = Class.forName("com.jdk"); Methods methods[]= cls.getDecliedMethod(); Fields  fields[] = cls.getDeclieredFields(); 
四、在運行期間:動態的調用任意對象的任意方法

步驟:
1.獲取Class對象
2.獲取Method方法
3.創建實例對象
4.調用方法

Class cls = Class.forName("com.jdk"); 
Methods methods[]= cls.getDecliedMethod(); 
jdk jdkobj = new jdk(); 
String returnvalue = methods.invoke(jdkobj,null) 

獲取泛型

Type type = cla.getGenericSuperclass();

獲取註釋

cla.getAnnotation(annotationClass);

動態代理底層也是用的反射
我們之前用的都是靜態代理

// 委託接口
public interface IHelloService {

    /**
     * 定義接口方法
     * @param userName
     * @return
     */
    String sayHello(String userName);

}
// 委託類實現
public class HelloService implements IHelloService {

    @Override
    public String sayHello(String userName) {
        System.out.println("helloService" + userName);
        return "HelloService" + userName;
    }
}

// 代理類
public class StaticProxyHello implements IHelloService {

    private IHelloService helloService = new HelloService();

    @Override
    public String sayHello(String userName) {
        /** 代理對象可以在此處包裝一下*/
        System.out.println("代理對象包裝禮盒...");
        return helloService.sayHello(userName);
    }
}
// 測試靜態代理類
public class MainStatic {
    public static void main(String[] args) {
        StaticProxyHello staticProxyHello = new StaticProxyHello();
        staticProxyHello.sayHello(" ttt");
    }
}
//靜態代理比較容易理解, 需要被代理的類和代理類實現自同一個接口, 然後在代理類中調用真正實現類, 
//並且靜態代理的關係在編譯期間就已經確定了。而動態代理的關係是在運行期間確定的。靜態代理實現簡單,
//適合於代理類較少且確定的情況,而動態代理則給我們提供了更大的靈活性。

代理對象和被代理對象都要實現同一個接口,代碼冗餘

動態代理工作處理器必須實現接口:InvocationHandler
這個方法
(1)它不是由程序員手動調用的,這個方法的代碼會被編譯器自動生成到代理類的對應方法中,當你調用代理類的方法時,自動執行這個方法的代碼

(2)參數列表
第一個參數: proxy代理類對象
第二個參數: method代理類要執行的真正的方法,例如: insert, update…
第三個參數:給 method方法的實參列表,如果有的返回值: method方法的返回值,就是 invoke的返回值
(3)編寫代理類要被代理者完成的工作,例如:給所有方法都增加一個功能,統計該方法的運行時間

2、動態的生成代理類及其它的對象
這個時候就需要藉助java.lang. reflect. Proxy
Proxy提供用於創建動態代理英和實例的態方法

static Object newproxy Instance(Classloader loader, Class<?>0) interfaces, Invocationhandler h)

第一個數:傳入被代理者的類加載器
第二個數:傳入被代理者實現的接口們
第三個參數:傳入代理者要替被代理者完成的工作的處理對象

// 委託類接口
public interface IHelloService {

    /**
     * 方法1
     * @param userName
     * @return
     */
    String sayHello(String userName);

    /**
     * 方法2
     * @param userName
     * @return
     */
    String sayByeBye(String userName);

}
// 委託類
public class HelloService implements IHelloService {

    @Override
    public String sayHello(String userName) {
        System.out.println(userName + " hello");
        return userName + " hello";
    }

    @Override
    public String sayByeBye(String userName) {
        System.out.println(userName + " ByeBye");
        return userName + " ByeBye";
    }
}
// 中間類
public class JavaProxyInvocationHandler implements InvocationHandler {

    /**
     * 中間類持有委託類對象的引用,這裏會構成一種靜態代理關係
     */
    private Object obj ;

    /**
     * 有參構造器,傳入委託類的對象
     * @param obj 委託類的對象
     */
    public JavaProxyInvocationHandler(Object obj){
        this.obj = obj;

    }

    /**
     * 動態生成代理類對象,Proxy.newProxyInstance
     * @return 返回代理類的實例
     */
    public Object newProxyInstance() {
        return Proxy.newProxyInstance(
                //指定代理對象的類加載器
                obj.getClass().getClassLoader(),
                //代理對象需要實現的接口,可以同時指定多個接口
                obj.getClass().getInterfaces(),
                //方法調用的實際處理者,代理對象的方法調用都會轉發到這裏
                this);
    }


    /**
     *
     * @param proxy 代理對象
     * @param method 代理方法
     * @param args 方法的參數
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("invoke before");
        Object result = method.invoke(obj, args);
        System.out.println("invoke after");
        return result;
    }
}
// 測試動態代理類
public class MainJavaProxy {
    public static void main(String[] args) {
        JavaProxyInvocationHandler proxyInvocationHandler = new JavaProxyInvocationHandler(new HelloService());
        IHelloService helloService = (IHelloService) proxyInvocationHandler.newProxyInstance();
        helloService.sayByeBye("ttt");
        helloService.sayHello("www");
    }

}
//JDK 動態代理所用到的代理類在程序調用到代理類對象時才由 JVM 真正創建,JVM 根據傳進來的 
//業務實現類對象 以及 方法名 ,動態地創建了一個代理類的 class 文件並被字節碼引擎執行,然後
//通過該代理類對象進行方法調用。我們需要做的,只需指定代理類的預處理、調用後操作即可。

總結

Java反射機制讓身爲靜態語言的Java有一定動態性,增加程序的靈活性,避免將程序寫死到代碼裏。目前框架大部分都是用了反射機制。
反射也會帶來性能問題,使用反射基本上是一種解釋操作,用於字段和方法接入時要遠慢於直接代碼。因此Java反射機制主要應用在對靈活性和擴展性要求很高的系統框架上,普通程序不建議使用。

參考:
類加載機制-深入理解jvm
Java 靜態代理、Java動態代理、CGLIB動態代理

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