本人大号:秃秃爱健身https://blog.csdn.net/Saintmm,系原创啦。
一、什么是反射?
1)Java反射机制的核心:是在程序运行时动态加载类并获取类的一些信息,从而操作类或对象的属性和方法。本质是在JVM得到class对象之后,通过class对象进行反编译,从而获取对象的各种信息。
2)Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
二、反射的原理?
下图为简易的类加载流程和反射的本质:
关于类的详细加载流程大家可以参阅官方文档:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html
三、反射的优缺点
优点:
- 功能强大,在运行时能获得类的各种内容,然后进行反编译,这就可以让我们灵活的构建代码,使其进行运行时装配,并且无需在新增或者移除组件时进行源代码修改,即可以更容易的实现面向对象编程。
缺点:
- 大家都知道的反射会消耗一定的系统资源;因此在不需要动态代理对象的时候不建议使用反射。
(2)破坏封装性,导致安全问题,因为反射调用方法时可以忽略权限检查。
四、反射的用途
1、反编译:.class–>.java
2、通过反射机制访问java对象的属性,方法,构造方法等
3、IDE工具,比如Intellij IDEA,当我们输入一个对象或者类后,一按点号,编译器就会自动列出他的属性或者方法,这里就是用到反射。
4、反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,它们会需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。
五、反射的用法
Java反射包:
https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/package-summary.html
0)反射机制常用的类:
- Java.lang.Class;
- Java.lang.reflect.Constructor;
- Java.lang.reflect.Field;
- Java.lang.reflect.Method;
官方文档参考:
https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
1)class的基本使用
1、获取class对象的三种方法
- Object#getClass()方法
https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html
- 任何数据类型都有一个“静态”的class属性
- **通过class类的静态方法:forName(String className)
https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
代码:
package com.saint.reflect;
/**
* @author 周鑫(玖枭)
*/
public class ObtainClass {
public static void main(String[] args) {
// 这里的new操作会产生一个User对象和一个User.class对象
User user = new User();
// todo: 第一种方式获取Class对象
Class<? extends User> userClass = user.getClass();
System.out.println("user.getClass(): " + userClass.getName());
System.out.println("-----------------------");
// todo: 第二种方式获取Class对象
Class<User> userCLass2 = User.class;
System.out.println("User.class : " + userCLass2.getName());
System.out.println(userCLass2 == userClass);
System.out.println("-----------------------");
// todo:第三种方式获取Class对象
try {
Class<?> userClass3 = Class.forName("com.saint.reflect.User");
System.out.println("Class.forName() : " + userClass3.getName());
System.out.println(userClass3 == userClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
控制台输出内容:
user.getClass(): com.saint.reflect.User
-----------------------
User.class : com.saint.reflect.User
true
-----------------------
Class.forName() : com.saint.reflect.User
true
从控制台输出来看,我们可以知道一个类的Class对象是唯一的,因为类加载器确保类只会被加载一次(此处不考虑热部署中不同类加载器加载类的特例)。
在上述三种方式中:第一种对象都有了还需要反射干嘛;第二种需要导入类包,依赖太强,不导包就抛编译错误;最常用的是Class.forName()方法,参数可以通过配置文件导入,比较灵活,并且可以指定不同的类加载器,比如在加载JDBC驱动时:
Class.forName("com.mysql.cj.jdbc.Driver").newInstance();
2、通过反射创建实例的两种方式
- 使用Class对象的newInstance()方法来创建Class对象对应类的实例。
- 先通过Class对象获取指定的Constructor构造器对象,再调用Constructor对象的newInstance()方法来创建Class对应的实例;这种方式可以指定的构造器来构造类的实例。
代码:
(1)User类
@Getter
@Setter
@ToString
public class User {
private long userId;
private String userName;
private String password;
//无参构造方法
public User() {
System.out.println("调用了公有、无参构造方法执行了。。。");
}
//有多个参数的构造方法
public User(long userId, String userName, String password) {
this.userId = userId;
this.userName = userName;
this.password = password;
System.out.println("编号:" + userId + "姓名:" + userName + "密码:" + password);
}
//有一个参数的构造方法
public User(String userName) {
this.userName = userName;
System.out.println("姓名:" + userName);
}
//受保护的构造方法
protected User(long userId) {
System.out.println("受保护的构造方法 userId = " + userId);
}
//私有构造方法
private User(long userId, String userName) {
this.userId = userId;
this.userName = userName;
System.out.println("私有的构造方法 编号:" + userId + ", 姓名:" + userName);
}
}
(2)创建实例的方式:
// todo: 第一种方式:使用Class.newInstance()方法
Class<User> clazz = User.class;
User newInstance = clazz.newInstance();
System.out.println("使用Class.newInstance()创建的实例: " + newInstance.toString());
// todo: 第二种方式:通过Class对象获取指定的Constructor构造器对象
Constructor constructor = clazz.getConstructor(String.class);
System.out.println("-----------------");
//根据构造器创建实例:
Object obj = constructor.newInstance("saint");
User newInstance2 = (User) obj;
System.out.println("使用构造器创建的实例: " + newInstance2.toString());
控制台输出:
调用了公有、无参构造方法执行了。。。
使用Class.newInstance()创建的实例: User(userId=0, userName=null, password=null)
-----------------
姓名:saint
使用构造器创建的实例: User(userId=0, userName=saint, password=null)
3、使用反射获取构造方法并使用
(1)批量获取的方法:
- public Constructor[] getConstructors():获取Class所有"public(公用)"的构造方法;
- public Constructor[] getDeclaredConstructors():获取Class所有的构造方法(包括private私有、protected受保护、public公有、默认);
(2)获取指定入参的单个构造器:
- public Constructor getConstructor(Class… parameterTypes):获取指定入参的“public”(共有)构造方法,非public类型的获取不到。
- public Constructor getDeclaredConstructor(Class… parameterTypes):不管构造方法是private私有的、protected受保护、默认、public公有的,只要存在我就可以根据入参获取到某个构造器。
(3)使用构造器创建实例(上面提到过):
- Constructor#newInstance(Object… initargs),它的返回类型是泛型T
官方的解释为:newInstance(Object… initargs) 该方法,使用此Constructor构造函数对象表示的构造函数,它使用指定的初始化入参来创建和初始化新实例。
更多Constructor的方法请参考官方API文档:
https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Constructor.html
代码:
代码中使用到的User类在上面3、tab中已贴出,它包含5个构造方法:一个public无参、二个public有参/多参、一个protected有参、一个private有参。
package com.saint.reflect;
import java.lang.reflect.Constructor;
/**
* @author Saint
*/
public class ConstructorTest {
public static void main(String[] args) throws Exception {
// 获取Class对象
Class<?> userClazz = Class.forName("com.saint.reflect.User");
// todo: 1. 获取Class所有public类型的构造器
Constructor<?>[] publicConstructors = userClazz.getConstructors();
System.out.println("User.class public类型的构造器:");
for (Constructor constructor : publicConstructors) {
System.out.println(constructor);
}
System.out.println("--------------");
System.out.println();
// todo: 2. 获取Class所有的构造器
Constructor<?>[] constructors = userClazz.getDeclaredConstructors();
System.out.println("User.class 所有的构造器: ");
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
System.out.println("--------------");
System.out.println();
// todo: 3. 获取指定入参的public类型构造器
System.out.println("User.class 入参为一个String的public类型构造器:");
Constructor<?> publicOneStringParamConst = userClazz.getConstructor(String.class);
System.out.println(publicOneStringParamConst);
System.out.println("--------------");
System.out.println();
// todo: 4. 获取指定入参的全类型构造器
System.out.println("User.class 入参为(long, String)的private类型构造器:");
Constructor<?> privateConstructor = userClazz.getDeclaredConstructor(long.class, String.class);
System.out.println(privateConstructor);
System.out.println("--------------");
System.out.println();
// todo: 5. 下面两种方式都会抛出异常--提示构造方法没有<init>
// 1. 假如我们在public类型的构造器中 尝试protected(非public)类型的构造方法
System.out.println("User.class 使用public类型构造器访问protected的构造方法:");
Constructor<?> shouldProtectedConstructor = userClazz.getConstructor(long.class);
System.out.println(shouldProtectedConstructor);
// 2. 假如指定入参的构造器不存在
System.out.println("User.class 入参为一个Boolean的构造器(其不存在):");
Constructor<?> publicOneBooleanParamConst = userClazz.getConstructor(Boolean.class);
System.out.println(publicOneBooleanParamConst);
}
}
控制台输出:
User.class public类型的构造器:
public com.saint.reflect.User()
public com.saint.reflect.User(long,java.lang.String,java.lang.String)
public com.saint.reflect.User(java.lang.String)
--------------
User.class 所有的构造器:
public com.saint.reflect.User()
public com.saint.reflect.User(long,java.lang.String,java.lang.String)
public com.saint.reflect.User(java.lang.String)
protected com.saint.reflect.User(long)
private com.saint.reflect.User(long,java.lang.String)
--------------
User.class 入参为一个String的public类型构造器:
public com.saint.reflect.User(java.lang.String)
--------------
User.class 入参为(long, String)的private类型构造器:
private com.saint.reflect.User(long,java.lang.String)
--------------
User.class 使用public类型构造器访问protected的构造方法:
Exception in thread "main" java.lang.NoSuchMethodException: com.saint.reflect.User.<init>(long)
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getConstructor(Class.java:1825)
at com.saint.reflect.ConstructorTest.main(ConstructorTest.java:54)
4、判断某个对象是否为某个Class的实例
我们通常使用instanceof 关键字来判断某个对象是否为某个类的实例。同时我们也可以借助反射,利用Class对象的isInstance()方法来判断对象是否为某个类的实例。官方文档中称其是Java语言instanceof运算符的动态等效方法。
代码:
User user = new User();
// 获取User.class对象
Class<?> userClazz = Class.forName("com.saint.reflect.User");
boolean instance = userClazz.isInstance(user);
System.out.println("user对象是否为User.class的实例: " + instance);
控制台输出:
user对象是否为User.class的实例: true
2)Field的基本使用
1、 获取类的成员变量并使用
(1)获取单个成员变量
- public Field getField(String fieldName):获取某个"public(公有)"的字段
- public Field getDeclaredField(String fieldName):获取任何类型(无论是public、private、protected、默认)的字段。
(2)批量获取成员变量
- public Field[] getFields():获取所有"public(公有)"的字段
- public Field[] getFields():获取所有字段,包括:private、protected、默认、public。
(3)设置成员变量的值
- Field#set(Object obj,Object value)
1.obj:要设置的字段所在的对象;
2.value:要为字段设置的值;
更多Field的方法请参考官方API文档:
https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Field.html
代码:
我们先为User类添加两个public的成员变量,以方便后续测试。
public Integer age;
public Boolean gender;
package com.saint.reflect;
import java.lang.reflect.Field;
/**
* @author Saint
*/
public class FieldTest {
public static void main(String[] args) throws Exception {
// 获取Class对象
Class<?> userClazz = Class.forName("com.saint.reflect.User");
// todo: 1. 获取Class对象的所有public类型的成员变量
Field[] publicFields = userClazz.getFields();
System.out.println("User.class的所有public类型的成员变量:");
for (Field field : publicFields) {
System.out.println(field);
}
System.out.println("--------------");
System.out.println();
// todo: 2. 获取Class对象的所有成员变量
Field[] declaredFields = userClazz.getDeclaredFields();
System.out.println("User.class的所有成员变量,含private、protected的:");
for (Field field : declaredFields) {
System.out.println(field);
}
System.out.println("------------------");
System.out.println();
// todo: 3. 获取Class对象 字段名称为fieldName且类型是public的成员变量信息
Field age = userClazz.getField("age");
System.out.println("User.class的字段名称为age(public)的字段信息:");
System.out.println(age);
// todo: 使用反射创建一个User实例,操作public成员变量
Object obj = userClazz.getConstructor().newInstance(null);
age.set(obj, 99);
User user = (User) obj;
System.out.println("填充public成员变量信息后的User实例:" + user);
System.out.println("------------------");
System.out.println();
// todo: 4. 获取Class对象 字段名称为fieldName成员变量信息(无论类型是private、protected、public、默认)
Field userName = userClazz.getDeclaredField("userName");
System.out.println("User.class的字段名称为userName(private)的字段信息:");
System.out.println(userName);
// todo: 使用反射创建一个User实例,操作private/protected成员变量
Object obj2 = userClazz.getConstructor().newInstance(null);
// 暴力反射,解决字段private的限制
userName.setAccessible(true);
userName.set(obj2, "saint");
User user2 = (User) obj2;
System.out.println("填充public成员变量信息后的User实例:" + user2);
}
}
控制台输出:
User.class的所有public类型的成员变量:
public java.lang.Integer com.saint.reflect.User.age
public java.lang.Boolean com.saint.reflect.User.gender
--------------
User.class的所有成员变量,含private、protected的:
private long com.saint.reflect.User.userId
private java.lang.String com.saint.reflect.User.userName
private java.lang.String com.saint.reflect.User.password
public java.lang.Integer com.saint.reflect.User.age
public java.lang.Boolean com.saint.reflect.User.gender
------------------
User.class的字段名称为age(public)的字段信息:
public java.lang.Integer com.saint.reflect.User.age
调用了公有、无参构造方法执行了。。。
填充public成员变量信息后的User实例:User(userId=0, userName=null, password=null, age=99, gender=null)
------------------
User.class的字段名称为userName(private)的字段信息:
private java.lang.String com.saint.reflect.User.userName
调用了公有、无参构造方法执行了。。。
填充public成员变量信息后的User实例:User(userId=0, userName=saint, password=null, age=null, gender=null)
3)Method的基本使用
1、 获取类的方法并使用
(1)获取单个方法
- public Method getMethod(String name,Class<?>… parameterTypes):获取某个"public(公有)"的方法
1.name : 方法名;
2.Class … : 形参的Class类型对象 - public Method getDeclaredMethod(String name,Class<?>… parameterTypes):获取任何类型(无论是public、private、protected、默认)的方法。
(2)批量获取方法
- public Method[] getMethods():获取所有"public(共有)"的成员方法;(含父类的方法、包括Object类)
- public Method[] getDeclaredMethods():获取所有的成员方法,包括private、protected(不包括继承的)
(3)调用方法
- Method#invoke(Object obj,Object… args):
1.obj:要调用方法的对象;
2.args:调用方式时所传递的实参;
更多Method的方法请参考官方API文档:
https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Method.html
代码:
我们先为User类添加四个非public的方法,以方便后续测试。
public void show1(String s){
System.out.println("调用了:公有的,String参数的show1(): s = " + s);
}
protected void show2(){
System.out.println("调用了:受保护的,无参的show2()");
}
void show3(){
System.out.println("调用了:默认的,无参的show3()");
}
private String show4(Integer number){
System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): number = " + number);
return number + "";
}
```i
```java
package com.saint.reflect;
import java.lang.reflect.Method;
/**
* @author Saint
*/
public class MethodTest {
public static void main(String[] args) throws Exception {
// 反射获取Class类
Class<?> userClazz = Class.forName("com.saint.reflect.User");
// todo: 1. 获取Class对象下的所有public方法
Method[] publicMethods = userClazz.getMethods();
System.out.println("User.class所有的public类型方法(含父类Object的方法): ");
for(Method method : publicMethods) {
System.out.println(method);
}
System.out.println("---------------");
System.out.println();
// todo: 2. 获取Class对象下的所有方法(含private、protected、默认、public)
Method[] methods = userClazz.getDeclaredMethods();
System.out.println("User.class所有的方法(含private、protected、默认、public,但不包含父类Object的方法): ");
for(Method method : methods) {
System.out.println(method);
}
System.out.println("---------------");
System.out.println();
// todo: 3. 获取Class对象指定methodName的public类型的method方法
Method publicMethod = userClazz.getMethod("show1", String.class);
System.out.println("User.class 中名称为show1,入参是String的public方法:");
System.out.println(publicMethod);
Object obj = userClazz.getConstructor().newInstance();
publicMethod.invoke(obj, "saint");
System.out.println("---------------");
System.out.println();
// todo: 4. 获取Class对象执行methodName的method方法(无论是private、protected、默认、public)
Method method = userClazz.getDeclaredMethod("show4", Integer.class);
System.out.println("User.class 中名称为show4,入参是Integer的方法:");
System.out.println(publicMethod);
// 暴力反射,解决字段private的限制
method.setAccessible(true);
Object obj2 = userClazz.getConstructor().newInstance();
method.invoke(obj2, 99);
}
}
控制台输出:
User.class所有的public类型方法(含父类Object的方法):
public java.lang.String com.saint.reflect.User.toString()
public long com.saint.reflect.User.getUserId()
public java.lang.Integer com.saint.reflect.User.getAge()
public java.lang.Boolean com.saint.reflect.User.getGender()
public void com.saint.reflect.User.setUserId(long)
public void com.saint.reflect.User.setUserName(java.lang.String)
public void com.saint.reflect.User.setPassword(java.lang.String)
public void com.saint.reflect.User.setAge(java.lang.Integer)
public void com.saint.reflect.User.setGender(java.lang.Boolean)
public java.lang.String com.saint.reflect.User.getUserName()
public java.lang.String com.saint.reflect.User.getPassword()
public void com.saint.reflect.User.show1(java.lang.String)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
---------------
User.class所有的方法(含private、protected、默认、public,但不包含父类Object的方法):
public java.lang.String com.saint.reflect.User.toString()
protected void com.saint.reflect.User.show2()
void com.saint.reflect.User.show3()
public long com.saint.reflect.User.getUserId()
public java.lang.Integer com.saint.reflect.User.getAge()
public java.lang.Boolean com.saint.reflect.User.getGender()
public void com.saint.reflect.User.setUserId(long)
public void com.saint.reflect.User.setUserName(java.lang.String)
public void com.saint.reflect.User.setPassword(java.lang.String)
public void com.saint.reflect.User.setAge(java.lang.Integer)
public void com.saint.reflect.User.setGender(java.lang.Boolean)
public java.lang.String com.saint.reflect.User.getUserName()
public java.lang.String com.saint.reflect.User.getPassword()
public void com.saint.reflect.User.show1(java.lang.String)
private java.lang.String com.saint.reflect.User.show4(java.lang.Integer)
---------------
User.class 中名称为show1,入参是String的public方法:
public void com.saint.reflect.User.show1(java.lang.String)
调用了公有、无参构造方法执行了。。。
调用了:公有的,String参数的show1(): s = saint
---------------
User.class 中名称为show4,入参是Integer的方法:
public void com.saint.reflect.User.show1(java.lang.String)
调用了公有、无参构造方法执行了。。。
调用了,私有的,并且有返回值的,int参数的show4(): number = 99
2)反射main方法
- 因为main方法时静态方法,所以使用放射获取方法时,方法名可以写null.
- 这里有个坑,第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数,所以我们要将String[]强转为Object,否则会报错(wrong number of arguments)
代码:
User类中增加main方法
public static void main(String[] args) {
System.out.println("main方法执行了。。。");
for (String arg : args) {
System.out.print(arg + ", ");
}
}
package com.saint.reflect;
import java.lang.reflect.Method;
/**
* @author Saint
*/
public class MainTest {
public static void main(String[] args) throws Exception {
// 获取Student对象的字节码
Class clazz = Class.forName("com.saint.reflect.User");
// 获取main方法 第一个参数:方法名称,第二个参数:方法形参的类型
Method methodMain = clazz.getMethod("main", String[].class);
// 这样写会报错(wrong number of arguments)
// methodMain.invoke(null, new String[]{"a", "b", "c"});
//这里拆的时候将 new String[]{"a","b","c"} 拆成3个对象。。。所以需要将它强转。
methodMain.invoke(null, (Object) new String[]{"a", "b", "c"});
}
}
控制台输出:
main方法执行了。。。
a, b, c,
4)其他
1、使用反射越过泛型检查
泛型用在编译期,编译过后会将泛型擦除,而反射是发生在运行期,所以我们可以通过反射越过泛型检查。
代码:
ArrayList<String> strList = new ArrayList<>();
strList.add("bob");
strList.add("saint");
// 获取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);
}
控制台输出:
bob
saint
100
2、使用反射创建数组
数组在Java里是比较特殊的一种引用类型,它可以赋值给一个Object Reference。
代码:
Class<?> cls = Class.forName("java.lang.String");
// 创建String类型的数组,且长度为5
Object array = Array.newInstance(cls,5);
//往数组里添加内容
Array.set(array,0,"bob");
Array.set(array,1,"saint");
Array.set(array,2,"john");
Array.set(array,3,"joy");
//获取指定下标的数组内容
System.out.println(Array.get(array,3));
控制台输出:
joy
六、反射在原理中的应用
(1)RocketMQ将配置文件中内容映射到配置类(BrokerConfig、NettyServerConfig、MessageStoreConfig等)中。
在BrokerStartup#createBrokerController()方法中:
MixAll.properties2Object(properties, brokerConfig);
会将properties中的信息映射到brokerConfig类中。
MixAll#properties2Object()方法如下:
public static void properties2Object(final Properties p, final Object object) {
Method[] methods = object.getClass().getMethods();
for (Method method : methods) {
String mn = method.getName();
if (mn.startsWith("set")) {
try {
String tmp = mn.substring(4);
String first = mn.substring(3, 4);
String key = first.toLowerCase() + tmp;
String property = p.getProperty(key);
if (property != null) {
Class<?>[] pt = method.getParameterTypes();
if (pt != null && pt.length > 0) {
String cn = pt[0].getSimpleName();
Object arg = null;
if (cn.equals("int") || cn.equals("Integer")) {
arg = Integer.parseInt(property);
} else if (cn.equals("long") || cn.equals("Long")) {
arg = Long.parseLong(property);
} else if (cn.equals("double") || cn.equals("Double")) {
arg = Double.parseDouble(property);
} else if (cn.equals("boolean") || cn.equals("Boolean")) {
arg = Boolean.parseBoolean(property);
} else if (cn.equals("float") || cn.equals("Float")) {
arg = Float.parseFloat(property);
} else if (cn.equals("String")) {
arg = property;
} else {
continue;
}
method.invoke(object, arg);
}
}
} catch (Throwable ignored) {
}
}
}
}
七、反射的应用案例
后续更新:比如注解限流、反射+javaassist实现数据库返回值动态解析