1. 程式人生 > >Java基礎之 反射是什麽?

Java基礎之 反射是什麽?

type類 cto 實例方法 描述 value ref ring 區別 什麽

什麽是反射,反射能幹嘛?

反射是:指程序可以訪問、檢測和修改它本身狀態或行為的一種能力

反射是一種能力,所以給的定義就是說明了它能幹嘛。

我們平時用反射主要做:

  • 獲取類型的相關信息
  • 動態調用方法
  • 動態構造對象
  • 從程序集中獲得類型。

獲取類型的相關信息

反射的核心Type類,Type對象提供的屬性和方法可以獲取對象的一切信息,如:方法、字段、屬性、事件...等等。

我們獲取已加載程序集中類型的Type對象的幾種方法:(以StringBuilder 類型為例)

  1. 直接使用typeof操作符 Type T1 = typeof(StringBuilder);
  2. 通過類型實例 Type T2 = new StringBuilder().GetType();
  3. 通過Type類的靜態方法 Type T3 = Type.GetType("System.IO.Stream");

不管使用那種,我們最終得到的結果都是一樣的。

那麽我們通過Type又能得到些什麽信息呢?

獲取類型本身信息(命名空間名、全名、是否是抽象、是否是類、、、等等)

var T1 = typeof(StringBuilder);                      
Console.WriteLine("命名空間名稱:" + T1.Namespace);
Console.WriteLine("直接基類型:" + T1.BaseType);
Console.WriteLine("全名:" + T1.FullName);
Console.WriteLine("是抽象類型:" + T1.IsAbstract);
Console.WriteLine("是類:" + T1.IsClass);
//.....等等

技術分享圖片

獲取類型成員信息(通過Tyep中的方法GetMembers)

Type T1 = typeof(TClass);
var Mets = T1.GetMembers();//獲取Type對象的所有公有成員           
foreach (var m in Mets)
{
    Console.WriteLine("【" + m.MemberType.ToString()+ "】:" + m.Name);
    // m.MemberType 是成員類型
}

技術分享圖片

MemberType所能包含的成員類型有哪些呢?如:(可以自己可以F12進去看看)

技術分享圖片

註意:其中MemberInfo的屬性DeclaringType返回的是這個屬性定義的類型,而ReflectedType返回的是獲取這個屬性的對象類型。

如:

Type T2 = typeof(TClass);
var Mets = T2.GetMembers();//獲取所有公共成員(返回值是MemberInfo類型集合)
foreach (var m in Mets)
{
    if (m.Name=="Equals")
    {
        Console.WriteLine("【" + m.MemberType.ToString() + "】:" + m.Name);
        // m.MemberType 是成員類型

// m.DeclaringType;//獲取申明該成員的類
// m.ReflectedType;//獲取用於獲取 MemberInfo 的此實例的類對象。
} }

T2中的Equals,我們知道這個方式是在Objec中定義的,在TClass中調用的,所以:

技術分享圖片

我們發現獲取Type對象的成員大多都是以 isxxx、Getxxx、Getxxxs格式的。

isxxx格式的基本上都是判斷是否是某類型。

Getxxx和Getxxxs都是放回某類型和某類型集合。其中主要的類型有:

//FieldInfo封裝了關於字段的所有信息   (通過Tyep對象的GetFields或GetField方法)
//PropertyInfo類型,封裝了類型的屬性信息;(通過Type對象的GetProperties或GetProperty方法)
//ConstructorInfo類型,封裝了類型的構造函數信息; (..........)
//MethodInfo類型,封裝了類型的方法信息; (........)

//MemberInfo類型,封裝了類型的所有公共成員;(**就是我們上面說的GetMembers方法**)
//EventInfo類型,封裝了類型的事件信息;(.......)
//ParameterInfo類型,封裝了方法和構造函數的參數信息;(........)

它們都在 System.Reflection 命名空間下,其每個isxxx、Getxxx、Getxxxs的細節實例用法就不一一演示了。和上面的GetMembers用法區別不大。

