目录
- 反射 (Reflect)
- 反射的原理
- 使用反射操作属性
- 使用反射操作构造函数
- 无参数构造方法
- 有参数构造方法
- 使用反射操作方法
- 实例方法
- 静态方法
- Modifier解析修饰符
- 内省 (Introspector)
- JavaBean 的规范
- MethodUtils
- ConstructorUtils
- PropertyUtils
- BeanUtils
- ConvertUtils
- 反射和内省的区别
反射 (Reflect)
在框架开发中,都是基于配置文件开发的,在配置文件中配置了类,可以通过读取配置文件中的类名,然后通过反射得到类中的所有内容,或是让类中的某个方法来执行。
也就是说,反射是在运行时获取一个类的所有信息,可以获取到 .class 的任何定义的信息(包括成员 变量,成员方法,构造器等)可以操纵类的字段、方法、构造器等部分。
反射的原理
-
得到 class 文件
- 把 java 文件保存到本地硬盘,得到 .java 文件
- 编译 java 文件,得到 .class 文件
- JVM 把 .class 文件加载到内存中,class 文件在内存中使用 Class 类表示
-
通过 class 文件得到 Class 类,可以通过以下 3 种方式获得 Class 类
- 通过成员变量获得:
类名.class
- 通过具体对象获得:
对象.getClass()
- 通过 Class 的静态方法获取:
Class.forName("classFilePath")
- 通过成员变量获得:
-
通过 Class 类获取 class 文件中的内容,包括:成员变量,构造方法,普通方法,它们都可以用相应的类表示:
- 成员方法:
Field
- 构造方法:
Constructor
- 普通方法:
Method
- 成员方法:
使用反射操作属性
public void test3() {
try {
Class c2 = Class.forName("cn.itcast.test09.Person"); // 得到Class类
Person p11 = (Person) c2.newInstance(); // 得到Person类的对象,返回
Field[] fields = c2.getDeclaredFields(); // 得到所有的属性,返回一个Field数组
Field f1 = c2.getDeclaredField("name"); // 通过属性名称得到特定属性,参数是属性的名称
// 如果操作的是私有的属性,不让操作,可以通过setAccessible(true)操作私有属性
f1.setAccessible(true);
f1.set(p11, "wangwu"); // 设置name值,相当于p.name = "wangwu";
System.out.println(f1.get(p11)); // 相当于 p.name
}catch(Exception e) {
e.printStackTrace();
}
}
使用反射操作构造函数(创建类的实例)
无参数构造方法
通过 Class 对象的 newInstance()
方法创建。
public void test1() throws Exception {
Class c3 = Class.forName("cn.itcast.test09.Person");
// 无参数的构造方法就是直接使用newInstance()方法
Person p = (Person) c3.newInstance();
p.setName("zhangsan");
System.out.println(p.getName());
}
有参数构造方法
不能再通过 Class 对象的 newInstance()
方法创建了,要先得到要调用的构造函数的 Consturctor 对象,然后通过 Constructor 对象的 newInstance()
方法创建。
public void test2() throws Exception {
Class c1 = Class.forName("cn.itcast.test09.Person");
// 获取所有的构造方法
Constructor[] css = c1.getConstructors();
// 获取特定的构造方法:传递是有参数的构造方法里面参数类型,类型使用class的形式传递
Constructor cs = c1.getConstructor(String.class, String.class);
// 通过有参数的构造方法创建Person实例,而不是通过Class的对象
Person p1 = (Person) cs.newInstance("lisi","100");
System.out.println(p1.getId()+" "+p1.getName());
}
使用反射操作方法
实例方法
public void test4() throws Exception {
Class c4 = Class.forName("cn.itcast.test09.Person");
Person p4 = (Person) c4.newInstance();
// 得到所有的普通方法
Method[] mds = c4.getDeclaredMethods();
// 得到特定的普通方法,传递两个参数:第一个参数:方法名称;第二个参数:方法里面参数的类型
Method m1 = c4.getDeclaredMethod("setName", String.class);
// 使用invoke执行方法,传递两个参数:第一个参数:person实例;第二个参数:设置的值
// 在这里要传入person对象的原因是:我们需要知道到底是哪一个对象的setName方法执行了
// 如果要操作的是私有的方法 ,需要 m1.setAccessible(true);
m1.invoke(p4, "niuqi");
System.out.println(p4.getName());
}
静态方法
静态方法调用方式是 类名.方法名
,不需要类的实例,所以使用反射操作静态方法的时候,也是不需要实例的,在 invoke 方法的第一个参数传入 null 即可: m1.invoke(null, "niuqi");
。
例如,我们利用反射操作一个主方法 main 方法(在学习《深入理解Java虚拟机》时遇到了这个用法:
Class clazz = ...
try {
Method method = clazz.getMethod("main", new Class[] { String[].class});
method.invoke(null, new String[] {null});
} catch (Throwable e){
e.printStackTrace();
}
Modifier解析修饰符
利用反射我们可以获得类的属性、构造器、方法等,那么如何判断类或变量、方法的修饰符呢?
Java针对类、成员变量、方法,有很多修饰符,例如public、private、static、final、synchronized、abstract等,这些修饰符用来控制访问权限或其他特性。
通过查看源码,我们发现,Field
、Method
、Constructor
都直接或间接实现了 Member
接口,看下Member接口:Member 表示一个类中的成员,包括成员变量、方法、构造方法三种实现。
Java 文档:
Interface Member
所有已知实现类:
Constructor , Executable , Field , 方法
Member是一个反映关于单个成员(字段或方法)或构造函数的标识信息的接口。
Member接口有个方法:
int getModifiers() 作为整数返回由此 Member所表示的成员或构造方法的 Java语言修饰符。
我们想要得到的应该是一个表示修饰符的字符串,却得到的是一个整数,那么一个整数表示怎样的修饰符或者这个整数怎么转化为修饰符呢?
在这里,需要用到java.lang.reflect.Modifier这个类。Modifier提供了很多静态方法。
static String toString(int mod) 返回描述指定修饰符中的访问修饰符标志的字符串。
static boolean isPrivate(int mod) 如果整数参数包含 private修饰符,则返回 true , false返回false。
static boolean isProtected(int mod) 如果整数参数包含 protected修饰符,则返回 true , false false。
static boolean isPublic(int mod) 如果整数参数包含 public修饰符,则返回 true , false false。
static boolean isStatic(int mod) 如果整数参数包含 static修饰符,则返回 true , false false。
如 public static String toString(int mod) 就可以输出该整数对应的所有的修饰符。
public static boolean isPublic(int mod) 就可以判断该整数对应的是不是包含public修饰符。
我们再来看下 Modifier
源码:
public static final int PUBLIC = 0x00000001;
public static final int PRIVATE = 0x00000002;
public static final int PROTECTED = 0x00000004;
public static final int STATIC = 0x00000008;
public static final int FINAL = 0x00000010;
public static final int SYNCHRONIZED = 0x00000020;
public static final int VOLATILE = 0x00000040;
public static final int TRANSIENT = 0x00000080;
public static final int NATIVE = 0x00000100;
public static final int INTERFACE = 0x00000200;
public static final int ABSTRACT = 0x00000400;
public static final int STRICT = 0x00000800;
static final int BRIDGE = 0x00000040;
static final int VARARGS = 0x00000080;
static final int SYNTHETIC = 0x00001000;
static final int ANNOTATION = 0x00002000;
static final int ENUM = 0x00004000;
static final int MANDATED = 0x00008000;
把它们转换成二进制,可以看出,其实 Modifier 使用一个二进制的位来表示是否包含某个修饰符。
而接口 Member 中有个方法,返回的整数就是该成员表示所有修饰符的数字的和的值:
public int getModifiers();
内省 (Introspector)
内省是基于反射实现的,主要用于操作 JavaBean,相比反射使用起来要方便一些。可以获取 bean 的 getter/setter 方法,也就是说,只要 JavaBean 有 getXxx()
方法,不管这个 Bean 有没有 Xxx 属性,使用内省我们都认为它有。
为了更好的理解,在讲解内省之前,我们先来介绍一下 JavaBean 的规范。
JavaBean 的规范
- JavaBean是一种特殊的类,主要用于传递数据信息。
- 必须有一个public的无参数构造器。
- 属性可以通过get、set、is(可以替代get,用在布尔型属性上)方法或遵循特定命名规范的其他方法访问。
- 可序列化。
- 提供 get/set 方法,如果只有 get 方法,那么这个属性是只读属性。
- 属性:有 get/set 方法的成员,还可以没有成员,只有 get/set 方法。属性名称由 get/set 方法来决定,而不是成员名称。
- 我的理解:属性是一个逻辑概念,能访问通过符合规则的方法访问到的就是属性!
实例代码:
/**
* @author ycw
*/
import java.io.Serializable;
public class UserInfo implements Serializable { //可序列化
// 属性名:使用驼峰命名法。
private String UserId;
private String userName;
private boolean isVIP;
// 有一个public的无参数构造器。
public UserInfo() {
}
public UserInfo(String userId, String userName, boolean isVIP) {
UserId = userId;
this.userName = userName;
this.isVIP = isVIP;
}
public UserInfo(String userId, String userName) {
UserId = userId;
this.userName = userName;
}
public String getUserId() {
return UserId;
}
public void setUserId(String userId) {
UserId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
// is 可以替代 get,用在布尔型属性上
public boolean isVIP() {
return isVIP;
}
public void setVIP(boolean VIP) {
isVIP = VIP;
}
@Override
public String toString() {
return "UserInfo{" +
"UserId='" + UserId + '\'' +
", userName='" + userName + '\'' +
", isVIP=" + isVIP +
'}' ;
}
}
MethodUtils
MethodUtils
通过反射访问对象的方法并且执行方法。
/**
* @author ycw
*/
public class MethodUtilsTest {
@Test
/* MethodUtils通过反射访问对象的方法并且执行方法。*/
public void test1() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException {
UserInfo user1 = new UserInfo();
// 直接通过反射获得类对象,方法,执行
Class<?> clazz = Class.forName("~.model.UserInfo");
Method method = clazz.getMethod("setUserName", String.class);
method.invoke(user1, "令狐冲");
System.out.println(user1);
// 通过方法名和参数类型获得可访问方法
method = MethodUtils.getAccessibleMethod(UserInfo.class, "setUserName", String.class);
method.invoke(user1, "东方不败");
System.out.println(user1);
// 可以直接通过invokeMethod执行方法
MethodUtils.invokeMethod(user1, "setUserName", "任我行");
System.out.println(user1);
}
}
执行结果:
UserInfo{UserId='null', userName='令狐冲', isVIP=false}
UserInfo{UserId='null', userName='东方不败', isVIP=false}
UserInfo{UserId='null', userName='任我行', isVIP=false}
ConstructorUtils
ConstructorUtils
通过反射提供了构造方法相关的便捷操作方法。
/**
* @author ycw
*/
public class ConstructorUtilsTest {
@Test
public void test2() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 直接通过反射获得类对象,构造器,创建实例
Class<?> clazz = Class.forName("org.simplespring.commonsBeanutilsTest.model.UserInfo");
Constructor<?> constructor1 = clazz.getConstructor(String.class, String.class, boolean.class);
Object user1 = constructor1.newInstance("独孤九剑弟子1号", "令狐冲", true);
System.out.println(user1);
// ConstructorUtils 通过反射提供了构造方法获取的便捷操作方法。
Constructor<UserInfo> constructor2 = ConstructorUtils.getAccessibleConstructor(UserInfo.class, new Class[]{String.class, String.class, boolean.class});
UserInfo user2 = constructor2.newInstance("魔教1号", "东方不拜", true);
System.out.println(user2);
// ConstructorUtils 创建实例更简洁的方法
UserInfo user3 = ConstructorUtils.invokeConstructor(UserInfo.class, new String[]{"魔教老1号", "任我行"});
System.out.println(user3);
}
}
执行结果:
UserInfo{UserId='独孤九剑弟子1号', userName='令狐冲', isVIP=true}
UserInfo{UserId='魔教1号', userName='东方不拜', isVIP=true}
UserInfo{UserId='魔教老1号', userName='任我行', isVIP=false}
PropertyUtils
PropertyUtils
通过反射提供了对象属性的便捷操作方法。
/**
* @author ycw
*/
public class PropertyUtilsTest {
@Test
public void test3() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
UserInfo user1 = new UserInfo("魔教1号", "东方不拜", true);
UserInfo user2 = new UserInfo("魔教老1号", "任我行");
System.out.println(user1);
System.out.println(user2);
// 使用 PropertyUtils 将一个 bean 实例的所有属性全部拷贝到另一个实例上,注意属性会完全覆盖,使用时应注意!
PropertyUtils.copyProperties(user2, user1);
System.out.println(user2);
// 为一个实例设置特定属性值
PropertyUtils.setProperty(user1, "userName", "东方必败");
System.out.println(user1);
}
}
执行结果:
UserInfo{UserId='魔教1号', userName='东方不拜', isVIP=true}
UserInfo{UserId='魔教老1号', userName='任我行', isVIP=false}
UserInfo{UserId='魔教1号', userName='东方不拜', isVIP=true}
UserInfo{UserId='魔教1号', userName='东方必败', isVIP=true}
BeanUtils
BeanUtils
通过反射提供了Bean对象的便捷操作方法。
/**
* @author ycw
*/
public class BeanUtilsTest {
@Test
public void test4() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
// BeanUtils 是一个更为通用的工具,通过反射提供了Bean对象的便捷操作方法。
UserInfo user1 = new UserInfo("魔教1号", "东方不拜", true);
UserInfo user2 = new UserInfo("魔教老1号", "任我行");
// 可以代替 PropertyUtils 提供一些操作属性的方法
// 复制属性
BeanUtils.copyProperties(user2, user1);
System.out.println(user2);
// 获取属性
System.out.println(BeanUtils.getProperty(user2, "userName"));
// 设置属性
BeanUtils.setProperty(user2, "userName", "任盈盈");
System.out.println(user2);
// 封装 Map 数据到 JavaBean 对象中
UserInfo user3 = new UserInfo();
Map<String, String> properties = new HashMap<>();
properties.put("userId", "华山掌门");
properties.put("userName", "岳不群");
BeanUtils.populate(user3, properties);
System.out.println(user3);
}
}
执行结果:
UserInfo{UserId='魔教1号', userName='东方不拜', isVIP=true}
东方不拜
UserInfo{UserId='魔教1号', userName='任盈盈', isVIP=true}
UserInfo{UserId='华山掌门', userName='岳不群', isVIP=false}
ConvertUtils
ConvertUtils
提供了数据类型相互转换的方法。
最基本的 convert()
方法将一个字符串转化为特定类型的实例对象。
public static Object convert(String value, Class<?> clazz) {
return ConvertUtilsBean.getInstance().convert(value, clazz);
}
示例:
/**
* @author ycw
*/
public class ConvertUtilsTest {
@Test
public void test5() {
// 将一个字符串转化为特定类型的实例对象。
String numStr = "123";
int num = (int) ConvertUtils.convert(numStr, Integer.class);
System.out.println(num);
}
}
执行结果:
123
反射和内省的区别
反射就像给类照镜子,这个的所有信息会毫无保留的反射到镜子中,将这个类的所有信息照出来,能照出来就是有,照不出来就是没有,得到的东西都是客观真实存在的。
而内省的目的是找出 bean 的 getter 和 setter 以便操作这个 bean,所以只要看到有 getter 或者 setter 就认为这个类有那么一个字段,比如看到 getName() 内省就会认为这个类中有 name 字段,但事实上并不一定会有 name。