Java反射,註解,以及動態代理

来源:https://www.cnblogs.com/xiaohuiduan/archive/2019/01/23/10306968.html
-Advertisement-
Play Games

Java反射,註解,以及動態代理 基礎 最近在準備實習面試,被學長問到了Java反射,註解和動態代理的內容,發現有點自己有點懵,這幾天查了很多資料,就來說下自己的理解吧【如有錯誤,望指正】 Java反射 首先,我們得弄清一個,什麼是反射(Reflection)。簡單的來說,反射就是讓我們在程式運行的 ...


Java反射,註解,以及動態代理

基礎 

最近在準備實習面試,被學長問到了Java反射,註解和動態代理的內容,發現有點自己有點懵,這幾天查了很多資料,就來說下自己的理解吧【如有錯誤,望指正】

Java反射

首先,我們得弄清一個,什麼是反射(Reflection)。簡單的來說,反射就是讓我們在程式運行的時候能夠查看到類的信息,獲取並調用類的任意方法和屬性。

在Java運行時,系統會將所有的對象維護一個被稱為運行是的類型標識,然後這個信息跟蹤這每個對象所屬的類,我們可以和根據Java專門的類訪問這些信息,這個類就是Class【實際上Class對象表示的是一個類型,它不一定是類,可能是基本數據類型,比如int】。

Class獲取方法

  1. 通過getClass()獲取

    Student stu = new Student;
    Class c =  stu.getClass();
    //如果這個類在包裡面,則會將包名也列印出來
    // getSimpleName只獲得類名
    System.out.println(c.getName());
    
  2. 使用forName獲取
    同樣,我們也可以使用靜態方法forName獲得Class對象。例如:

    String className= "java.util.Random";
    Class c2 = Class.forName(className);
    

    當然,className必須為介面或者類名才能成功。

  3. 直接獲取

    Class c3 = Student.class;
    

由Class得到對象

  1. 使用Class對象的newInstance()方法來創建Class對象

    Class c3 = Test.class;
    Object o = c3.newInstance();
    

    其中newInstance()會根據類的預設構造器【無參構造器】創建新的對象,如果沒有預設的構造器,就會報錯。假如我們的構造器裡面有參數怎麼辦,這時候我們就需要使用java.lang.reflect.Constructor中的newInstance()方法了。

  2. 使用Constructor類中的newInstance()

    // getConstructor裡面是構造參數的形參內容
    Constructor constructor = c3.getConstructor(String.class);
    Object o = constructor.newInstance("你好");  
    
    

java反射中最重要的內容——檢查類的結構

在Java的java.lang.reflect中有三個類:Field、Method、Constructor分別來描述類的域【也就是變數】,方法和構造器。

  1. Field的獲取以及方法

    Class textClass = Test.class;
    // getDeclaredFields()獲得這個類的全部域
    // getField()獲得公有域以及其父類的公有域
    Field[] fields = textClass.getDeclaredFields();
    

    簡單的來說,通過Field可以獲得:

    變數的許可權——getModifiers(),返回int,然後通過Modifier.toString(int)獲得訪問許可權

    獲得變數的類型——getType()

    變數的名字——getName()

  2. Method的獲取以及方法

    Class textClass = Test.class;
    // 同樣可以使用getMethods()和getDeclaredMethods()返回介面和類的方法
    Method[] methods = textClass.getMethods();
    

    通過Method可以獲取:

    方法的許可權——getgetModifiers()

    方法的返回值類型——getReturnType(),方法返回類型為Class,然後你懂得。

    方法的所有參數——Parameter[] parameters = method.getParameters();

    方法的執行——invoke()。在獲取一個方法後,我們可以使用invoke()來調用這個方法。

    Object invoke(Object obj,Object...args),obj為實例化後的對象【對於靜態方法可以被設置null】,args為方法調用的參數

    例如,

    public class Test {
    
        public void say(String msg){
            System.out.println(msg);
        }
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    
            Class c = Test.class;
            // 返回唯一的方法,第一個參數是方法的名字,第二個是方法參數的類型
            Method method = c.getMethod("say", String.class);
            Object o = c.newInstance();
            method.invoke(o,"你好");
        }
    }
    
  3. Constructor的獲取以及方法

    Class textClass = Test.class;
    // 同樣getDeclaredConstructors()和getConstructors()
    Constructor[] constructors = aClass.getConstructors();
    

    方法的的使用和Method差不多,但是它沒有getReturnType()方法。