動態調用方法

首先定義個類:

public class TClass
{
    public void fun(string str)
    {
        Console.WriteLine("我是fun方法,我被調用了。" + str);
    }
    public void fun2()
    {
        Console.WriteLine("我是fun2方法,我被調用了。");
    }

    public static void fun3()
    {
        Console.WriteLine("我是fun3靜態方法,我被調用了");
    }
}

調用方式一(使用InvokeMember調用方法)

調用帶參實例方法fun

Type T1 = typeof(TClass);
T1.InvokeMember("fun", BindingFlags.InvokeMethod, null, new TClass(), new string[] { "test" });

技術分享圖片

調用無參實例方法fun2

Type T1 = typeof(TClass);
T1.InvokeMember("fun2", BindingFlags.InvokeMethod, null, new TClass(), null);

調用靜態方法

Type T1 = typeof(TClass);
T1.InvokeMember("fun3", BindingFlags.InvokeMethod, null, T1, null);

我們發現了一個問題當我們調用實例方法的時候需要傳實例對象過去。(有人會說,都實例對象了,我還要你動態掉調用個屁啊。有種情況,在我們實例了對象後,仍不確定應該調用那個方法時可以只有使用。然後有人有說了,那如果實例對象我也不確定呢?那我們下面會分析連實例對象也給動態了。那接著完下看吧。)

我們來說下這幾個參數的意思吧。

第一個:要被動態調用的方法名。

第二個:是一個枚舉,表示是調用一個方法

第三個:是Binder,傳的是null,使用默認值。

第四個:傳如實例對象(調用實例方法時)或者Type對象(調用靜態方法時)。

第五個:要傳給被調用發的參數數組。

調用方式二(使用MethodInfo.Invoke調用方法)

Type T1 = typeof(TClass);
T1.GetMethod("fun", BindingFlags.Instance | BindingFlags.Public).Invoke(new TClass(), new string[] { "testfun1" });
T1.GetMethod("fun2", BindingFlags.Instance | BindingFlags.Public).Invoke(new TClass(), null);
T1.GetMethod("fun3", BindingFlags.Static | BindingFlags.Public).Invoke(T1, null);

技術分享圖片

使用其實和上面的方式一區別不大。

真正的全動態調用

上面的兩種方式,在編寫代碼的時候總是要先確定了已知的對象名和方法名。那麽我們在不知道對象和方法名的時候是否也可以調用呢?答案是肯定的,實現如下:

Console.WriteLine("請輸入對象類名");
string className = Console.ReadLine();
Console.WriteLine("請輸入要執行的方法名");

string funName = Console.ReadLine();
Type T1 = Type.GetType(className);

ConstructorInfo ci = T1.GetConstructors()[0]; //獲取構造函數 
var obj = ci.Invoke(null);//實例化構造函數

T1.InvokeMember(funName, BindingFlags.InvokeMethod, null, obj, null);

當然,這個代碼只能只是fun2,因為上面的傳參寫死了。(你也可以自己稍微修改下,就可以執行fun、fun2、fun3了)

效果如下:(對象名和方法名都是手動輸入的)

技術分享圖片

動態構造對象

我們先定義一個對象:

public class TClass
{
    public TClass()
    {
        Console.WriteLine("構造函數被執行了。。");
    }
    public TClass(string str)
    {
        Console.WriteLine("有參構造函數被執行了。。" + str);
    }        
}

動態構造對象

//動態構造對象,方式一
Assembly asm = Assembly.GetExecutingAssembly();
TClass obj = (TClass)asm.CreateInstance("net.tclass", true);//true:不區分大小寫

//動態構造對象,方式二
ObjectHandle handler = Activator.CreateInstance(null, " net.TClass");//null:當前程序集
obj = (TClass)handler.Unwrap();

//動態構造對象,方式三(構造有參構造函數)
Assembly asm2 = Assembly.GetExecutingAssembly();
obj = (TClass)asm2.CreateInstance("net.tclass", true, BindingFlags.Default, null, new string[] { "test" }, null, null);//true:不區分大小寫            

