[轉]Java 反射在實際開發中的應用

來源:互聯網
上載者:User

標籤:pac   war   靜態成員   動態建立   shp   tar   int   roo   sse   

  • 一:Java類載入和初始化
    • 1.1 類載入器(類載入的工具)
    • 1.2 Java使用一個類所需的準備工作
  • 二:Java中RTTI  
    • 2.1 :為什麼要用到運行時類型資訊(就是RTTI)
    • 2.2  :RTTI在運行時如何表示
    • 2.3   :  Class對象
    • 2.3 : RTTI形式總結:
  • 三:Java利用反射擷取運行時類型資訊
    • 3.1 : 擷取的方式
    • 3.2 :   動態代理
  • 四: Java反射在實際開發中應用
    • 4.1  :在web項目中建立統一的攔截層 
    • 4.2 : 用於webService服務 :和servlet做同意攔截,用反射去調用方法的目的一樣(添加一些想要的處理,比如校正使用者)。核心也是反射調用方法

 

本文

    運行時類型識別(RTTI, Run-Time Type Information)是Java中非常有用的機制,在java中,有兩種RTTI的方式,一種是傳統的,即假設在編譯時間已經知道了所有的類型;還有一種,是利用反射機制,在運行時再嘗試確定類型資訊。

  本篇博文會結合Thinking in Java 的demo 和實際開發中碰到的例子,對Java反射和擷取類型資訊做總體上整理。文章主要分為三塊:

  •   Java類載入和初始化
  •   Java中RTTI
  •   Java利用反射擷取運行時類型資訊
回到頂部一:Java類載入和初始化

  在學習RTTI的時候,首先需要知道Java中類是如何載入的,java又是如何根據這些class檔案得到JVM中需要的資訊(備忘:我在此處實在是想不到更好的描述,望讀者可以給出更好的描述)

1.1 類載入器(類載入的工具)

  類載入器子系統包含一條載入器鏈,只有一個“原生的類載入器”他是jvm實現的一部分,可以用來記載本地jar包內的class,若涉及載入網路上的類,或者是web伺服器應用,可以掛接額外的類載入器。

1.2 Java使用一個類所需的準備工作1.2.1 動態載入

  所有的類都是第一次使用的時候,動態載入到JVM中。建立對類的靜態成員的引用,載入這個類。Java程式在開始啟動並執行時候並非完全載入,類都是用的地方在載入,這就是動態載入

  ①:首先檢查這個類是否被載入

  ②:如果沒有載入,再去根據類名尋找.class檔案,載入類的位元組碼,並校正是否存在不良代碼,

測試代碼如下:

//candy.javapublic class Candy {    static {        System.out.println("loading Candy");    }}//cookie.javapublic class Cookie {    static {        System.out.println("loading Cookie");    }}//Gum.javapublic class Gum {    static {        System.out.println("loading Gum");    }}//TestMain.javapublic class TestMain {    public static void main(String[] args) {        System.out.println("inside main");        new Candy();        System.out.println("After create Candy");        try {            Class.forName("com.RuntimeTypeInformation.Gum");        } catch (ClassNotFoundException e) {            System.out.println("Could not find Class");        }        System.out.println("After Class.forName");        new Cookie();        System.out.println("After new Cookie()");            }    static void printClassInfo(Class c){        System.out.println("Class Name :"+c.getName()                    +"is interface? :" + c.isInterface()                    +"simple Name "+ c.getSimpleName()                    );                   }

 從輸出結果可以清楚看到;class對象僅在需要的時候才會載入,static初始化是在類載入的時候進行

1.2.2 連結

  驗證類中的位元組碼,為靜態域分配儲存空間。如果必須的話,將解析這個類建立的對其他類的所有引用

1.2.3 初始化

  如果該類存在超類,對其初始化,執行靜態初始化器和靜態代碼塊。初始化延遲至 對靜態方法或者非靜態方法首次引用時執行

回到頂部二:Java中RTTI  

2.1 :為什麼要用到運行時類型資訊(就是RTTI)

實際開發中,需求並不是一成不變的(準確來說是經常變),而每新添加需求如果代碼的改動量越小肯定是越能提高效率。比如:

