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

  本篇博文會結(jié)合Thinking in Java 的demo 和實際開發(fā)中碰到的例子,對Java反射和獲取類型信息做總體上整理。文章主要分為三塊:

  •   Java類加載和初始化

  •   Java中RTTI

  •   Java利用反射獲取運行時類型信息

一:Java類加載和初始化

  在學習RTTI的時候,首先需要知道Java中類是如何加載的,java又是如何根據(jù)這些class文件得到JVM中需要的信息(備注:我在此處實在是想不到更好的描述,望讀者可以給出更好的描述)

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

  類加載器子系統(tǒng)包含一條加載器鏈,只有一個“原生的類加載器”他是jvm實現(xiàn)的一部分,可以用來記載本地jar包內(nèi)的class,若涉及加載網(wǎng)絡(luò)上的類,或者是web服務(wù)器應(yīng)用,可以掛接額外的類加載器。

1.2 Java使用一個類所需的準備工作

1.2.1 動態(tài)加載

  所有的類都是第一次使用的時候,動態(tài)加載到JVM中。創(chuàng)建對類的靜態(tài)成員的引用,加載這個類。Java程序在開始運行的時候并非完全加載,類都是用的地方在加載,這就是動態(tài)加載

 ?、伲菏紫葯z查這個類是否被加載

 ?、冢喝绻麤]有加載,再去根據(jù)類名查找.class文件,加載類的字節(jié)碼,并校驗是否存在不良代碼,

測試代碼如下:

平面設(shè)計培訓,網(wǎng)頁設(shè)計培訓,美工培訓,游戲開發(fā),動畫培訓

//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()
                    );       
        
    }

平面設(shè)計培訓,網(wǎng)頁設(shè)計培訓,美工培訓,游戲開發(fā),動畫培訓

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

1.2.2 鏈接

  驗證類中的字節(jié)碼,為靜態(tài)域分配存儲空間。如果必須的話,將解析這個類創(chuàng)建的對其他類的所有引用

1.2.3 初始化

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

二:Java中RTTI  

2.1 :為什么要用到運行時類型信息(就是RTTI)

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

平面設(shè)計培訓,網(wǎng)頁設(shè)計培訓,美工培訓,游戲開發(fā),動畫培訓

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 可變參數(shù)列表,可以把傳入的多個對象轉(zhuǎn)為一個list
        List<Shape> shapes = Arrays.asList(new Triangle(),new Circle());        for (Shape shape : shapes) {
            shape.draw();
        }
    }
}

平面設(shè)計培訓,網(wǎng)頁設(shè)計培訓,美工培訓,游戲開發(fā),動畫培訓

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

準確的知道應(yīng)該調(diào)用具體哪個子類的方法)

  當你第一次了解"多態(tài)",你可能是簡單知道墮胎就是這么一回事,那么,現(xiàn)在我們?nèi)パ芯恳幌?,java是怎樣處理的.

   ?、?當把Triangle,Circle 放到 List<Shape>時,會向上轉(zhuǎn)型為Shape,丟失具體的類型

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

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

2.2  :RTTI在運行時如何表示

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

平面設(shè)計培訓,網(wǎng)頁設(shè)計培訓,美工培訓,游戲開發(fā),動畫培訓

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

2.3   :  Class對象

   每一個類都存在與之對應(yīng)的Class對象(保存在.class文件中),根據(jù)class得到具體的對象,請參考“第一章節(jié) 類的加載和初始化”

2.3.1 Class對象獲取的方式

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

   ?、冢喝绻袑ο螅梢灾苯佑脤ο蟮玫脚c之對應(yīng)的Class對象  比如  

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

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

2.3.2: Class對象的常用方法  

 