這些方法我只是簡單的介紹了一下,詳細信息可以參考API。

神奇的Java註解

Java註解可以很簡單的說,就是為方法或者其他數據提供描述的東西。

它的本質就是一個介面,一個繼承了Annotation的介面。

  1. 基本java註解的類型
    【元註解】:也就是在自定義一個註解時,可以註解在註解上面,有以下幾個元註解——>

    • @Target:註解的作用目標,用來指明註解可以作用的目標是誰,例如類,方法或者欄位屬性,裡面的value【為一個ElementType數組】可以指明值如下:

      ElementType.TYPE:允許被修飾的註解作用在類、介面和枚舉上

      ElementType.FIELD:允許作用在屬性欄位上

      ElementType.METHOD:允許作用在方法上

      ElementType.PARAMETER:允許作用在方法參數上

      ElementType.CONSTRUCTOR:允許作用在構造器上

      ElementType.LOCAL_VARIABLE:允許作用在本地局部變數上

      ElementType.ANNOTATION_TYPE:允許作用在註解上

      ElementType.PACKAGE:允許作用在包上

    • @Retention:註解的生命周期,裡面的value【枚舉類型】可以指明值如下:

      RetentionPolicy.SOURCE:當前註解編譯期可見,不會寫入 class 文件

      RetentionPolicy.CLASS:類載入階段丟棄,會寫入 class 文件

      RetentionPolicy.RUNTIME:永久保存,可以反射獲取
      - @Documented:註解是否應當被包含在 JavaDoc 文檔中
      - @Inherited:是否允許子類繼承該註解
      - @Repeatable:重覆註解,允許這個註解在某個方法和其他數據上面重覆使用

    【Java內置三大註解】:除了上述元註解,Java還內置了另外三種註解——>

    • @Override:子類重寫父類的方法時,會使用該註解。用於檢查父類是否包含該註解
    • @Deprecated:當某一方法和欄位不推薦使用時,使用該註解標註。
    • @SuppressWarnings:壓制Java的警告
  2. Java註解的自定義以及實現

    Java註解的自定義如下

    @Target(value = {ElementType.METHOD,ElementType.TYPE}) // 註解的作用地方
    @Retention(value = RetentionPolicy.RUNTIME)  // 註解的生命周期
    public @interface TestAnnotation {
        String name() default "這是個類";
        int time();
    }
    

    那麼我們該如果如何使用註解發揮作用呢?我們可以想想,如果我們能夠獲得註解的信息,那麼我們是不是就可以根據註解的信息來對方法做適當的調整。這時候,當然是大名鼎鼎的反射出馬了。

    • java.lang.Package.getAnnotation(Class<A> annotationClass) 獲得這個指令類型的註解。

    使用如下:

    @TestAnnotation(time = 0)
    public class Test {
    
        @TestAnnotation(name = "這是個方法",time = 1)
        public void say(){
        }
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
            // 獲得類上面的註解
            TestAnnotation classAnnotation = Test.class.getAnnotation(TestAnnotation.class);
            System.out.println("類的名字為:"+classAnnotation.name()+"------類的時間是"+classAnnotation.time());
            Method method = Test.class.getMethod("say");
    
            // 獲得方法上面的註解
            TestAnnotation methodAnnotation = method.getAnnotation(TestAnnotation.class);
            System.out.println("方法的名字是:"+methodAnnotation.name()+"------方法的時間是"+methodAnnotation.time());
        }
    }
    
    // 輸出:
    // 類的名字為:這是個類------類的時間是0
    // 方法的名字是:這是個方法------方法的時間是1
    

    現在我們知道如何進行自定義註解的使用了,那麼我們怎麼能夠根據註釋內容的不同去改變方法的執行呢?這時候,我們我們就可以使用invoke()方法了。

    舉個最簡單的慄子:

    @TestAnnotation(name = "你好")
        public void say(String msg){
            System.out.println("信息是:"+msg);
        }
        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
    
            Method method = Test.class.getMethod("say",String.class);
            // 獲得方法上面的註解
            TestAnnotation methodAnnotation = method.getAnnotation(TestAnnotation.class);
            // 執行方法
            method.invoke(Test.class.newInstance(),methodAnnotation.name());
        }
        // 輸出結果:
        // 信息是:你好
    

