一、什么是java反射?
在java的面向对象编程过程中,通常咱们须要先晓得一个Class类,而后new 类名()
形式来获取该类的对象。也就是说咱们须要在写代码的时候(编译期或者编译期之前)就晓得咱们要实例化哪一个类,运行哪一个办法,这种通常被称为动态的类加载。
然而在有些场景下,咱们当时是不晓得咱们的代码的具体行为的。比方,咱们定义一个服务工作工作流,每一个服务工作都是对应的一个类的一个办法。
- 服务工作B执行哪一个类的哪一个办法,是由服务工作A的执行后果决定的
- 服务工作C执行哪一个类的哪一个办法,是由服务工作A和B的执行后果决定的
- 并且用户不心愿服务工作的性能在代码中写死,心愿通过配置的形式执行不同的程序
面对这个状况,咱们就不能用代码new 类名()
来实现了,因为你不晓得用户具体要怎么做配置,这一秒他心愿服务工作A执行Xxxx类的x办法,下一秒他可能心愿执行Yyyy类的y办法。当然你也能够说提需要嘛,用户改一次需要,我改一次代码。这种形式也能需要,但对于用户和程序员集体而言都是苦楚,那么有没有一种办法在运行期动静的改变程序的调用行为的办法呢?这就是要为大家介绍的“java反射机制”。
那么java的反射机制可能做那些事呢?大略是这样几种:
- 在程序运行期动静的依据
package名.类名
实例化类对象 - 在程序运行期动静获取类对象的信息,包含对象的老本变量和办法
- 在程序运行期动静应用对象的成员变量属性
- 在程序运行期动静调用对象的办法(公有办法也能够调用)
二、Hello World
咱们定义一个类叫做Student
package com.zimug.java.reflection;public class Student { public String nickName; private Integer age; public void dinner(){ System.out.println("吃晚餐!"); } private void sleep(int minutes){ System.out.println("睡" + minutes + "分钟"); }}
如果不必反射的形式,我置信只有学过java的敌人必定会调用dinner办法
Student student = new Student();student.dinner();
如果是反射的形式咱们该怎么调用呢?
//获取Student类信息Class cls = Class.forName("com.zimug.java.reflection.Student");//对象实例化Object obj = cls.getDeclaredConstructor().newInstance();//依据办法名获取并执行办法Method dinnerMethod = cls.getDeclaredMethod("dinner");dinnerMethod.invoke(obj); //打印:吃晚餐!
通过下面的代码咱们看到,com.zimug.java.reflection.Student类名和dinner办法名是字符串。既然是字符串咱们就能够通过配置文件,或数据库、或什么其余的灵便配置办法来执行这段程序了。这就是反射最根底的应用形式。
三、类加载与反射关系
java的类加载机制还是挺简单的,咱们这里为了不混同重点,只为大家介绍和“反射”有关系的一部分内容。
java执行编译的时候将java文件编译成字节码class文件,类加载器在类加载阶段将class文件加载到内存,并实例化一个java.lang.Class的对象。比方:对于Student类在加载阶段
- 在内存(办法区或叫代码区)中实例化一个Class对象,留神是Class对象不是Student对象
- 一个Class类(字节码文件)对应一个Class对象
- 该Class对象保留了Student类的根底信息,比方这个Student类有几个字段(Filed)?有几个构造方法(Constructor)?有几个办法(Method)?有哪些注解(Annotation)?等信息。
有了下面的对于Student类的根本信息对象(java.lang.Class对象),在运行期就能够依据这些信息来实例化Student类的对象。
- 在运行期你能够间接new一个Student对象
- 也能够应用反射的办法结构一个Student对象
然而无论你new多少个Student对象,不管你反射构建多少个Student对象,保留Student类信息的java.lang.Class对象都只有一个。上面的代码能够证实。
Class cls = Class.forName("com.zimug.java.reflection.Student");Class cls2 = new Student().getClass();System.out.println(cls == cls2); //比拟Class对象的地址,输入后果是true
四、操作反射的java类
理解了下面的这些根底信息,咱们就能够更深刻学习反射类相干的类和办法了:
- java.lang.Class: 代表一个类
- java.lang.reflect.Constructor: 代表类的构造方法
- java.lang.reflect.Method: 代表类的一般办法
- java.lang.reflect.Field: 代表类的成员变量
- Java.lang.reflect.Modifier: 修饰符,办法的修饰符,成员变量的修饰符。
- java.lang.annotation.Annotation:在类、成员变量、构造方法、一般办法上都能够加注解
4.1.获取Class对象的三种办法
Class.forName()
办法获取Class对象
/*** Class.forName办法获取Class对象,这也是反射中最罕用的获取对象的办法,因为字符串传参加强了配置实现的灵活性*/Class cls = Class.forName("com.zimug.java.reflection.Student");
类名.class
获取Class对象
/*** `类名.class`的形式获取Class对象*/Class clz = User.class;
类对象.getClass()
形式获取Class对象
/*** `类对象.getClass()`形式获取Class对象*/User user = new User();Class clazz = user.getClass();
尽管有三种办法能够获取某个类的Class对象,然而只有第一种能够被称为“反射”。
4.2.获取Class类对象的根本信息
Class cls = Class.forName("com.zimug.java.reflection.Student");//获取类的包名+类名System.out.println(cls.getName()); //com.zimug.java.reflection.Student//获取类的父类Class cls = Class.forName("com.zimug.java.reflection.Student");//这个类型是不是一个注解?System.out.println(cls.isAnnotation()); //false//这个类型是不是一个枚举?System.out.println(cls.isEnum()); //false//这个类型是不是根底数据类型?System.out.println(cls.isPrimitive()); //false
Class类对象信息中简直包含了所有的你想晓得的对于这个类型定义的信息,更多的办法就不一一列举了。还能够通过上面的办法
- 获取Class类对象代表的类实现了哪些接口: getInterfaces()
- 获取Class类对象代表的类应用了哪些注解: getAnnotations()
4.3. 取得Class对象的成员变量
联合上文中的Student类的定义了解上面的代码
Class cls = Class.forName("com.zimug.java.reflection.Student");Field[] fields = cls.getFields();for (Field field : fields) {System.out.println(field.getName()); //nickName}fields = cls.getDeclaredFields();for (Field field : fields) {System.out.println(field.getName()); //nickName 换行 age}
- getFields()办法获取类的非公有的成员变量,数组,蕴含从父类继承的成员变量
- getDeclaredFields办法获取所有的成员变量,数组,然而不蕴含从父类继承而来的成员变量
4.4.获取Class对象的办法
- getMethods() : 获取Class对象代表的类的所有的非公有办法,数组,蕴含从父类继承而来的办法
- getDeclaredMethods() : 获取Class对象代表的类定义的所有的办法,数组,然而不蕴含从父类继承而来的办法
- getMethod(methodName): 获取Class对象代表的类的指定办法名的非公有办法
- getDeclaredMethod(methodName): 获取Class对象代表的类的指定办法名的办法
Class cls = Class.forName("com.zimug.java.reflection.Student"); Method[] methods = cls.getMethods(); System.out.println("Student对象的非公有办法"); for (Method m : methods) { System.out.print(m.getName() + ","); } System.out.println(" end"); Method[] allMethods = cls.getDeclaredMethods(); System.out.println("Student对象的所有办法"); for (Method m : allMethods) { System.out.print(m.getName() + ","); } System.out.println(" end"); Method dinnerMethod = cls.getMethod("dinner"); System.out.println("dinner办法的参数个数" + dinnerMethod.getParameterCount()); Method sleepMethod = cls.getDeclaredMethod("sleep",int.class); System.out.println("sleep办法的参数个数" + sleepMethod.getParameterCount()); System.out.println("sleep办法的参数对象数组" + Arrays.toString(sleepMethod.getParameters())); System.out.println("sleep办法的参数返回值类型" + sleepMethod.getReturnType());
下面代码的执行后果如下:
Student对象的非公有办法dinner,wait,wait,wait,equals,toString,hashCode,getClass,notify,notifyAll, endStudent对象的所有办法dinner,sleep, enddinner办法的参数个数0sleep办法的参数个数1sleep办法的参数对象数组[int arg0]sleep办法的参数返回值类型void
能够看到getMethods获取的办法中蕴含Object父类中定义的办法,然而不蕴含本类中定义的公有办法sleep。另外咱们还能够获取办法的参数及返回值信息:
获取参数相干的属性:
- 获取办法参数个数:getParameterCount()
- 获取办法参数数组对象:getParameters() ,返回值是java.lang.reflect.Parameter数组
获取返回值相干的属性
- 获取办法返回值的数据类型:getReturnType()
4.5.办法的调用
理论在上文中曾经演示了办法的调用,如下invoke调用dinner办法
Method dinnerMethod = cls.getDeclaredMethod("dinner");dinnerMethod.invoke(obj); //打印:吃晚餐!
dinner办法是无参的那么有参数的办法怎么调用?看看invoke办法定义,第一个参数是Method对象,无论前面 Object... args
有多少参数就依照办法定义顺次传参就能够了。
public Object invoke(Object obj, Object... args)
4.6.创立类的对象(实例化对象)
//获取Student类信息Class cls = Class.forName("com.zimug.java.reflection.Student");//对象实例化Student student = (Student)cls.getDeclaredConstructor().newInstance();//上面的这种办法是曾经Deprecated了,不倡议应用。然而在比拟旧的JDK版本中依然是惟一的形式。//Student student = (Student)cls.newInstance();
五、反射的罕用场景
- 通过配置信息调用类的办法
- 联合注解实现非凡性能
- 按需加载jar包或class
5.1. 通过配置信息调用类的办法
将上文的hello world中的代码封装一下,你晓得类名className和办法名methodName是不是就能够调用办法了?至于你将className和 methodName配置到文件,还是nacos,还是数据库,本人决定吧!
public void invokeClassMethod(String className,String methodName) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //获取类信息 Class cls = Class.forName(className); //对象实例化 Object obj = cls.getDeclaredConstructor().newInstance(); //依据办法名获取并执行办法 Method dinnerMethod = cls.getDeclaredMethod(methodName); dinnerMethod.invoke(obj);}
5.2.联合注解实现非凡性能
大家如果学习过mybatis plus都应该学习过这样的一个注解TableName,这个注解示意以后的实例类Student对应的数据库中的哪一张表。如下问代码所示,Student所示该类对应的是t_student这张表。
@TableName("t_student")public class Student { public String nickName; private Integer age;}
上面咱们自定义TableName这个注解
@Target(ElementType.TYPE) //示意TableName可作用于类、接口或enum Class, 或interface@Retention(RetentionPolicy.RUNTIME) //示意运行时由JVM加载public @interface TableName { String value() ; //则应用@TableName注解的时候: @TableName(”t_student”);}
有了这个注解,咱们就能够扫描某个门路下的java文件,至于类注解的扫描咱们就不必本人开发了,引入上面的maven坐标就能够
<dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> <version>0.9.10</version></dependency>
看上面代码:先扫描包,从包中获取标注了TableName注解的类,再对该类打印注解value信息
// 要扫描的包String packageName = "com.zimug.java.reflection";Reflections f = new Reflections(packageName);// 获取扫描到的标记注解的汇合Set<Class<?>> set = f.getTypesAnnotatedWith(TableName.class);for (Class<?> c : set) {// 循环获取标记的注解TableName annotation = c.getAnnotation(TableName.class);// 打印注解中的内容System.out.println(c.getName() + "类,TableName注解value=" + annotation.value());
输入后果是:
com.zimug.java.reflection.Student类,TableName注解value=t_student
有的敌人会问这有什么用?这有大用处了。有了类定义与数据库表的对应关系,你还能通过反射获取类的成员变量,之后你是不是就能够依据表明t_student和字段名nickName,age构建增删改查的SQL了?全都构建结束,是不是就是一个根底得Mybatis plus了?
反射和注解联合应用,能够演化出许许多多的利用场景,特地是在架构优化方面,期待你去察觉啊!
5.3.按需加载jar包或class
在某些场景下,咱们可能不心愿JVM的加载器一次性的把所有的jar包装载到JVM虚拟机中,因为这样会影响我的项目的启动和初始化效率,并且占用较多的内存。咱们心愿按需加载,须要用到哪些jar,依照程序动静运行的需要取加载这些jar。
//按门路加载jar包File file = new File("D:/com/zimug/commons-lang3.jar");URL url = file.toURI().toURL();//创立类加载器ClassLoader classLoader = new URLClassLoader(new URL[]{url});Class cls = classLoader.loadClass("org.apache.commons.lang3.StringUtils");
同样的把.class文件放在一个门路下,咱们也是能够动静加载到的
//java的.class文件所在门路File file = new File("D:/com/zimug");URL url = file.toURI().toURL();//创立类加载器ClassLoader classLoader = new URLClassLoader(new URL[]{url});//加载指定类,package全门路Class<?> cls = classLoader.loadClass("com.zimug.java.reflection.Student");
类的动静加载能不能让你想到些什么?是不是能够实现代码批改,不须要重新启动容器?对的,就是这个原理,因为一个类的Class对象只有一个,所以不论你从新加载多少次,都是应用最初一次加载的class对象(上文讲过哦)。
六、反射的优缺点
- 长处:自在,应用灵便,不受类的拜访权限限度。能够依据指定类名、办法名来实现办法调用,非常适合实现业务的灵便配置。
毛病:
- 也正因为反射不受类的拜访权限限度,其安全性低,很大部分的java平安问题都是反射导致的。
- 绝对于失常的对象的拜访调用,反射因为存在类和办法的实例化过程,性能也绝对较低
- 毁坏java类封装性,类的信息暗藏性和边界被毁坏
欢送关注我的布告号:字母哥杂谈,回复003赠送作者专栏《docker修炼之道》的PDF版本,30余篇精品docker文章。字母哥博客:zimug.com