方法名說明
forName()(1)獲取Class對象的一個引用,但引用的類還沒有加載(該類的第一個對象沒有生成)就加載了這個類。
(2)為了產(chǎn)生Class引用,forName()立即就進行了初始化。
Object-getClass()獲取Class對象的一個引用,返回表示該對象的實際類型的Class引用。
getName()取全限定的類名(包括包名),即類的完整名字。
getSimpleName()獲取類名(不包括包名)
getCanonicalName()獲取全限定的類名(包括包名)
isInterface()判斷Class對象是否是表示一個接口
getInterfaces()返回Class對象數(shù)組,表示Class對象所引用的類所實現(xiàn)的所有接口。
getSupercalss()返回Class對象,表示Class對象所引用的類所繼承的直接基類。應(yīng)用該方法可在運行時發(fā)現(xiàn)一個對象完整的繼承結(jié)構(gòu)。
newInstance()返回一個Oject對象,是實現(xiàn)“虛擬構(gòu)造器”的一種途徑。使用該方法創(chuàng)建的類,必須帶有無參的構(gòu)造器
getFields()獲得某個類的所有的公共(public)的字段,包括繼承自父類的所有公共字段。 類似的還有g(shù)etMethods和getConstructors。
getDeclaredFields獲得某個類的自己聲明的字段,即包括public、private和proteced,默認但是不包括父類聲明的任何字段。類似的還有g(shù)etDeclaredMethods和getDeclaredConstructors。

2.3.3  泛化的Class 

  Class引用表示它所指向的對象的確切類型,java1.5之后,允許開發(fā)者對Class引用所指向的Class對象進行限定,也就是添加泛型。

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

 

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

目的:

 ?、伲簽榱丝梢栽诰幾g器就做類型檢查

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

平面設(shè)計培訓,網(wǎng)頁設(shè)計培訓,美工培訓,游戲開發(fā),動畫培訓

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類型     }
}

平面設(shè)計培訓,網(wǎng)頁設(shè)計培訓,美工培訓,游戲開發(fā),動畫培訓

2.3 : RTTI形式總結(jié):

 ?、伲簜鹘y(tǒng)的類型轉(zhuǎn)換,比如我們在上邊的demo中用到的  shape.draw();

 ?、冢豪肅lass,獲取運行時信息。

 ?、郏旱玫骄唧w的對象

三:Java利用反射獲取運行時類型信息

  如果不知道某一個對象引用的具體類型(比如已經(jīng)上轉(zhuǎn)型的對象),RTTI可以得到。但前提是這個類型編譯器必須已知(那些是編譯期不可知呢? 磁盤文件或者是網(wǎng)絡(luò)連接中獲取一串代表類的字節(jié)碼)

跨網(wǎng)絡(luò)的遠程平臺上提供創(chuàng)建和運行對象的能力 這被稱為 RMI(遠程方法調(diào)用),下面會具體的介紹一下 RMI的實現(xiàn)方式

  反射提供了一種機制,用于檢查可用的方法,并返回方法名,調(diào)用方法。

3.1 : 獲取的方式

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

3.1.1 Java.lang.reflect :

  該類庫中包含了Field Method  Constructor.這些類型的對象在JVM運行時創(chuàng)建,用于表示未知類里對應(yīng)的成員。從而:

 ?、伲河肅onstructor創(chuàng)建對象,用get set讀取Field內(nèi)的字段

 ?、冢河肕ethod.invoke()調(diào)用方法

 ?、郏河胓etFields()、getMethods()、getConstuctors() 得到與之對應(yīng)的數(shù)組

3.1.2 RTTI和RMI的區(qū)別

   檢查對象,查看對象屬于哪個類,加載類的class文件

 ?、伲篟TTI會在編譯期打開和檢查.class文件

  ②:RMI  在編譯期是 看不到.class文件。只能在運行期打開和檢查.class文件

3.2 :   動態(tài)代理

3.2.1 我假設(shè)你對“代理模式”存在一定的了解(還是簡單說一下,代理模式就是在接口和實現(xiàn)之前加一層,用于剝離接口的一些額外的操作)下面是代理模式的示例代碼:

平面設(shè)計培訓,網(wǎng)頁設(shè)計培訓,美工培訓,游戲開發(fā),動畫培訓

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   
  {   
    //在轉(zhuǎn)調(diào)具體目標對象之前,可以執(zhí)行一些功能處理    //轉(zhuǎn)調(diào)具體目標對象的方法
    return method.invoke( proxied, args);  
    
    //在轉(zhuǎn)調(diào)具體目標對象之后,可以執(zhí)行一些功能處理  }    
}

平面設(shè)計培訓,網(wǎng)頁設(shè)計培訓,美工培訓,游戲開發(fā),動畫培訓

 

