当前位置: 首页 > 后端技术 > Java

java反射机制及其应用场景长图解

时间:2023-04-02 00:24:00 Java

1.什么是java反射?在java面向对象编程的过程中,通常我们需要先知道一个Class类,然后使用new类名()方法来获取这个类的对象。也就是说,我们在写代码的时候(编译时或者编译前)需要知道我们要实例化哪个类,运行哪个方法。这通常称为静态类加载。但是在某些场景下,我们并不能事先知道我们代码的具体行为。比如我们定义一个服务任务工作流,每个服务任务就是一个对应类的方法。服务任务B执行哪个类的哪个方法由服务任务A的执行结果决定,服务任务C执行哪个类的哪个方法由服务任务A和B的执行结果决定,用户不需要想要服务任务的功能硬编码在代码中。我们希望通过配置来执行不同的程序。面对这种情况,我们不能用代码newclassname()来实现,因为你不知道用户会怎么配置。前一秒他想让服务任务A执行Xxxx类的x方法,下一秒他可能又想执行Yyyy类的y方法。当然,你也可以说要求提高了。用户改一次需求,我改一次代码。这个方法也可以要求,但是对用户和程序员个人来说是痛苦的,那么有没有办法在运行时动态改变程序的调用行为呢?这就是我要给大家介绍的“java反射机制”。那么java的反射机制能做什么呢?大概有几种:在程序运行过程中根据包名和类名动态实例化类对象,在程序运行过程中动态获取类对象的信息,包括对象的成本变量和方法,并在程序运行期间动态使用对象的成员变量属性,在程序运行期间动态调用对象方法(也可以调用私有方法)2、HelloWorld,我们定义了一个类Studentpackagecom.zimug.java.reflection;公共课学生{publicStringnickName;私人整数年龄;publicvoiddinner(){System.out.println("吃晚饭!");}privatevoidsleep(intminutes){System.out.println("sleep"+minutes+"minutes");如果不使用反射,相信只要学过java的朋友肯定会调用晚餐方法Studentstudent=newStudent();学生晚餐();如果是反射方法,应该怎么调用呢?//获取Student班级信息Classcls=Class.forName("com.zimug.java.reflection.Student");//对象实例化Objectobj=cls.getDeclaredConstructor().newInstance();//根据方法名并执行方法MethoddinnerMethod=cls.getDeclaredMethod("dinner");dinnerMethod.invoke(obj);//打印:吃晚饭!通过上面的代码,我们可以看到com.zimug.java.reflection.Student类名和dinner方法名都是字符串。既然是字符串,我们可以通过配置文件,或者数据库,或者其他灵活的配置方式来执行这个程序。这是使用反射的最基本方法。3、类加载和反射的关系java的类加载机制还是比较复杂的。为了不混淆重点,我们只介绍一部分与“反射”相关的内容。java编译时,将java文件编译成字节码类文件,类加载器在类加载阶段将类文件加载到内存中,并实例化一个java.lang.Class对象。例如:对于Student类,在加载阶段在内存(方法区或代码区)中实例化一个Class对象。请注意,Class对象不是Student对象。一个Class类(字节码文件)对应一个Class对象。类对象被保存。Student类的基本信息,比如这个Student类有多少字段(Filed)?有多少种构造方法(Constructor)?有多少种方法?什么是注解(Annotation)?和其他信息。有了上面关于Student类的对象的基本信息(java.lang.Class对象),就可以在运行时根据这些信息来实例化Student类的对象了。在运行时,你可以直接新建一个Student对象,也可以使用反射构造一个Student对象,但是不管你new了多少个Student对象,不管你通过反射建立了多少个Student对象,java.lang.只有一个。下面的代码可以证明这一点。Classcls=Class.forName("com.zimug.java.reflection.Student");Classcls2=newStudent().getClass();System.out.println(cls==cls2);//比较Class对象地址,输出结果为true4.操作反射的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对象,这也是反射中最常用的获取对象的方法,因为字符串参数增强了灵活性配置实现*/Classcls=Class.forName("com.zimug.java.reflection.Student");类名.class获取Class对象/***`类名.class`获取Class对象的方式*/Classclz=User.class;classobject.getClass()获取Class对象的方式/***`classobject.getClass()`获取Class对象的方式*/Useruser=newUser();类clazz=user.getClass();虽然获取某个类的Class对象有3种方式,但只有第一种可以称为“反射”。4.2.获取Class类对象的基本信息Classcls=Class.forName("com.zimug.java.reflection.Student");//获取类的包名+类名System.out.println(cls.getName());//com.zimug.java.reflection.Student//获取父类Classcls=Class.forName("com.zimug.java.reflection.Student");//这个类型是注解吗?System.out.println(cls.isAnnotation());//false//这个类型是枚举吗?System.out.println(cls.isEnum());//false//这个类型是基本数据类型吗?System.out.println(cls.isPrimitive());//falseClass类对象信息几乎包含了你想知道的关于这个类型定义的所有信息,更多的方法就不一一列举了。还可以通过以下方法获取Class类对象表示的类实现了哪些接口:getInterfaces()获取Class类对象表示的类使用了哪些注解:getAnnotations()4.3。获取上面结合Student类的Class对象的成员变量定义理解如下代码Classcls=Class.forName("com.zimug.java.reflection.Student");Field[]fields=cls.getFields();for(Fieldfield:fields){System.out.println(field.getName());//昵称}fields=cls.getDeclaredFields();for(Fieldfield:fields){System.out.println(field.getName());//nickNamenewlineage}getFields()方法获取类的非私有成员变量和数组,包括继承自父类的成员变量。getDeclaredFields方法获取所有成员变量和数组,但不包括从父类继承的成员变量。4.4.获取Class对象的方法getMethods():获取Class对象所代表的类的所有非私有方法,一个数组,包括从父类继承的方法getDeclaredMethods():获取所代表的类定义的所有方法Class对象,一个数组,但不包括继承自父类的方法继承自类的方法getMethod(methodName):非私有方法,获取Class对象所代表的类的指定方法名getDeclaredMethod(methodName):方法获取Class对象表示的类的指定方法名Classcls=Class.forName("com.zimug.java.reflection.Student");方法[]方法=cls.getMethods();System.out.println("Student对象的非私有方法");为我thodm:方法){System.out.print(m.getName()+",");}System.out.println("结束");方法[]allMethods=cls.getDeclaredMethods();系统输出。println("学生对象的所有方法");for(Methodm:allMethods){System.out.print(m.getName()+",");}System.out.println("结束");方法晚餐方法=cls.getMethod("晚餐");System.out.println("晚餐方法的参数个数"+dinnerMethod.getParameterCount());方法sleepMethod=cls.getDeclaredMethod("sleep",int.class);系统。out.println("睡眠方法的参数个数"+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方法的参数对象数组[intarg0]sleep方法的参数返回类型void可以看到getMethods获取的方法包括了Object父类中定义的方法,但是不包括该类中定义的私有方法sleep。另外,我们还可以获取方法的参数和返回值信息:获取参数相关属性:获取方法参数个数:getParameterCount()获取方法参数数组对象:getParameters(),返回值为java.lang。reflect.Parameterarray获取返回值相关的属性获取方法返回值的数据类型:getReturnType()4.5.方法调用其实在上面已经演示了方法的调用,如下invoke调用了晚餐方法MethoddinnerMethod=cls.getDeclaredMethod("dinner");dinnerMethod.invoke(obj);//打印:吃晚饭!dinner方法没有参数,那么如何调用带参数的方法呢?查看invoke方法的定义。第一个参数是方法对象。Object...args中不管有多少参数,只要按照方法定义的顺序依次传递即可。publicObjectinvoke(Objectobj,Object...args)4.6。创建类对象(实例化对象)//获取Student班级信息Classcls=Class.forName("com.zimug.java.reflection.Student");//对象实例化Studentstudent=(Student)cls.getDeclaredConstructor().newInstance();//下面的方法已经弃用,不推荐使用。但它仍然是旧JDK版本中的唯一方法。//学生student=(Student)cls.newInstance();五、反射的常见场景通过配置信息调用类的方法,结合注解实现特殊功能按需加载jar包或类5.1。封装上面helloworld中的代码。您是否知道类名className和方法名methodName以便调用该方法?至于className和methodName配置到文件,nacos,还是数据库,自己决定吧!publicvoidinvokeClassMethod(StringclassName,StringmethodName)throwsClassNotFoundException,NoSuchMethodException,InvocationTargetException,InstantiationException,IllegalAccessException{//获取类信息Classcls=Class.forName(className);//对象实例化Objectobj=cls.getDeclaredConstructor()newInstance();//根据方法名获取并执行方法MethoddinnerMethod=cls.getDeclaredMethod(methodName);dinnerMethod.invoke(obj);}5.2。结合注解实现特殊功能如果你学过mybatisplus,应该知道这个注解TableName,表示当前实例类Student对应数据库中的哪一张表。如下代码所示,Student中显示的类对应于t_student表。@TableName("t_student")publicclassStudent{publicStringnickName;privateIntegerage;}下面我们自定义TableName的注解@Target(ElementType.TYPE)//表示TableName可以应用于类,接口或者枚举类,或者接口@Retention(RetentionPolicy.RUNTIME)//表示运行时由JVM加载public@interfaceTableName{Stringvalue();//使用@TableName注解时:@TableName("t_student");}有了这个注解,我们就可以扫描某个路径下的java文件了。至于类注解的扫描,我们不用自己开发。只需引入以下maven坐标即可。org.reflectionsreflections0.9.10看下面代码:先扫包,得到标记的类withtheTableNameannotationfromthepackage,然后打印类的注解值信息//要扫描的包StringpackageName="com.zimug.java.reflection";Reflectionsf=newReflections(packageName);//获取扫描标注的集合Set>set=f.getTypesAnnotatedWith(TableName.class);for(Classc:set){//循环获取标注标注TableNameannotation=c.getAnnotation(TableName.class);//打印注解中的内容System.out.println(c.getName()+"class,TableNameannotationvalue="+annotation.value());输出结果为:com.zimug.java.reflection.Student类,TableName注解value=t_student有朋友会问这个有什么用?这非常有用。有了类定义和数据库表的对应关系,还可以通过反射获取类的成员变量。那你能不能根据t_student和字段名nickName、age构造出增删改查的SQL呢?都建好了,难道只是基础的Mybatisplus?反射和注解的结合可以演化出很多应用场景,尤其是在架构优化方面,等你去发现!5.3.按需加载jar包或类在某些场景下,我们可能不希望JVM加载器一次性将所有jar包加载到JVM虚拟机中,因为这样会影响项目的启动和初始化效率,而且会占用更多记忆。我们要按需加载,需要用到哪些jar,根据程序动态运行的需要加载这些jar。//通过路径加载jar包Filefile=newFile("D:/com/zimug/commons-lang3.jar");URLurl=file.toURI().toURL();//创建类加载器ClassLoaderclassLoader=newURLClassLoader(newURL[]{url});Classcls=classLoader.loadClass("org.apache.commons.lang3.StringUtils");同样,我们也可以动态加载一个路径下的.class文件To//java的.class文件路径Filefile=newFile("D:/com/zimug");URLurl=file.toURI().toURL();//创建类加载器ClassLoaderclassLoader=newURLClassLoader(newURL[]{url});//加载指定类,包全路径Classcls=classLoader.loadClass("com.zimug.java.reflection.Student");你怎么认为?是否可以在不重启容器的情况下实现代码修改?是的,就是这个原理,因为一个类只有一个Class对象,所以无论重新加载多少次,都使用最后一次加载的类对象(如上所述)。六、反射的优缺点优点:自由,灵活使用,不受类访问权限的限制。可以根据指定的类名和方法名来实现方法调用,非常适合业务的灵活配置。缺点:由于反射不受类访问权限的限制,安全性较低,大多数java安全问题都是由反射引起的。与普通的对象访问调用相比,反射由于类和方法的实例化过程,性能相对较低。它破坏了java类的封装性,破坏了类的信息隐藏和边界。欢迎关注我的公告号:字母哥,回复003赠送作者专栏《docker修炼之道》30余篇优质docker文章PDF版。Antetokounmpo博客:zimug.com