執行效果圖:

技術分享圖片

獲取和修改屬性

var obj = new TClass();
obj.name = "張三";
Type type = typeof(TClass);
//獲取屬性
var Name = type.InvokeMember("name", BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance, null,
                     obj, new object[] { }) as string;
Console.WriteLine(obj.name);
//設置屬性
type.InvokeMember("name", BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance, null,
                      obj, new object[] { "新屬性(李四)" });
Console.WriteLine(obj.name);

//=====================
PropertyInfo[] pros = type.GetProperties(---);//
PropertyInfo pro = null;
var value = pro.GetValue(type);//獲取值

技術分享圖片

從程序集中獲得類型

取得當前代碼所在程序集(使用GetExecutingAssembly)

Assembly ass = Assembly.GetExecutingAssembly();
Console.WriteLine("當前所在程序集名:"+ass.ManifestModule.Name);
Console.WriteLine("當前所在程序集路徑:"+ass.Location);

技術分享圖片

通過反射加載程序集並創建程序中的類型對象

從程序集中獲得類型,這個應該是我們平時用得比較多。如我們所謂的依賴註入和控制反轉(這個主題將在下篇博文進行分析)就用到了通過反射從程序集中獲取類型。

首先我們還是看看怎麽從程序集中獲得類型吧。我們可以使用Assembly類型提供的靜態方法LoadFrom()或Load(),如:

Assembly asm = Assembly.LoadFrom("Demo.dll");
Assembly asm = Assembly.Load("Demo");

區別:

Assembly asm = Assembly.LoadFrom("net.exe");//需要加後綴,可以指定路徑,如下面的
Assembly asm1 = Assembly.LoadFrom(@"C:\01文件\05Svn\BlogsCode\Blogs\Blogs.Web\bin\Blogs.BLL.dll");

Assembly asm2 = Assembly.Load("Blogs.BLL");//無需加後綴,不可以指定路徑
//Assembly asm3 = Assembly.Load(@"C:\01文件\05Svn\BlogsCode\Blogs\Blogs.Web\bin\Blogs.BLL");//這裏會報錯
//使用Load可以加載當前程序bin目錄行下的程序集或者系統程序集

//這裏TClass可以是一個接口,那麽可以在外面的dll任意實現了。  
TClass obj = (TClass)asm2.CreateInstance("net.tclass", true);//true:不區分大小寫
obj.fun();//***調用動態加載的dll中的方法***

這樣帶來的功能是非常強大的。如 我們在沒有引用程序集的情況下,也可以使用到程序外的程序集。我們還可以根據不同情況引用不同的程序集。我們甚至還可以通過配置文件來直接配置代碼運行時應該加載哪個dll,運行哪個dll中的哪個實現方法。(下篇在講依賴註入的時候會講到,同學們繼續關註哦~)

從上所知,反射不是某一個概念,而是一類操作的統稱。或者說是某些能力的統稱。 感覺不好回答反射到底是什麽,只能說反射能幹什麽。它能動態創建對象、動態調用對象方法、動態讀取和設置屬性和字段、它能動態加載程序外的dll。總的感覺就是大多數都是跟“動態”扯上了關系。


補充:跨程序集反射

如果我們反射A.dll,而A.dll中引用了B.dll,那麽在assembly.GetTypes(); //運行到這個地方會彈出如下錯誤描述

“未處理 System.Reflection.ReflectionTypeLoadException Message="無法加載一個或多個請求的類型。有關更多信息,請檢索LoaderExceptions屬性。”

這種情況可以

Assembly assembly = Assembly.LoadFrom("A.dll") ;
Type type = assembly.GetType("xxx.myclassname") ; //傳入對應的需要反射的類型 而不能GetTypes。且,應用程序需要應用A.dll鎖依賴的B.dll。

Java基礎之 反射是什麽?