1. 程式人生 > >JAVA基礎:反射

JAVA基礎:反射

什麼是反射?

反射可以在執行時動態載入類,建立物件。這種類和物件在編譯期間都是不確定的,只有在執行到對應的反射程式碼才能確定下來。反射還可以獲取物件自身的資訊,包括屬性、方法等,並且可以打破訪問修飾符的限制,設定並訪問私有的屬性,獲取並呼叫私有的方法。

通過反射載入類

反射,最剛開始接觸其實是在jdbc的學習中。
看下面獲取mysql連線的程式碼

//反射載入mysql驅動
Class.forName(“com.mysql.jdbc.Driver”);
String url = “jdbc:mysql://localhost:3306/test?user=root&password=123456″;
Connection con = DriverManager.getConnection(url);                                                            

Class.forName用來載入com.mysql.jdbc.Driver這個驅動類,並且返回Class物件,但是這裡並沒有去接收它的返回值,那麼DriverMannger是如何得到這個驅動類的呢?
看下com.mysql.jdbc.Driver的實現就知道了

  static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }

可以看到mysql驅動類中有這一段靜態塊,在載入驅動類時,初始化的階段會執行靜態塊裡的程式碼,載入驅動類到DriverManager.

此外對於Class.forName需要了解一點就是,通過呼叫方的類載入器反射載入該類。

通過反射建立物件

 Class clazz =  Class.forName("com.domain.Person");
 //建立物件,注意必須要有空構造器
       Object obj =  clazz.newInstance();
//兩種方法判斷某個物件時某一個類的物件
        System.out.println(obj instanceof Person);
        System.out.println(clazz.isInstance(obj));

通過反射獲取物件的域

 	 Class clazz =  Class.forName("com.domain.Person");
       Object obj =  clazz.newInstance();
       //可獲取到私有域
       //getField只可獲得公開域
        Field field = clazz.getDeclaredField("name");

//私有域 可以設定為可以訪問,打破訪問修飾符的訪問控制權限
        field.setAccessible(true);
        //為obj的name屬性設定值 yang
        field.set(obj,"yang");
        //獲取輸出
        System.out.println(field.get(obj));

//靜態域
 Field field1 = clazz.getField("type");
       field1.set(clazz,"人");
        System.out.println(field1.get(clazz));

通過反射呼叫物件的方法

//獲取方法,可以獲取到私有方法    
    Method method = clazz.getDeclaredMethod("show",int.class);
    //   私有方法設定可訪問,打破訪問修飾符的訪問許可權控制
    //        method.setAccessible(true);

//執行方法
        method.invoke(obj,1);
//靜態方法
 Method method1 = clazz.getDeclaredMethod("say",String.class);
        method1.invoke(clazz,"hello");

method深入

1、method的get方法

//這個方法讀取快取找那個的 method物件,通過public only來過濾私有和公開方法
 private Method[] privateGetDeclaredMethods(boolean publicOnly) {
        checkInitted();
        Method[] res;
        ReflectionData<T> rd = reflectionData();
        if (rd != null) {
            res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
            if (res != null) return res;
        }
        // No cached value available; request value from VM
        res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
        if (rd != null) {
            if (publicOnly) {
                rd.declaredPublicMethods = res;
            } else {
                rd.declaredMethods = res;
            }
        }
        return res;
    }


        SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;

reflectionData是軟引用物件,在記憶體不足時會被回收,如果被回收了在需要用到他,又需要從jvm中取一把。

把從快取中取出並過濾好的方法陣列拿出來,按要求的方法簽名去找對應方法

 private static Method searchMethods(Method[] methods,
                                        String name,
                                        Class<?>[] parameterTypes)
    {
        Method res = null;
        String internedName = name.intern();
        for (int i = 0; i < methods.length; i++) {
            Method m = methods[i];
            if (m.getName() == internedName
                && arrayContentsEq(parameterTypes, m.getParameterTypes())
                && (res == null
                    || res.getReturnType().isAssignableFrom(m.getReturnType())))
                res = m;
        }

//注意下面這個判斷,不要看反了,
//這裡是說,如果沒有找到這個方法,直接返回出去null。否則就copy這個方法返回出去,
//所以由此可知,每次取method,得到的都不是同一個物件,所以能複用method就儘量複用
        return (res == null ? res : getReflectionFactory().copyMethod(res));
    }

ok,看到這裡,我試了一下,取兩次method,然後輸出hashCode一看,,居然一樣,不信邪,又來幾次,還是一樣,一度懷疑人生。
後來仔細一看才發現method重寫了hashCode,它的hashCode跟他的簽名有關,跟地址無關。
所以,判斷兩個物件是否是同一個物件,堅決不能用hashCode。
要用==,
沒錯用==就明顯對了。

