深入解析反射機制 (二)
來源:程序員人生 發布時間:2014-09-15 19:43:24 閱讀次數:3104次
上一篇中已經介紹了一些關于反射的基本概念,這篇主要通過一個實例說一說反射的過程,以及實際中應用的例子。
這個例子是這樣的設計思路:從一個屬性文件中讀取一段字符串,然后,根據該字符串生成對應的類實例對象;這之后還有一個增強版的例子,可以根據類里面的setter()方法將類的成員變量(引用類型)也進行初始化,Spring框架是這么實現的。
項目結構如下:

本例子包括三個類
1.reflect.properties屬性文件,里面為key-value鍵值對,如下
name=javax.swing.JFrame
useraction=com.tgb.reflect.UserAction
2.UserAction.java,Action類
<span style="font-size:14px;">public class UserAction {
public void addUser()
{
System.out.println("添加用戶");
}
}</span>
3.ObjectPoolFactory.java,該類負責讀取文件,實例化對象
<span style="font-size:14px;">public class ObjectPoolFactory {
//定義一個對象池,采用key-value key為對象名、value為對象
private Map<String, Object> objectPool=new HashMap<>();
//定義一個創建對象的方法,參數為字符串 類名
private Object createObject(String clazzName)
throws InstantiationException,IllegalAccessException,ClassNotFoundException
{
//根據字符串來獲取對應的class對象
Class<?> clazz=Class.forName(clazzName);
return clazz.newInstance();
}
//從屬性文件中讀取key-value初始化類的實例,也可以利用dom4j從配置文件中讀取
public void initPool(String fileName)
throws InstantiationException,IllegalAccessException,ClassNotFoundException
{
try(FileInputStream fis=new FileInputStream(fileName))
{
Properties pros=new Properties();
//從輸入流加載屬性文件
pros.load(fis);
//循環屬性文件中的key
for(String name:pros.stringPropertyNames())
{
//取出key-value,根據value創建對象,并放入對象池中
objectPool.put(name, createObject(pros.getProperty(name)));
}
} catch (Exception e) {
e.printStackTrace();
}
}
public Object getObject(String name)
{
return objectPool.get(name);
}
public void test()
throws InstantiationException, IllegalAccessException, ClassNotFoundException
{
String path=this.getClass().getResource("/com/tgb/reflect/reflect.properties").toString();
path=path.substring(path.indexOf("/")+1);
System.out.println(path);
ObjectPoolFactory opf=new ObjectPoolFactory();
opf.initPool(path);
UserAction userAction=(UserAction)opf.getObject("useraction");
userAction.addUser();
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException
{
ObjectPoolFactory opf=new ObjectPoolFactory();
opf.test();
}</span>
此類中,主要是createObject()這個方法創建實例對象,然后又調用Class類的forName()方法,該方法返回的是一個類的Class對象,再利用Class對象的newInstance()返回它所代表的類的實例。
我們用一個map對象來存儲一個已經創建好的對象,作為對象池使用。Class<?>這里使用了類型通配符,代表的意思是Class對象的類型,這次Class對象未知因此使用了類型通配符,這里其實使用類型參數也是可以的,如Class<T>,至于類型參數與類型通配符區別以后會介紹。
那么跟類加載器有什么關系呢?
我們可以看一下JDK源碼,forName()這個方法重載了兩個,有一個是需要提供類加載器這個參數的,如
<span style="font-size:14px;"> @CallerSensitive
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
if (loader == null) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader ccl = ClassLoader.getClassLoader(Reflection.getCallerClass());
if (ccl != null) {
sm.checkPermission(
SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
}
return forName0(name, initialize, loader);
}</span>
這個方法是forName()參數最多,重載之前的方法,它為我們提供了默認的類加載器,里面還有關于一些安全方面的處理判斷。
運行結果如下:

我們可以看到上面程序UserAction的addUser()已經執行,表名創建實例成功。
接下來我們完善該方法,實現對UserAction里面的一個引用屬性賦值,修改UserAction如下增加了一個setter()方法,這里你也就知道setter()方法的作用,Spring進行諸如時就是根據setter()來給依賴屬性注入的。
UserAction.java
<span style="font-size:14px;">public class UserAction {
//依賴屬性
private UserManager userManager;
public void addUser()
{
System.out.println("執行UserAction的addUser()方法");
}
//屬性的set方法
public void setUserManager(UserManager userManager) {
this.userManager = userManager;
}
}</span>
在工廠類里面主要多了一個初始化屬性的方法,其余有稍微改動但基本類似,如下
<span style="font-size:14px;">//初始化類的屬性,也可以利用dom4j從配置文件中讀取
public void initProperty()
throws InstantiationException,IllegalAccessException,ClassNotFoundException
{
try
{
//循環屬性文件中的key
for(String name:pros.stringPropertyNames())
{
if (name.contains("%")) {
String[] namesArray=name.split("%");
Object target=getObject(namesArray[0]);
Class<?> targetClass=target.getClass();
String mName="set"+namesArray[1].substring(0,1).toUpperCase()+namesArray[1].substring(1);
//得到目標對象userManager屬性的set方法
//第一個參數為方法名、第二個為set方法中傳入的參數類型,即UserManager.class=userManager.getClass()
Method m=targetClass.getMethod(mName,getObject(name).getClass());
//調用set方法給屬性賦值
m.invoke(target,getObject(name));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}</span>
通過看注釋大家也可以理解,和上面類似這里只不過是找到屬性對應的實例,然后,通過set方法把這個實例給屬性賦值的。
屬性文件改為了如下變量值:
name=javax.swing.JFrame
useraction=com.tgb.reflect.UserAction
useraction%userManager=com.tgb.reflect.UserManager
第三句用一個%分割,前面代表類后面代表該類的屬性,該屬性主要用于拼接set方法名,因為得到set方法時需要用到這個作為參數。
至此,反射的基本內容就介紹完了,相信大家通過上面的一些概念和簡單的實例已經理解了反射的原理是怎么實現的,很多框架也是利用這一個過程通過xml配置文件來實例化各種類,所不同的是框架對于xml里面的標簽已經作為限制,道理和讀取屬性文件是一樣的,xml文件包含的信息會更豐富一些,這樣在解析xml和實例化對象時也會更復雜一些,通過配置文件來處理類之間的各種關系。
我們通常接觸到的AOP、IOC、容器、還有一些注解原理都依賴反射實現,多了解一些反射的機制是很有好處的。很多內容在內部都是有聯系的,把它們都相互聯系起來比較著學習和應用才會掌握的更好。
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