Java反射详解,学以致用,实战案例(AOP修改参数、Mybatis拦截器实现自动填充)1:https://developer.aliyun.com/article/1394599
3.1、反射获取运行时类构造方法并使用
class获取构造方法的相关API
// 获取所有的构造函数 Constructor<?>[] getConstructors() // 获取 public或 private 修饰的狗赞函数,只要参数匹配即可 Constructor<?>[] getDeclaredConstructors() // 获取所有 public 修饰的 构造函数 Constructor<T> getConstructor(Class<?>... parameterTypes) //调用此方法,创建对应的运行时类的对象。 public T newInstance(Object ... initargs)
newInstance()
:调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。
要想此方法正常的创建运行时类的对象,要求:
- 运行时类必须提供空参的构造器;
- 空参的构造器的访问权限得够。通常,设置为 public。
为什么要 javabean 中要求提供一个 public 的空参构造器?
原因: 1、便于通过反射,创建运行时类的对象;2、便于子类继承此运行时类时,默认调用 super() 时,保证父类有此构 造器。
想要更详细的了解,建议去看生成的字节码文件,在那里能够给出你答案。
测试:
/** * @description: * @author: Ning Zaichun * @date: 2022年09月17日 1:17 */ public class ConstructorTest { /** * 获取公有、私有的构造方法 并调用创建对象 */ @Test public void test1() throws Exception { Class<?> aClass = Class.forName("com.nzc.Student"); System.out.println("======获取全部public 修饰的构造方法========="); Constructor<?>[] constructors = aClass.getConstructors(); for (Constructor<?> constructor : constructors) { System.out.println(constructor); } System.out.println("======获取 public、private 修饰的构造方法,只要参数匹配即可========="); /** * 这里的参数 Class<?>... parameterTypes 填写的是参数的类型,而并非某个准确的值信息 */ Constructor<?> constructor = aClass.getDeclaredConstructor(String.class); System.out.println(constructor); // 因为此构造函数是 private 修饰,如果不设置暴力反射,则没有权限访问。 // 这里setAccessible(true) 是设置暴力反射,如果不设置,则会报错, constructor.setAccessible(true); // 这里调用的有参构造,所以在调用 newInstance 方法时,也需要填写参数 Object o1 = constructor.newInstance("宁在春"); constructor.setAccessible(false); System.out.println("o1===>"+o1); /** * 如果需要获取有参构造,只需要填写对应的参数类型即可, * 获取无参构造,填null或不填都可。 */ Constructor<?> constructor1 = aClass.getConstructor(); Constructor<?> constructor2 = aClass.getConstructor(String.class,String.class); System.out.println("无参构造==>"+constructor1); System.out.println("有参构造==>"+constructor2); Object o2 = constructor1.newInstance(); Object o3 = constructor2.newInstance("宁在春2","xxxx社会"); System.out.println("o2===>"+o2); System.out.println("o3===>"+o3); } }
既然能够获取到构造方法,那么也就是可以使用的,用Constructor.newInstance()
方法来调用构造方法即可,在下列的打印信息中,也可以看出来确实如此,如果明确要获取为Student
对象的话,进行强转即可。
======获取全部public 修饰的构造方法========= public com.nzc.Student(java.lang.String,java.lang.String) public com.nzc.Student() ======获取 public、private 修饰的构造方法,只要参数匹配即可========= private com.nzc.Student(java.lang.String) o1===>Student(username=宁在春, school=null, age=null) 无参构造==>public com.nzc.Student() 有参构造==>public com.nzc.Student(java.lang.String,java.lang.String) o2===>Student(username=null, school=null, age=null) o3===>Student(username=宁在春2, school=xxxx社会, age=null)
3.2、反射获取运行时类成员变量信息
class
对象中获取类成员信息使用到的API
scss
Field[] getFields(); Field[] getDeclaredFields(); Field getDeclaredField(String name); public native Class<? super T> getSuperclass();
这里的 Field
类对象,其实就是表示类对象中的成员属性,不过还有多了很多其他在反射时需要用到的属性和API罢了~
3.2.1、获取私有公有类成员信息
为了有更好的对比,我先编写了一段不使用反射时的正常操作。
/** * 不使用反射的进行操作 */ @Test public void test1() { Student student = new Student(); student.setUsername("username"); student.setSchool("xxxx社会"); student.setScore(100.0); // 永远三岁的小伙子 哈哈 🤡 student.setAge(3); student.setSex("男"); System.out.println("student信息===>" + student); // out:student信息===>Student(super=Person(sex=男, age=3), username=username, school=xxxx社会, score=100.0) }
现在我再使用反射来实现上述操作~
在实现之前,还是先来看看如何获取公有、私有的成员变量吧
@Test public void test2() throws Exception { Class<?> stuClass = Class.forName("com.nzc.Student"); System.out.println("========获取所有 public 修饰的成员属性(包括父类属性)====="); Field[] fields = stuClass.getFields(); for (Field field : fields) { System.out.println(field); } //public java.lang.String com.nzc.Student.school //public java.lang.Integer com.nzc.Person.age System.out.println("========获取所有(public、private、protected等修饰的)属性成员(不包括父类成员属性)====="); Field[] fields1 = stuClass.getDeclaredFields(); for (Field field : fields1) { System.out.println(field); } //private java.lang.String com.nzc.Student.username //public java.lang.String com.nzc.Student.school //private java.lang.Double com.nzc.Student.score System.out.println("========通过反射,获取对象指定的属性====="); Field username = stuClass.getDeclaredField("username"); System.out.println("username===>" + username); }
但是你发现没有,这无法获取到父类的成员变量信息,父类的信息,我们该如何获取呢?
3.2.2、获取父类成员属性信息
其实一样的,我们也是要获取到父类的Class
对象,在Class API
中有一个getSuperClass()
方法可以获取到父类的class
对象,其他的操作都是一样的~
@Test public void test5() throws ClassNotFoundException { Class<?> stuClass = Class.forName("com.nzc.Student"); System.out.println("========获取所有属性成员(包括父类成员属性)====="); Class clazz = stuClass; List<Field> fieldList = new ArrayList<>(); while (clazz != null) { fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()))); clazz = clazz.getSuperclass(); } Field[] fields2 = new Field[fieldList.size()]; fieldList.toArray(fields2); for (Field field : fields2) { System.out.println(field); } //private java.lang.String com.nzc.Student.username //public java.lang.String com.nzc.Student.school //private java.lang.Double com.nzc.Student.score //private java.lang.String com.nzc.Person.sex //public java.lang.Integer com.nzc.Person.age }
到这里我们已经知道如何获取到类的成员变量信息了,就可以来看看如何给它们设置值,或者通过反射的方式来获取到值了。
3.2.3、设置或修改类成员变量值
@Test public void test3() throws Exception { Class<?> stuClass = Class.forName("com.nzc.Student"); System.out.println("获取到 filed 后,设置值或修改值"); Constructor<?> constructor = stuClass.getConstructor(); Object o = constructor.newInstance(); //操作pubilc 属性 Field school = stuClass.getDeclaredField("school"); school.set(o,"xxxx社会"); System.out.println(o); //Student(super=Person(sex=null, age=null), username=null, school=xxxx社会, score=null) // 另外既然可以设置,那么自然也是可以获取的 System.out.println(school.get(o)); //xxxx社会 // 操作 private 修饰的成员属性 Field name = stuClass.getDeclaredField("username"); // setAccessible(true) 因为是获取private 修饰的成员变量 // 不设置暴力反射 是无法获取到的,会直接报 IllegalAccessException 异常 name.setAccessible(true); name.set(o,"Ningzaichun"); name.setAccessible(false); System.out.println(o); //Student(super=Person(sex=null, age=null), username=Ningzaichun, school=xxxx社会, score=null) }
其实看到这里对于反射已经有个大概的印象了,并且这都是比较平常且实用的一些方法。
3.2.4、获取成员变量注解信息
@Test public void test3() throws Exception { Class<?> stuClass = Class.forName("com.nzc.Student"); System.out.println("获取到 filed 后,设置值或修改值"); Constructor<?> constructor = stuClass.getConstructor(); Object o = constructor.newInstance(); //操作pubilc 属性 Field school = stuClass.getDeclaredField("school"); school.set(o,"xxxx社会"); System.out.println(o); //Student(super=Person(sex=null, age=null), username=null, school=xxxx社会, score=null) // 另外既然可以设置,那么自然也是可以获取的 System.out.println(school.get(o)); //xxxx社会 // 操作 private 修饰的成员属性 Field name = stuClass.getDeclaredField("username"); // setAccessible(true) 因为是获取private 修饰的成员变量 // 不设置暴力反射 是无法获取到的,会直接报 IllegalAccessException 异常 name.setAccessible(true); name.set(o,"Ningzaichun"); name.setAccessible(false); System.out.println(o); //Student(super=Person(sex=null, age=null), username=Ningzaichun, school=xxxx社会, score=null) }
关于getAnnotation、getDeclaredAnnotation 的分析大家可以看看下面这篇文章@Repeatable详解-getAnnotation、getDeclaredAnnotation获取不到对象
对于想要更详细的了解Java注解的朋友,大家可以继续找找相关资料。
3.3、反射获取运行时类对对象方法信息并调用
Class对象中使用的相关API
Method[] getMethods(); Method getMethod(String name, Class<?>... parameterTypes); Method[] getDeclaredMethods(); Method getDeclaredMethod(String name, Class<?>... parameterTypes); // 获取方法返回值类型 Class<?> getReturnType(); // obj – 调用底层方法的对象 // args – 用于方法调用的参数 // return 使用参数args在obj上调度此对象表示的方法的结果 Object invoke(Object obj, Object... args)
3.3.1、获取对象方法
获取公有、私有方法
@Test public void test1() throws Exception { Class<?> stuClass = Class.forName("com.nzc.Student"); System.out.println("========获取所有public修饰的方法======"); Method[] methods = stuClass.getMethods(); for (Method method : methods) { System.out.println(method); } //public boolean com.nzc.Student.equals(java.lang.Object) //public java.lang.String com.nzc.Student.toString() //public int com.nzc.Student.hashCode() //public void com.nzc.Student.hello() //public void com.nzc.Student.say(java.lang.String) //public java.lang.Double com.nzc.Student.getScore() //public void com.nzc.Student.setScore(java.lang.Double) //public void com.nzc.Student.setSchool(java.lang.String) .... 还有一些没写出来了 System.out.println("========获取对象【指定的public修饰的方法】======"); // 第一个参数为方法名,此处是获取public修饰的无参方法 Method hello = stuClass.getMethod("hello"); System.out.println("hello===>"+hello); // 带参方法,第二个参数填写【参数类型】 Method say = stuClass.getMethod("say", String.class); System.out.println("say===>"+say); //hello===>public void com.nzc.Student.hello() //say===>public void com.nzc.Student.say(java.lang.String) // 参数为 方法名,此处是获取 private 修饰的无参方法 // stuClass.getDeclaredMethod(""); Method myScore = stuClass.getDeclaredMethod("myScore", Double.class); System.out.println("myScore==>"+myScore); //myScore==>private void com.nzc.Student.myScore(java.lang.Double) System.out.println("=======获取所有的方法======="); Method[] declaredMethods = stuClass.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { System.out.println(declaredMethod); } //public void com.nzc.Student.say(java.lang.String) //private void com.nzc.Student.myScore(java.lang.Double) //public java.lang.Double com.nzc.Student.getScore() //public void com.nzc.Student.setScore(java.lang.Double) //protected boolean com.nzc.Student.canEqual(java.lang.Object) }
既然能获取到,那么也是能够调用的啦~,
3.3.2、调用对象方法
还记得经常能看到的一个invoke()
方法吗,这里就是调用method.invoke()
方法来实现方法的调用。
@Test public void test2() throws Exception { Class<?> stuClass = Class.forName("com.nzc.Student"); Constructor<?> constructor = stuClass.getConstructor(); Object o = constructor.newInstance(); Method myScore = stuClass.getDeclaredMethod("myScore", Double.class); myScore.setAccessible(true); // 调用方法 myScore.invoke(o, 99.0); // 相当于 Student student= new Student(); // student.myScore(99.0); myScore.setAccessible(false); }
之前阐述了 Method
它本身就记录了方法的一切信息,我们实现调用也就是第一步罢了,说它可以获取到当时定义方法的一切都不为过,接下来一步一步来说吧。
Java反射详解,学以致用,实战案例(AOP修改参数、Mybatis拦截器实现自动填充)3:https://developer.aliyun.com/article/1394602