代理

代理就是給某個對象提供一個代理對象,並由代理對象控制對於原對象的訪問,即客戶不直接操控原對象,而是通過代理對象間接地操控原對象。
代理分為:

  • 靜態代理:代理類是在編譯時就已經實現好了,成為了一個class文件
  • 動態代理:是在程式運行時動態地生成類位元組碼,然後載入到JVM中

有幾個概念:

  1. 抽象角色:介面類
  2. 實現角色:實現類
  3. 代理角色:代理實現的類,最終使用的對象

靜態代理

在說動態代理之前,我們先說一下靜態代理,靜態代理很簡單,就是工廠模式。

那麼就讓我們來實現一下靜態代理吧

抽象角色:介面類

public interface TestService {
    void say();
    void play();
}

實現角色:實現類

public class TestServiceImpl implements TestService {

    @Override
    public void say() {
        System.out.println("說話乎");
    }

    @Override
    public void play() {
        System.out.println("浪的飛起");
    }
}

代理類

public class Test implements TestService{

    private TestService testService;

    public Test(TestService testService) {
        this.testService = testService;
    }

    @Override
    public void say() {
        System.out.println("開始說話");
        testService.say();
        System.out.println("結束說話");
    }

    @Override
    public void play() {
        System.out.println("開始浪");
        testService.play();
        System.out.println("是個狼人");
    }

    public static void main(String[] args) {
        TestServiceImpl testImpl = new TestServiceImpl();
        Test test = new Test(testImpl);
        test.play();
        test.say();
    }
}

在這裡面,我們可以看到,從外表看起來say()play()方法都是由test這個代理來完成的,但實際上,真正的執行者是TestServiceImpl來完成的,test只是在執行的時候加了一些事務邏輯。

既然有了靜態代理,為什麼我們還需要動態代理呢?從代碼中可以看出,代理類和實現類是一一對應的,如果我們有N個實現類,都要在方法執行前加一樣的邏輯,那麼我們不得不創建N個代理類。這時候,我們就需要使用動態代理了。

動態代理

本次動態代理是針對JDK動態代理進行探討。

正如前面所說,如果我們要在很多類使用同一種邏輯時,會心態爆炸,那麼我們怎麼去解決這個問題呢,這時候,我們可以想一想反射。

在使用的動態代理的過程中,有兩個關鍵的東東,一個是InvocationHandler介面,一個是Proxy類。

  • InvocationHandler

每一個動態代理類都必須要實現InvocationHandler這個介面,並且每個代理類的實例都關聯到了一個handler,當我們通過代理對象調用一個方法的時候,這個方法的調用就會被轉發為由InvocationHandler這個介面的 invoke 方法來進行調用。

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

proxy:  指代我們所代理的那個真實對象,也就是實現類

method:  指代的是我們所要調用真實對象的某個方法的Method對象

args:  指代的是調用真實對象某個方法時接受的參數

  • Proxy

Proxy這個類的作用就是用來動態創建一個代理對象的類

其中我們使用最多是newProxyInstance()去創建代理類

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

loader:一個ClassLoader對象,定義了由哪個ClassLoader對象來對生成的代理對象進行載入

interfaces:一個Interface對象的數組,表示的是我將要給我需要代理的對象提供一組什麼介面,如果我提供了一組介面給它,那麼這個代理對象就宣稱實現了該介面(多態),這樣我就能調用這組介面中的方法了

h:一個InvocationHandler對象,表示的是當我這個動態代理對象在調用方法的時候,會關聯到哪一個InvocationHandler對象上

