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

    運行時類型識別(RTTI, Run-Time Type Information)是Java中很是有用的機制,在java中,有兩種RTTI的方式,一種是傳統的,即假設在編譯時已經知道了全部的類型;還有一種,是利用反射機制,在運行時再嘗試肯定類型信息。html

  本篇博文會結合Thinking in Java 的demo 和實際開發中碰到的例子,對Java反射和獲取類型信息作整體上整理。文章主要分爲三塊:java

  •   Java類加載和初始化
  •   Java中RTTI
  •   Java利用反射獲取運行時類型信息

一:Java類加載和初始化

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

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

  類加載器子系統包含一條加載器鏈,只有一個「原生的類加載器」他是jvm實現的一部分,能夠用來記載本地jar包內的class,若涉及加載網絡上的類,或者是web服務器應用,能夠掛接額外的類加載器。spring

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

1.2.1 動態加載

  全部的類都是第一次使用的時候,動態加載到JVM中。建立對類的靜態成員的引用,加載這個類。Java程序在開始運行的時候並不是徹底加載,類都是用的地方在加載,這就是動態加載數組

  ①:首先檢查這個類是否被加載緩存

  ②:若是沒有加載,再去根據類名查找.class文件,加載類的字節碼,並校驗是否存在不良代碼,服務器

測試代碼以下:cookie

//candy.java
public class Candy {
    static {
        System.out.println("loading Candy");
    }
}
//cookie.java
public class Cookie {
    static {
        System.out.println("loading Cookie");
    }
}
//Gum.java
public class Gum {
    static {
        System.out.println("loading Gum");
    }
}
//TestMain.java
public 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 連接

  驗證類中的字節碼,爲靜態域分配存儲空間。若是必須的話,將解析這個類建立的對其餘類的全部引用session

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

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

一、JDK動態代理實現原理

二、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作贊成攔截,用反射去調用方法的目的同樣(添加一些想要的處理,好比校驗用戶)。核心也是反射調用方法

相關文章
相關標籤/搜索