聊聊反射
概念
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
反射就是把java类中的各种成分映射成一个个的Java对象,一个类有成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把各个组成部分映射成一个个对象。(其实:一个类中这些成员方法、构造方法、在Class类中都有一个对应的类来描述)
相关类
与Java反射相关的类如下:
类名 | 用途 |
---|---|
Class类 | 代表类的实体,在运行的Java应用程序中表示类和接口 |
Field类 | 代表类的成员变量(成员变量也称为类的属性) |
Method类 | 代表类的方法 |
Constructor类 | 代表类的构造方法 |
Class类
Class代表类的实体,在运行的Java应用程序中表示类和接口。在这个类中提供了很多有用的方法,这里对他们简单的分类介绍。
- 获得类相关的方法
方法 | 用途 |
---|---|
asSubclass(Class<U> clazz) | 把传递的类的对象转换成代表其子类的对象 |
cast(Object obj) | 把对象转换成代表类或是接口的对象 |
getClassLoader() | 获得类的加载器 |
getClasses() | 返回一个数组,数组中包含该类中所有公共类和接口类的对象 |
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象 |
forName(String className) | 根据类名返回类的对象 |
getName() | 获得类的完整路径名字 |
newInstance() | 创建类的实例 |
getPackage() | 获得类的包 |
getSimpleName() | 获得类的名字 |
getSuperclass() | 获得当前类继承的父类的名字 |
getInterfaces() | 获得当前类实现的类或是接口 |
- 获得类中属性相关的方法
方法 | 用途 |
---|---|
getField(String name) | 获得某个公有的属性对象 |
getFields() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对象 |
getDeclaredFields() | 获得所有属性对象 |
- 获得类中注解相关的方法
方法 | 用途 |
---|---|
getAnnotation(Class<A> annotationClass) | 返回该类中与参数类型匹配的公有注解对象 |
getAnnotations() | 返回该类所有的公有注解对象 |
getDeclaredAnnotation(Class<A> annotationClass) | 返回该类中与参数类型匹配的所有注解对象 |
getDeclaredAnnotations() | 返回该类所有的注解对象 |
- 获得类中构造器相关的方法
方法 | 用途 |
---|---|
getConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
- 获得类中方法相关的方法
方法 | 用途 |
---|---|
getMethod(String name, Class...<?> parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class...<?> parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
- 类中其他重要的方法
方法 | 用途 |
---|---|
isAnnotation() | 如果是注解类型则返回true |
isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果是指定类型注解类型则返回true |
isAnonymousClass() | 如果是匿名类则返回true |
isArray() | 如果是一个数组类则返回true |
isEnum() | 如果是枚举类则返回true |
isInstance(Object obj) | 如果obj是该类的实例则返回true |
isInterface() | 如果是接口类则返回true |
isLocalClass() | 如果是局部类则返回true |
isMemberClass() | 如果是内部类则返回true |
Field类
Field代表类的成员变量(成员变量也称为类的属性)。
方法 | 用途 |
---|---|
equals(Object obj) | 属性与obj相等则返回true |
get(Object obj) | 获得obj中对应的属性值 |
set(Object obj, Object value) | 设置obj中对应属性值 |
Method类
Method代表类的方法。
方法 | 用途 |
---|---|
invoke(Object obj, Object... args) | 传递object对象及参数调用该对象对应的方法 |
Constructor类
Constructor代表类的构造方法。
方法 | 用途 |
---|---|
newInstance(Object... initargs) | 根据传递的参数创建类的对象 |
示例
简单示例
为了演示反射的使用,首先构造一个与书籍相关的model——Book.java,然后通过反射方法示例创建对象。
-
被反射类Book.java
public class Book { private int number = 9999; public void print() { System.out.println("Book类公开的print()方法,number=" + number); } private void innerPrint() { System.out.println("Book类私有的print()方法,number=" + number); } }
-
通过反射创建对象,调用print方法
public class ReflectTest1 { public static void main(String[] args) { try { //获取class对象 Class<?> classBook = Class.forName("com.boxuegu.intermediate.language.reflect.Book"); //通过反射进行实例化 Object objectBook = classBook.newInstance(); //通过反射获取print方法 Method method=classBook.getMethod("print"); //调用method的方法,需要传入对象作为参数 method.invoke(objectBook); //也可以通过强制类型转换形式调用对象的print方法 //Book book = (Book) objectBook; //book.print(); } catch (Exception ex) { ex.printStackTrace(); } } }
-
控制台输出
Book类公开的print()方法,number=9999
通过反射修改私有变量
在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或私有方法。
public class ReflectTest2 {
public static void main(String[] args) {
Book object = new Book();
//object.myNumber = 11; //私有变量,无法直接访问,编译错误
object.print();
//通过反射来修改私有变量
try {
Class cl = object.getClass();
Field field = cl.getDeclaredField("number");
field.setAccessible(true); //将私有变量设置为可以访问
field.set(object, 1111);
object.print();//通过对象调用公开方法
//通过反射调用公开方法print()
Method method = cl.getDeclaredMethod("print");
method.invoke(object);
//通过反射调用私有方法innerPrint()
Method innerMethod = cl.getDeclaredMethod("innerPrint");
innerMethod.setAccessible(true); //将私有方法设置为可以访问
innerMethod.invoke(object);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException ex) {
ex.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
控制台输出:
公开的print()方法,myNumber=999
公开的print()方法,myNumber=1111
公开的print()方法,myNumber=1111
私有的print()方法,myNumber=1111
通过反射越过泛型检查
泛型用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查。
public class ReflectTest3 {
public static void main(String[] args) throws Exception {
ArrayList<String> strList = new ArrayList<>();
strList.add("aaa");
strList.add("bbb");
//strList.add(100); //编译错误
//获取ArrayList的Class对象,反向的调用add()方法,添加数据
//得到 strList 对象的Class对象
Class listClass = strList.getClass();
//获取add()方法
Method m = listClass.getMethod("add", Object.class);
//调用add()方法
m.invoke(strList, 100);
//遍历集合
for (Object obj : strList) {
System.out.println(obj);
}
}
}
控制台输出:
aaa
bbb
100
通过反射运行配置文件内容
很多框架如Spring、MyBatis等都支持在配置文件中指定类名,而框架本身不依赖于这个指定的类名,这也是框架灵活性扩展性的体现,而其本质就是基于反射来实现的,下面这个例子展示了其原理。
-
基本类Book
public class Book { private int number = 9999; public void print() { System.out.println("Book类公开的print()方法,number=" + number); } private void innerPrint() { System.out.println("Book类私有的print()方法,number=" + number); } }
-
配置文件test.properties
class = com.boxuegu.intermediate.language.reflect.Book method = print
-
测试类Demo
public class ReflectTest4 { public static void main(String[] args) throws Exception { //通过反射获取Class对象 Class stuClass = Class.forName(getValue("class")); //2获取show()方法 Method m = stuClass.getMethod(getValue("method")); //3.调用show()方法 m.invoke(stuClass.getConstructor().newInstance()); } /** * 此方法接收一个key,在配置文件中获取相应的value * * @param key * @return * @throws IOException */ public static String getValue(String key) throws IOException { Properties properties = new Properties();//获取配置文件的对象 InputStream inputStream = ReflectTest4.class.getResourceAsStream("test.properties");//获取输入流 properties.load(inputStream);//将流加载到配置文件对象中 inputStream.close(); return properties.getProperty(key);//返回根据key获取的value值 } }
当我们升级这个系统时,不要Book类,而需要新写一个Book2的类时,这时只需要更改test.properties的文件内容就可以了,而ReflectTest4代码就一点不用改动。
应用举例
反射的应用非常广泛,在主流的框架中都离不开反射的影子:
-
MyBatis中的反射举例
mybatis中的反射.png
可以看到其中的数据库驱动、插件都是基于配置文件结合反射来使用的。而在MyBatis源代码中有一个reflection包,在其中封装了大量的反射相关的类和方法:
mybatis反射举例2.png
-
Spring中的反射举例
Spring的依赖注入其本质上也基于反射来实现的,通过反射实例化bean,设置bean的属性等。
配置文件xml:<bean id="id" class="com.xy.Student"> <property name="stuName" value="xy" /> </bean>
Spring将采用的代码创建代码Java实例,并注入值:
Class c = Class.forName("com.xy.Student"); Object bean = c.newInstance();
通过一些操作获取对stuName对应的setter方法名:
String setname = "set" + "StuName"; Method method = c.getMehod(setname,String.Class); method.invoke(bean,"xy");
另外,注解也需要基于反射来使用。
总结
在阅读Class类文档时有一个特点,以通过反射获得Method对象为例,一般会提供四种方法,getMethod(parameterTypes)
、getMethods()
、getDeclaredMethod(parameterTypes)
和getDeclaredMethods()
。getMethod(parameterTypes)
用来获取某个公有的方法的对象,getMethods()
获得该类所有公有的方法,getDeclaredMethod(parameterTypes)
获得该类某个方法,getDeclaredMethods()
获得该类所有方法(含私有方法)。带有Declared修饰的方法可以反射到私有的方法,没有Declared修饰的只能用来反射公有的方法。其他的Annotation、Field、Constructor也是如此。