創建一個代理類,實現方法調用前或後的邏輯

public class TestHandler implements InvocationHandler{

    // object為實現類的對象
    private Object object;

    public TestHandler(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("開始方法執行");
        Object o = method.invoke(object,args);
        System.out.println("方法結束");
        return o;
    }
}

實例化代理類,並

public static void main(String[] args) {

    // 實現類
    TestService testService = new TestServiceImpl();

    // 裡面傳入要代理的實現類對象
    TestHandler testHandler = new TestHandler(testService);
    /**
    * testService.getClass().getClassLoader()  代表我們使用這個類來載入我們代理對象
    * testService.getClass().getInterfaces()  代表我們調用這些介面中的方法
    * testHandler  將代理對象與testHandler關聯
    */
    TestService service = (TestService) Proxy.newProxyInstance(testService.getClass().getClassLoader(),
            testService.getClass().getInterfaces(),testHandler);
    service.play();
    service.say();

反射,註解,以及動態代理就簡單地介紹完了,可以這樣說反射是註解以及動態代理的基礎,註解的實現和動態代理都要靠反射發揮作用。

還是多讀下書吧,面試實習是把殺豬刀

 


 

 

 


 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • if (window.performance) { console.info("window.performance is supported"); console.log(performance.navigation.type) } ...
  • 一、 主要知識點:html佈局,css變換,js事件觸發 二.CSS屬性記憶: CSS規則:子元素會繼承父元素的佈局屬性。不專本改變子元素的屬性,其會跟隨父元素。 功能 語句 margin 外邊距 Padding 內邊距 關於文本的水平居中為: text-align: center; 關於文本的垂直 ...
  • 1. 博客皮膚選擇 SimpleMemory 因為簡單,所以愛~ 2. 在”頁面定製CSS代碼“中填入以下內容 重要提示:側邊欄藍色風格,有的模塊可能遺漏,需要請自行在CSS樣式中的“側邊欄”加上對應DIV的id或者class 3. 在“首頁Html代碼”中填入以下內容: 說明:此樣式非首創,收集了 ...
  • 作為普通的網民來說,一般不需要知道也不用關心什麼是盜鏈,不過如果你是網站的開發者或維護者,就不得不重視盜鏈的問題了。如果你剛剛開發完一個沒有防盜鏈的帶有文件下載功能的網站,掛上internet,然後上傳幾個時下非常熱門的軟體或電影併在網站內公佈下載地址,讓MSN上的所有好友都來體驗一下你的傑作。 不 ...
  • 參考:https://www.cnblogs.com/lewis0077/p/5133812.html(深入解析策略模式) 參考:https://www.cnblogs.com/lewis0077/p/5133812.html(深入解析策略模式) 定義: 策略模式定義了一系列的演算法,並將每一個演算法封 ...
  • 前言 在上一篇中我們學習了結構型模式的組合模式和過濾器模式。本篇則來學習下結構型模式最後的兩個模式, 享元模式和代理模式。 享元模式 簡介 享元模式主要用於減少創建對象的數量,以減少記憶體占用和提高性能。這種類型的設計模式屬於結構型模式,它提供了減少對象數量從而改善應用所需的對象結構的方式。 用通俗的 ...
  • 基本語法 1.Grovvy的註釋分為//和/**/和java的一樣. 2.Grovvy語法可以不已分號結尾. 3.單引號,裡面的內容嚴格的對應java中的String,不對$符號進行轉義. 4.雙引號“ ”的內容中如果有$號的話,會先對錶達式先求值. 5.三個引號中的字元串支持隨意的換行. 定義變數 ...
  • 1. 手機APP數據 寫在前面 繼續練習pyspider的使用,最近搜索了一些這個框架的一些使用技巧,發現文檔竟然挺難理解的,不過使用起來暫時沒有障礙,估摸著,要在寫個5篇左右關於這個框架的教程。今天教程中增加了圖片的處理,你可以重點學習一下。 2. 手機APP數據 頁面分析 咱要爬取的網站是 這個 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...