package com.RuntimeTypeInformation.circle;import java.util.Arrays;import java.util.List;abstract class Shape {    void draw(){        System.out.println(this+".draw()");    }    abstract public String toString();}class Circle extends Shape{    @Override    public String toString() {        return "Circle";    }    }class Triangle extends Shape{    @Override    public String toString() {        return "Triangle";    }    }public class Shapes{    public static void main(String[] args) {        //題外話,Arrays.asList 可變參數列表,可以把傳入的多個對象轉為一個list        List<Shape> shapes = Arrays.asList(new Triangle(),new Circle());        for (Shape shape : shapes) {            shape.draw();        }    }}

  當我想要添加一個新的形狀,比如說長方形,我只需要編寫一個新類繼承Shape即可,而不需要修改調用的地方 。在這裡用到了 ”多態“(雖然調用的都是shpe的方法,但是JVM能在運行期

準確的知道應該調用具體哪個子類的方法)

  當你第一次瞭解"多態",你可能是簡單知道墮胎就是這麼一回事,那麼,現在我們去研究一下,java是怎樣處理的.

    ① 當把Triangle,Circle 放到 List<Shape>時,會向上轉型為Shape,丟失具體的類型

    ② 當從容器中取出Shape對象的時候,List內實際存放的是Object, 在運行期自動將結果轉為Shape,這就是RTTI的工作( 在運行時識別一個對象的類型

這時候,如果客戶需求又改了,說不希望畫的結果存在圓形。應對這種需求,我們可以採用RTTI 查詢某個shape引用所指向的具體類型(具體怎麼用,可以接著往下看)

2.2  :RTTI在運行時如何表示

  Java的核心思想就是:”一切皆是對象“,比如我們對形狀抽象,得到圓形類,三角形類。但我們 對這些類在做一次抽象,得到class用於描述類的一般特性

是我用畫圖畫的(有點撈見諒),如果我們可以拿到對象的class,我們就可以利用RTTI得到具體的java類。至於如何拿到Class和怎樣用Class得到準確的類,繼續往下看。

2.3   :  Class對象

   每一個類都存在與之對應的Class對象(儲存在.class檔案中),根據class得到具體的對象,請參考“第一章節 類的載入和初始化”

2.3.1 Class對象擷取的方式

    ①:Class.forName("全限定類名"),得到Class對象,副作用是“如果對應的類沒有載入,則會載入類”。找不到會拋出“”ClassNotFoundException”

    ②:如果有對象,可以直接用對象得到與之對應的Class對象  比如  

Shape shape  = new Circle();shape.getClass()

    ③ ;通過類字面常量  : Shape.class.推薦用該方法,第一是編譯器會做檢查,第二是根除了對forName的調用,提高效率

2.3.2: Class對象的常用方法  

 

方法名 說明
forName() (1)擷取Class對象的一個引用,但引用的類還沒有載入(該類的第一個對象沒有產生)就載入了這個類。
(2)為了產生Class引用,forName()立即就進行了初始化。
Object-getClass() 擷取Class對象的一個引用,返回表示該對象的實際類型的Class引用。
getName() 取全限定的類名(包括包名),即類的完整名字。
getSimpleName() 擷取類名(不包括包名)
getCanonicalName() 擷取全限定的類名(包括包名)
isInterface() 判斷Class對象是否是表示一個介面
getInterfaces() 返回Class對象數組,表示Class對象所引用的類所實現的所有介面。
getSupercalss() 返回Class對象,表示Class對象所引用的類所繼承的直接基類。應用該方法可在運行時發現一個對象完整的繼承結構。
newInstance() 返回一個Oject對象,是實現“虛擬構造器”的一種途徑。使用該方法建立的類,必須帶有無參的構造器
getFields() 獲得某個類的所有的公用(public)的欄位,包括繼承自父類的所有公用欄位。 類似的還有getMethods和getConstructors。
getDeclaredFields 獲得某個類的自己聲明的欄位,即包括public、private和proteced,預設但是不包括父類聲明的任何欄位。類似的還有getDeclaredMethods和getDeclaredConstructors。
2.3.3  泛化的Class 

  Class參考資料表示它所指向的對象的確切類型,java1.5之後,允許開發人員對Class引用所指向的Class對象進行限定,也就是添加泛型。

public static void main(String[] args) {        Class<Integer> intclass = int.class;        intclass = Integer.class;    }

 

  這樣可以在編譯器進行類型檢查,當然可以通過 “萬用字元” 讓引用泛型的時候放鬆限制 ,文法 : Class<?>

目的:

  ①:為了可以在編譯器就做類型檢查

  ② : 當 Class<Circle> circle = circle.getClass(); circle.newInstance() 會得到具體的類型 。但此處需注意:

public class Shapes{    public static void main(String[] args) throws InstantiationException, IllegalAccessException {        Class<Circle> circles = Circle.class;        Circle circle = circles.newInstance();//第一:泛化class.newInstance可以直接得到具體的對象        Class<? super Circle> shape = circles.getSuperclass();        Object shape1 = shape.newInstance();//第二:它的父類,只能用逆變的泛型class接收,newInstance得到的是Object類型     }}

2.3 : RTTI形式總結:

  ①:傳統的類型轉換,比如我們在上邊的demo中用到的  shape.draw();

  ②:利用Class,擷取運行時資訊。

  ③:得到具體的對象

回到頂部三:Java利用反射擷取運行時類型資訊

  如果不知道某一個對象引用的具體類型(比如已經上轉型的對象),RTTI可以得到。但前提是這個類型編譯器必須已知(那些是編譯期不可知呢? 磁碟檔案或者是網路連接中擷取一串代表類的位元組碼)

跨網路的遠程平台上提供建立和運行對象的能力 這被稱為 RMI(遠程方法調用),下面會具體的介紹一下 RMI的實現方式

  反射提供了一種機制,用於檢查可用的方法,並返回方法名,調用方法。

3.1 : 擷取的方式

   Java中提供了jar包 ,Java.lang.reflect 和Class對象一起對反射的概念提供支援。

3.1.1 Java.lang.reflect :

  該類庫中包含了Field Method  Constructor.這些類型的對象在JVM運行時建立,用於表示未知類裡對應的成員。從而:

  ①:用Constructor建立對象,用get set讀取Field內的欄位

  ②:用Method.invoke()調用方法

  ③:用getFields()、getMethods()、getConstuctors() 得到與之對應的數組

3.1.2 RTTI和RMI的區別

   檢查對象,查看對象屬於哪個類,載入類的class檔案

  ①:RTTI會在編譯期開啟和檢查.class檔案

  ②:RMI  在編譯期是 看不到.class檔案。只能在運行期開啟和檢查.class檔案

3.2 :   動態代理3.2.1 我假設你對 “代理模式”存在一定的瞭解(還是簡單說一下,代理模式就是在介面和實現之前加一層,用於剝離介面的一些額外的操作)下面是代理模式的範例程式碼:
public interface Subject   {     public void doSomething();   }   public class RealSubject implements Subject   {     public void doSomething()     {       System.out.println( "call doSomething()" );     }   }   public class ProxyHandler implements InvocationHandler   {     private Object proxied;          public ProxyHandler( Object proxied )     {       this.proxied = proxied;     }          public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable     {       //在轉調具體目標對象之前,可以執行一些功能處理    //轉調具體目標對象的方法    return method.invoke( proxied, args);          //在轉調具體目標對象之後,可以執行一些功能處理  }    }

 

3.2.2  動態代理就是:動態建立代理並動態地處理對其所代理的方法的調用。可以參考 "徹底理解JAVA動態代理" ,"深度剖析JDK動態代理機制"。可以理解為更加靈活的代理模式

①  動態代理使用步驟:

  1.通過實現InvocationHandler介面來自訂自己的InvocationHandler;

  2.通過Proxy.getProxyClass獲得動態代理類 

  3.通過反射機制獲得代理類的構造方法,方法簽名為getConstructor(InvocationHandler.class)    4.通過建構函式獲得代理對象並將自訂的InvocationHandler執行個體對象傳為參數傳入    5.通過代理對象調用目標方法
public class MyProxy {    public interface IHello{        void sayHello();    }    static class Hello implements IHello{        public void sayHello() {            System.out.println("Hello world!!");        }    }    //自訂InvocationHandler    static  class HWInvocationHandler implements InvocationHandler{        //目標對象        private Object target;        public HWInvocationHandler(Object target){            this.target = target;        }        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {            System.out.println("------插入前置通知代碼-------------");            //執行相應的目標方法            Object rs = method.invoke(target,args);            System.out.println("------插入後置處理代碼-------------");            return rs;        }    }    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {       //產生$Proxy0的class檔案       System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");       IHello  ihello = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(),  //載入介面的類載入器               new Class[]{IHello.class},      //一組介面               new HWInvocationHandler(new Hello())); //自訂的InvocationHandler       ihello.sayHello();   }}

②  :動態代理的原理,列舉一下參考文獻把:(本質上還是用到了反射)

1、JDK動態代理實現原理

2、Java動態代理機制分析及擴充

③  動態代理應用以及備忘說明 :

  JDK實現動態代理需要實作類別通過介面定義業務方法 (接下來我會簡單說一下Cglib實現動態代理)。第二是動態代理非常重要 是反射一個極其重要的模組,很多架構都離不開動態代理,比如Spring 。所以,推薦讀者在多去研究一下。

④:Cglib實現動態代理

  參考文檔: cglib動態代理介紹(一)

  CGLIB是一個強大的高效能的代碼產生包。它廣泛的被許多AOP的架構使用,例如spring AOP和dynaop,為他們提供方法的interception(攔截)。最流行的OR Mapping工具hibernate也使用CGLIB來代理單端single-ended(多對一和一對一)關聯(對集合的延遲抓取,是採用其他機制實 現的)。EasyMock和jMock是通過使用模仿(moke)對象來測試Java代碼的包。它們都通過使用CGLIB來為那些沒有介面的類建立模仿 (moke)對象。
  CGLIB包的底層是通過使用一個小而快的位元組碼處理架構ASM,來轉換位元組碼並產生新的類。除了CGLIB包,指令碼語言例如 Groovy和BeanShell,也是使用ASM來產生java的位元組碼。當不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class文 件的格式和指令集都很熟悉

  "在運行期擴充java類及實現java介面",補充的是java動態代理機制要求必須實現了介面,而cglib針對沒實現介面的那些類,原理是通過繼承這些類,成為子類,覆蓋一些方法,所以cglib對final的類也不生效

cglib實現動態代理的demo:參考  CGLib動態代理原理及實現

  這是要代理的類:

public class SayHello {   public void say(){    System.out.println("hello everyone");   }  }  

  代理類的核心

public class CglibProxy implements MethodInterceptor{   private Enhancer enhancer = new Enhancer();   public Object getProxy(Class clazz){    //設定需要建立子類的類    enhancer.setSuperclass(clazz);    enhancer.setCallback(this);    //通過位元組碼技術動態建立子類執行個體    return enhancer.create();   }   //實現MethodInterceptor介面方法   public Object intercept(Object obj, Method method, Object[] args,     MethodProxy proxy) throws Throwable {    System.out.println("前置代理");    //通過代理類調用父類中的方法    Object result = proxy.invokeSuper(obj, args);    System.out.println("後置代理");    return result;   }  }  

測試結果:

public class DoCGLib {   public static void main(String[] args) {    CglibProxy proxy = new CglibProxy();    //通過產生子類的方式建立代理類    SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.class);    proxyImp.say();   }  }  

 

回到頂部四: Java反射在實際開發中應用

   通常,我們在一般的業務需求中是用不到反射的,但我們在更加動態代碼時,我們就可以選擇反射來實現(例如對象序列化和 JavaBean)。主要的邏輯我在上邊都已經說明了,所以接下來 更多的是代碼展示:

    實際開發中,在運行時得到Class資訊,擷取method ,通過反射method.invoke()調用方法。這樣做是出於AOP的設計思想。舉例來說,我一個傳統的web項目,我可以同過http直接傳遞請求給後台servlet,假如我想添加一個記錄日誌,或者是在請求的session中添加一個資訊,如果只有一個請求,我可以直接在htttp加,但實際上請求會很多,這是我為什麼在sevlet外在抽出一層,通過反射調用servlet 

    當然,很多架構其實也為我們提供了攔截的配置(這是後話)

4.1  :在web項目中建立統一的攔截層 
doPost(..){//這是項目中的setvlet統一的攔截層,接下來我們看一下 actionInvoker.invoke  ...  else if (requestType.equalsIgnoreCase("image")) {                try {                    ActionClassInfo actionClassInfo = actionInvoker.getClassInfo(action, request, response);                    actionClassInfo.setArgs(queryStringMap);                    Object object = actionInvoker.invoke(actionClassInfo);                    response.addHeader("accept-ranges", "bytes");                    byte[] bytes = (byte[]) object;                    response.addHeader("Content-type", "application/png");                    response.addHeader("content-length", String.valueOf(bytes.length));                    response.getOutputStream().write(bytes, 0, bytes.length);                } catch (Exception e) {                    e.printStackTrace();                } catch (Throwable e) {                    e.printStackTrace();                } finally {                    response.getOutputStream().flush();                    response.getOutputStream().close();                }            }    }

 actionInvoker.invoke()方法代碼如下: 在這方法內,我就可以添加我想要的處理,比如先判斷是否在緩衝中存在,核心的只有 method.invoke

public Object invoke(ActionClassInfo action) throws Exception {        // 執行方法之前        Object cache = null;        for (Object object : action.getProxys()) {            if (object instanceof Intercepter){                cache = ((Intercepter) object).before(action);                if(cache != null && object instanceof RedisCacheHandler){                    return cache;    //緩衝的結果直接返回                }            }        }        Method method = action.getMethod();        Object business = action.getClazz();        Map<Object, Object> args = action.getArgs();        method.setAccessible(true);        Object result = method.invoke(business, args);        // 執行方法後        for (Object object : action.getProxys()) {            if (object instanceof Intercepter)                result = ((Intercepter) object).after(result, action);        }        return result;    }

 

4.2 : 用於webService服務 :和servlet做同意攔截,用反射去調用方法的目的一樣(添加一些想要的處理,比如校正使用者)。核心也是反射調用方法

 

(原文地址:  https://www.cnblogs.com/ldh-better/p/7148975.html#_label1_0 )

[轉]Java 反射在實際開發中的應用

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.