今天看了下java的反射机制,看后,恍然间对spring、annotation的工作机制有了进一步的了解。反射用在java上指的是我们可以运行时加载、探知、使用编译期间完全未知的classes,就是说,java程序可以加载一个运行时才得知名称的class,获得其完整的结构,并生成对象实例,读取改变fields,或调用其methods。
谈到反射,要涉及到java.lang.class与java.lang.reflect。下面我就具体讲下反射的作用吧
1.通过反射查看类信息。
1.1获取class对象
每个类被加载之后,系统都会为该类生成一个对应的class对象,但是程序是如何获取这个class对象的呢?主要有三种方法,
(1)使用class类的forName方法,改方法需要传入字符串参数,该字符串参数的值是某个类的全限定类名(必须是完整包名)。我觉得spring用的就是这种反射机制
(2)调用class属性来获取class对象
(3)调用对象的getclass()方法
例如:
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
|
package
Reflect;
class
Demo{
//other codes...
}
class
hello{
public
static
void
main(String[] args) {
Class<?> demo1=
null
;
Class<?> demo2=
null
;
Class<?> demo3=
null
;
try
{
//一般尽量采用这种形式
demo1=Class.forName(
"Reflect.Demo"
);
}
catch
(Exception e){
e.printStackTrace();
}
demo2=
new
Demo().getClass();
demo3=Demo.
class
;
System.out.println(
"类名称 "
+demo1.getName());
System.out.println(
"类名称 "
+demo2.getName());
System.out.println(
"类名称 "
+demo3.getName());
}
}
|
【运行结果】:
类名称 Reflect.Demo
类名称 Reflect.Demo
类名称 Reflect.Demo
一旦获取了某个类对应的class对象,程序就可以调用class对象的方法了。
1.2.从class中获取信息
java.lang.class中提供了大量的实例方法来获取class对象所对应类的详细信息,如
(1)通过getConstructor等获取class对应类包含的构造器,共有4个方法,不再一一列举
(2)通过getMethod等获取class对应类包含的方法,共有4个方法,不再一一列举
(3)通过getField等获取class对应类包含的Field,共有4个方法,不再一一列举
(4)通过getAnnotation等获取class对应类包含的Annotation,共有3个方法,不再一一列举
(5)获得class对应类的内部类、外部类、父类、继承的接口
(6)获得class对应类的修饰符、所在包、类名等信息
………………
2.使用反射生成并操作对象
1中从class对象中获取类的方法(由Method对象表示)、构造器(由Constructor对象表示)、属性(由Feild对象表示)等,这三个类都位于java.lang.reflect包下,并实现了java.lang.reflect.member接口,程序可以通过method对象来执行对应的方法,通过Constructor对象来调用对应的构造器创建实例,能通过Field对象直接访问并修改对象的属性值。
2.1通过反射来生成对象有以下两种方式
(1)使用class对象的newInstance()方法来创建该class对象对应类的实例。要求class对象的对应类有默认的构造器。这种方式比较常见,因为目前绝大多数框架都是根据配置文件信息来创建java对象,从配置文件读取的只是某个类的字符串类名,要利用反射根据该字符串来创建对应的实例。spring对象就是采取的这种方式。
下面程序就实现了一个简单的对象池,该对象会根据配置文件读取key-value对,然后创建这些对象,并放入一个HashMap中
public class ObjectPoolFactory{//定义一个对象池,前面是对象名,后面是实际对象
private Map<String,Object> objectPool = new HashMap<>();
//定义一个创建对象的方法
//该方法只要传入一个字符床类名,程序就可以根据该类名生成java对象
private Object createObject(String clazzName) throws InstantiationException,
IllegalAccessException,ClassNotFoundException {
Class<?> clazz =Class.forName(clazzName);
return clazz.newInstance();
}
//该方法根据指定文件来初始化对象池
public void initPool(String fileName) throws InstantiationException,
IllegalAccessException,ClassNotFoundException {
try(FileInputStream fis = new FileInputStream(fileName)) {
Properties props =new Properties();
props.load(fis);
for (SOtring name:props.stringPropertyNames()) {
//每取出一对key-valued,就根据value创建一个对象
objectPool.put(name, createObject(props.getProperty(name)));
}
}catch(IOException ex){
System.out.println("读取"+fileName+"异常");
}
}
}
(2)先使用class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对应类的实例
下面程序利用反射来创建一个JFrame对象,使用指定的构造器
public class CreateJFrame {
public static void main(String[] args) throws Exception {
Class<?> jframeClazz =Class.forName("javax.swing.JFrame");
//获取JFrame中带一个字符串参数的构造器
Constructor<T> ctor = new jframeClazz.getConstructor(String.class);
//调用Constructor对象的newInstance方法创建对象
object obj = ctor.newInstance("测试窗口");
System.out.println(obj);
}
}
这种已知javax.swing.JFrame类的情形,通常没有必要使用反射来创建该对象,毕竟通过反射创建对象时性能要稍低一些。通常在开发通用性比较广的框架、基础平台时可能会大量使用反射。
2.2调用方法
获得class对象后,就可以通过前面讲的获取方法,获得Method对象后,就可以通过Method来调用它对应的方法,在Method里包含一个invoke()方法,该方法如下
Object invoke(Object obj,Object……args),obj是执行方法的主调,args是执行方法时的传入该方法的实参。
如:
Class<?> targetClass = target.getClass();
Method mtd = targetClass.getMethod("setter方法名",String.class);
//通过Method的invoke方法执行setter方法
//讲“1234”值作为调用setter方法实参
mtd.invoke(target,"1234");
通过值Method对象的setAccessible(true),可以访问相应类的private方法,private构造器、private属性
2.3访问属性
获得class对象后,就可以通过前面讲的获取Fields,获得Fields对象后,就可以通过Fields来读取或设置Field值
,主要有两个方法
(1)getXxx(Object obj)
(2)setXxx(Object obj,Xxx val)
使用这两个方法可以随意的访问指定对象的所有属性,包括private属性
例子:
//创建一个Person对象
Person p =new Person;
//获取Person类对应的class对象
class<Person> personClazz = Person.class;
//获取person的名为name的field,使用getDeclaredField表明可以获取各种访问控制符的field
Field nameField = personClazz.getDeclaredField("name");
//可以访问private的field
nameField.setAccessible(true);
//为field设置值
nameFiled.set(p,"Moqian");
2.4操作数组
在java.lang.reflect包下还提供了一个Array类,Array对象可以代表所有的数组,程序可以使用Array来动态的创建数组,操作数组元素
如:
//创建一个长度为10的string类型数组
Object arr = Array.newInstance(String.Class,10);
//为数组中index=5的元素赋值为“12345677”
Array.set(arr,5,"12345677");