2、method的invoke方法
invoke最終呼叫到MethodAccessor 的invoke

public interface MethodAccessor {
    Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException;
}

method裡有個root物件,也就是快取中的method,也就是這個method是通過root複製出來的,如果root裡有MethodAccessor ,就直接使用,否則就需要建立。
MethodAccessor 有三個實現類:
DelegatingMethodAccessorImpl
NativeMethodAccessorImpl
GeneratedMethodAccessorXXX

其中DelegatingMethodAccessorImpl是最終注入到method的MethodAccessor中的,也就是最終要呼叫到DelegatingMethodAccessorImpl的invoke,DelegatingMethodAccessorImpl作為一個代理類存在,委託執行NativeMethodAccessorImpl 或者 GeneratedMethodAccessorXXX【反射生成】的invoke

class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
    private MethodAccessorImpl delegate;

    DelegatingMethodAccessorImpl(MethodAccessorImpl var1) {
        this.setDelegate(var1);
    }

    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        return this.delegate.invoke(var1, var2);
    }

    void setDelegate(MethodAccessorImpl var1) {
        this.delegate = var1;
    }
}

ok,現在的核心在NativeMethodAccessorImpl

class NativeMethodAccessorImpl extends MethodAccessorImpl {
    private final Method method;
    private DelegatingMethodAccessorImpl parent;
    private int numInvocations;

    NativeMethodAccessorImpl(Method var1) {
        this.method = var1;
    }

    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
      
      //在15次之後,設定對應的代理物件為GeneratedMethodAccessorXXX,
      //這個反射得到的類中invoke,其實就是普通的方法呼叫
        if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
            MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
            this.parent.setDelegate(var3);
        }

//在方法執行的前15次預設都是呼叫原生的native方法
        return invoke0(this.method, var1, var2);
    }

    void setParent(DelegatingMethodAccessorImpl var1) {
        this.parent = var1;
    }

    private static native Object invoke0(Method var0, Object var1, Object[] var2);
}

其中我上面說的是15次就是ReflectionFactory.inflationThreshold()這個方法返回的,這個15當然也不是一塵不變的,我們可以通過-Dsun.reflect.inflationThreshold=xxx來指定,我們還可以通過-Dsun.reflect.noInflation=true來直接繞過上面的15次NativeMethodAccessorImpl呼叫,和-Dsun.reflect.inflationThreshold=0的效果一樣的。

為什麼要有兩種方式去執行方法的呼叫?

1、原生的在啟動時效能較好,時間長了,效能就會變差
2、反射生成類的在啟動時效能不好,因為需要去反射生成類,啟動一段時間後,效能就很好了。
所以兩者進行了結合,先使用原生,一段時間後,使用反射生成類。

併發呼叫method產生執行緒安全問題?

在呼叫15次後,生成反射類來實現呼叫的方法並沒有加鎖,也就是並不是執行緒安全的。假設在外部method的呼叫層也沒有進行鎖的控制,那麼就會出現一些問題。
假設1000個執行緒同時進來,反射類,那麼就會生成1000個類,雖然最後留下來的就一個類,而且程式也都能正常秩序,但是會有999個類在未被解除安裝之前會佔用記憶體,在極端情況下就會OOM。

GeneratedMethodAccessorXXX如何解除安裝?

首先明確兩點:
1、GeneratedMethodAccessorXXX 需要被解除安裝,為什麼?
假設GeneratedMethodAccessorXXX 都不被解除安裝,當程式中大量使用反射呼叫物件時,很可能因為GeneratedMethodAccessorXXX 的大量生產而導致OOM
2、JVM虛擬機器自帶的類載入器能不能解除安裝類?
在JVM執行期間,JVM會持有預設的類載入器,預設的類載入器又會持有他們載入的Class物件,所以這些Class物件都是可以觸及的,所以在執行期間不可能解除安裝類。

那麼如何實現GeneratedMethodAccessorXXX 的解除安裝,最簡單的方式就是自定義類載入器去實現GeneratedMethodAccessorXXX 的載入,因為使用者自定義的類載入器所載入的類可以被解除安裝。
比如JSP類載入器,每個JSP對應一個類載入器,當發現有JSP修改,就會解除安裝原來的類載入器和類,重新建立類載入器,載入修改後的jsp所編譯的位元組碼檔案。

 static Class<?> defineClass(String var0, byte[] var1, int var2, int var3, final ClassLoader var4) {
        ClassLoader var5 = (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
            public ClassLoader run() {
            //可以看到GeneratedMethodAccessorXXX  確實使用自定義的類載入器  DelegatingClassLoader
                          return new DelegatingClassLoader(var4);
            }
        });
        return unsafe.defineClass(var0, var1, var2, var3, var5, (ProtectionDomain)null);
    }