3.2.2  動態(tài)代理就是:動態(tài)的創(chuàng)建代理并動態(tài)地處理對其所代理的方法的調(diào)用??梢詤⒖?"徹底理解JAVA動態(tài)代理" ,"深度剖析JDK動態(tài)代理機制"??梢岳斫鉃楦屿`活的代理模式

①  動態(tài)代理使用步驟:

  1.通過實現(xiàn)InvocationHandler接口來自定義自己的InvocationHandler;

  2.通過Proxy.getProxyClass獲得動態(tài)代理類 

  3.通過反射機制獲得代理類的構(gòu)造方法,方法簽名為getConstructor(InvocationHandler.class) 

 

  4.通過構(gòu)造函數(shù)獲得代理對象并將自定義的InvocationHandler實例對象傳為參數(shù)傳入 

 

  5.通過代理對象調(diào)用目標方法

平面設(shè)計培訓,網(wǎng)頁設(shè)計培訓,美工培訓,游戲開發(fā),動畫培訓

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("------插入前置通知代碼-------------");            //執(zhí)行相應(yīng)的目標方法
            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();
   }
}

平面設(shè)計培訓,網(wǎng)頁設(shè)計培訓,美工培訓,游戲開發(fā),動畫培訓

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

1、JDK動態(tài)代理實現(xiàn)原理

2、Java動態(tài)代理機制分析及擴展

③  動態(tài)代理應(yīng)用以及備注說明 :

  JDK實現(xiàn)動態(tài)代理需要實現(xiàn)類通過接口定義業(yè)務(wù)方法 (接下來我會簡單說一下Cglib實現(xiàn)動態(tài)代理)。第二是動態(tài)代理非常重要 是反射一個極其重要的模塊,很多框架都離不開動態(tài)代理,比如Spring 。所以,推薦讀者在多去研究一下。

④:Cglib實現(xiàn)動態(tài)代理

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

  CGLIB是一個強大的高性能的代碼生成包。它廣泛的被許多AOP的框架使用,例如spring AOP和dynaop,為他們提供方法的interception(攔截)。最流行的OR Mapping工具hibernate也使用CGLIB來代理單端single-ended(多對一和一對一)關(guān)聯(lián)(對集合的延遲抓取,是采用其他機制實 現(xiàn)的)。EasyMock和jMock是通過使用模仿(moke)對象來測試Java代碼的包。它們都通過使用CGLIB來為那些沒有接口的類創(chuàng)建模仿 (moke)對象。
  CGLIB包的底層是通過使用一個小而快的字節(jié)碼處理框架ASM,來轉(zhuǎn)換字節(jié)碼并生成新的類。除了CGLIB包,腳本語言例如 Groovy和BeanShell,也是使用ASM來生成java的字節(jié)碼。當不鼓勵直接使用ASM,因為它要求你必須對JVM內(nèi)部結(jié)構(gòu)包括class文 件的格式和指令集都很熟悉

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

cglib實現(xiàn)動態(tài)代理的demo:參考  CGLib動態(tài)代理原理及實現(xiàn)

  這是要代理的類:

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

  代理類的核心

平面設(shè)計培訓,網(wǎng)頁設(shè)計培訓,美工培訓,游戲開發(fā),動畫培訓

public class CglibProxy implements MethodInterceptor{  
 private Enhancer enhancer = new Enhancer();  
 public Object getProxy(Class clazz){  
  //設(shè)置需要創(chuàng)建子類的類    enhancer.setSuperclass(clazz);  
  enhancer.setCallback(this);  
  //通過字節(jié)碼技術(shù)動態(tài)創(chuàng)建子類實例  
  return enhancer.create();  
 }  
 //實現(xiàn)MethodInterceptor接口方法  
 public Object intercept(Object obj, Method method, Object[] args,  
   MethodProxy proxy) throws Throwable {  
  System.out.println("前置代理");  
  //通過代理類調(diào)用父類中的方法  
  Object result = proxy.invokeSuper(obj, args);  
  System.out.println("后置代理");  
  return result;  
 }  
}

平面設(shè)計培訓,網(wǎng)頁設(shè)計培訓,美工培訓,游戲開發(fā),動畫培訓

測試結(jié)果:

    

作者:leader_Hoo 
出處:http://www.cnblogs.com/ldh-better/ 
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,如果有幫助,請多多支持。

http://www.cnblogs.com/ldh-better/p/7148975.html