18.4 使用反射生成并操作对象 18.4.2 调用方法

18.4.2 调用方法

当获得某个类对应的Class对象后,就可以通过该Class对象的getMethods()方法或者getMethod()方法来获取全部方法或指定方法—这两个方法的返回值是Method数组,或者Method对象。
每个Method对象对应一个方法,获得Method对象后,程序就可通过该Method来调用它对应的方法。在Method里包含一个invoke()方法,该方法的签名如下:
Object invoke(Object object,Object.args):该方法中的object是执行该方法的类的实例,后面的args是执行该方法时传入该方法的实参。

实例 对象池工厂

下面程序对前面的对象池工厂进行加强,允许在配置文件中增加配置对象的成员变量的值,对象池工厂会读取为该对象配置的成员变量值,并利用该对象对应的setter法设置成员变量的值。

ExtendedObjectPoolFactory.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import java.util.*;
import java.io.*;
import java.lang.reflect.*;
public class ExtendedObjectPoolFactory
{
// 定义一个对象池,前面是对象名,后面是实际对象
private Map<String ,Object> objectPool = new HashMap<>();
private Properties config = new Properties();
// 从指定属性文件中初始化Properties对象
public void init(String fileName)
{
try(
FileInputStream fis = new FileInputStream(fileName))
{
config.load(fis);
}
catch (IOException ex)
{
System.out.println("读取" + fileName + "异常");
}
}
// 定义一个创建对象的方法,
// 该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
private Object createObject(String clazzName)
throws InstantiationException
, IllegalAccessException , ClassNotFoundException
{
// 根据字符串来获取对应的Class对象
Class<?> clazz =Class.forName(clazzName);
// 使用clazz对应类的默认构造器创建实例
return clazz.newInstance();
}
// 该方法根据指定文件来初始化对象池,
// 它会根据配置文件来创建对象
public void initPool()throws InstantiationException
,IllegalAccessException , ClassNotFoundException
{
for (String name : config.stringPropertyNames())
{
// 每取出一对key-value对,如果key中不包含百分号(%)
// 这就标明是根据value来创建一个对象
// 调用createObject创建对象,并将对象添加到对象池中
if (!name.contains("%"))
{
objectPool.put(name ,
createObject(config.getProperty(name)));
}
}
}
// 该方法将会根据属性文件来调用指定对象的setter方法
public void initProperty()throws InvocationTargetException
,IllegalAccessException,NoSuchMethodException
{
for (String name : config.stringPropertyNames())
{
// 每取出一对key-value对,如果key中包含百分号(%)
// 即可认为该key用于控制调用对象的setter方法设置值,
// %前半为对象名字,后半控制setter方法名
if (name.contains("%"))
{
// 将配置文件中key按%分割
String[] objAndProp = name.split("%");
// 取出调用setter方法的参数值
Object target = getObject(objAndProp[0]);
// 获取setter方法名:set + "首字母大写" + 剩下部分
String mtdName = "set" +
objAndProp[1].substring(0 , 1).toUpperCase()
+ objAndProp[1].substring(1);
// 通过target的getClass()获取它实现类所对应的Class对象
Class<?> targetClass = target.getClass();
// 获取希望调用的setter方法
Method mtd = targetClass.getMethod(mtdName , String.class);
// 通过Method的invoke方法执行setter方法,
// 将config.getProperty(name)的值作为调用setter的方法的参数
mtd.invoke(target , config.getProperty(name));
}
}
}
public Object getObject(String name)
{
// 从objectPool中取出指定name对应的对象。
return objectPool.get(name);
}
public static void main(String[] args)
throws Exception
{
//创建对象工厂
ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory();
//加载配置文件
epf.init("extObj.txt");
//初始化对象池
epf.initPool();
epf.initProperty();
System.out.println(epf.getObject("a"));
}
}

上面程序中initProperty()方法里的第一行粗体字代码获取目标类中包含一个String参数的setter方法,第二行粗体字代码通过调用Methodinvoke()方法来执行该setr方法,该方法执行完成后,就相当于执行了目标对象的setter方法。为上面程序提供如下配置文件:

extObj.txt

1
2
3
4
a=javax.swing.JFrame
b=javax.swing.JLabel
#set the title of a
a%title=Test Title

上面配置文件中的a%title行表明希望调用a对象的setTitle()方法,调用该方法的参数值为Test Title编译、运行上面的ExtendedObjectPoolFactory.java程序,输出如下:

1
javax.swing.JFrame[frame0,0,0,0x0,invalid,hidden,layout=java.awt.BorderLayout,title=Test Title,resizable,normal,defaultCloseOperation=HIDE_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,0,0x0,invalid,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]

可以看到输出一个JFrame窗口,该窗口的标题为Test Title.

应用

Spring框架就是通过这种方式将成员变量值以及依赖对象等都放在配置文件中进行管理的,从而实现了较好的解耦。这也是Spring框架的loC的秘密。

setAccessible取消权限检查

当通过Methodinvoke()方法来调用对应的方法时,Java会要求程序必须有调用该方法的权限。如果程序确实需要调用某个对象的private方法,则可以先调用Method对象的如下方法。

  • setAccessible(boolean flag):将Method对象的accessible设置为指定的布尔值。
    • 如果值为true,指示该Method在使用时取消Java语言的访问权限检查;
    • 如果值为false,则指示该Method在使用时要实施Java语言的访问权限检查

实际上,setAccessible()方法并不属于Method,而是属于它的父类AccessibleObject因此MethodConstructorField都可调用该方法,从而实现通过反射来调用private方法、private构造器和private成员变量,下一节将会让读者看到这种示例。也就是说,它们可以通过调用setAccessible()方法来取消访问权限检查,从而可以通过反射来访问private成员