Home >Java >javaTutorial >What are the Java reflection mechanism and common application scenarios?
In the object-oriented programming process of java, usually we need to know a Class class first, and then new class name()
method to obtain the object of this class. That is to say, we need to know which class we want to instantiate and which method to run when writing code (at compile time or before class loading). This is usually called static class loading.
But in some scenarios, we don’t know the specific behavior of our code in advance. For example, if we define a service task workflow, each service task is a method of a corresponding class.
Which class and which method service task B executes is determined by the execution result of service task A
Which class method to be executed by service task C is determined by the execution results of service tasks A and B
And the user does not want the function of the service task to be in The code is hard-coded, hoping to execute different programs according to different conditions through configuration. The conditions themselves also change
Faced with this situation, we cannot use codenew Class name ()
is implemented because you don’t know how the user specifically configures it. This second he wants service task A to execute the x method of the Xxxx class, and the next second he may want to execute the y method of the Yyyy class. Of course, you can also ask for requirements. Once the user changes the requirements, I will change the code. This method can also be required, but it is painful for users and programmers personally. So is there a way to dynamically change the calling behavior of the program during runtime? This is the "java reflection mechanism" that I want to introduce to you.
So what can Java’s reflection mechanism do? They are probably as follows:
Dynamicly instantiate class objects according to package name.class name
during program running time
Dynamicly obtain the information of the class object during the running of the program, including the cost variables and methods of the object
Dynamicly use the member variable attributes of the object during the running of the program
Dynamically call the method of the object during the running of the program (private methods can also be called)
Let’s get in first, the boss can Skip this paragraph. We define a class called Student
package com.zimug.java.reflection; public class Student { public String nickName; private Integer age; //这里是private public void dinner(){ System.out.println("吃晚餐!"); } private void sleep(int minutes){ //private修饰符 System.out.println("睡" + minutes + "分钟"); } }
If reflection is not used, I believe that friends who have learned Java will definitely call the dinner method
Student student = new Student(); student.dinner();
If it is reflection, how should we call it?
//获取Student类信息 Class cls = Class.forName("com.zimug.java.reflection.Student"); //对象实例化 Object obj = cls.getDeclaredConstructor().newInstance(); //根据方法名获取并执行方法 Method dinnerMethod = cls.getDeclaredMethod("dinner"); dinnerMethod.invoke(obj); //打印:吃晚餐!
We can see from the above code that the com.zimug.java.reflection.Student class name and dinner method name are strings. Since it is a string, we can execute this program through a configuration file, database, or other flexible configuration method. This is the most basic way to use reflection.
The class loading mechanism of Java is quite complicated. In order not to confuse the key points, we will only introduce some of the content related to "reflection".
When java compiles, it compiles the java file into a bytecode class file. The class loader loads the class file into memory during the class loading phase and instantiates a java.lang.Class object. For example, the Student class will have the following actions during the loading phase:
Instantiate a Class object in the memory (method area or code area). Note that the Class object is not the Student object
A Class class (bytecode file) corresponds to a Class object, and there is only one
This Class object saves the basic information of the Student class, For example, how many fields (Filed) does this Student class have? How many constructors are there? How many methods are there? What annotations are there? Waiting for information
With the above basic information object (java.lang.Class object) about the Student class, you can use these during runtime Information to instantiate an object of the Student class.
During runtime, you can directly create a new Student object
You can also use reflection to construct a Student object
But no matter how many new Student objects you create, no matter how many Student objects you build by reflection, there is only one java.lang.Class object that saves Student class information. The following code can prove it.
Class cls = Class.forName("com.zimug.java.reflection.Student"); Class cls2 = new Student().getClass(); System.out.println(cls == cls2); //比较Class对象的地址,输出结果是true
After understanding the above basic information, we can learn more about the classes and methods related to reflection classes:
java.lang.Class: Represents a class
java.lang.reflect.Constructor: Represents the constructor method of the class
java.lang.reflect.Method: Represents the ordinary method of the class
java.lang.reflect.Field: Represents member variables of the class
Java.lang.reflect.Modifier: 修饰符,方法的修饰符,成员变量的修饰符。
java.lang.annotation.Annotation:在类、成员变量、构造方法、普通方法上都可以加注解
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对象,但是只有第一种可以被称为“反射”。
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()
结合上文中的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
方法获取所有的成员变量,数组,但是不包含从父类继承而来的成员变量
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, end
Student对象的所有方法
dinner,sleep, end
dinner方法的参数个数0
sleep方法的参数个数1
sleep方法的参数对象数组[int arg0]
sleep方法的参数返回值类型void
可以看到getMethods获取的方法中包含Object父类中定义的方法,但是不包含本类中定义的私有方法sleep。另外我们还可以获取方法的参数及返回值信息:
获取参数相关的属性:
获取方法参数个数:getParameterCount()
获取方法参数数组对象:getParameters() ,返回值是java.lang.reflect.Parameter数组
获取返回值相关的属性
获取方法返回值的数据类型:getReturnType()
实际在上文中已经演示了方法的调用,如下invoke调用dinner方法
Method dinnerMethod = cls.getDeclaredMethod("dinner"); dinnerMethod.invoke(obj); //打印:吃晚餐!
dinner方法是无参的,那么有参数的方法怎么调用?看看invoke方法定义,第一个参数是Method对象,无论后面Object... args
有多少参数就按照方法定义依次传参就可以了。
public Object invoke(Object obj, Object... args)
//获取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
将上文的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); }
大家如果学习过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了?
反射和注解结合使用,可以演化出许许多多的应用场景,特别是在框架代码实现方面。等待你去发觉啊!
在某些场景下,我们可能不希望JVM的加载器一次性的把所有classpath下的jar包装载到JVM虚拟机中,因为这样会影响项目的启动和初始化效率,并且占用较多的内存。我们希望按需加载,需要用到哪些jar,按照程序动态运行的需求取加载这些jar。
把jar包放在classpath外面,指定加载路径,实现动态加载。
//按路径加载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");
类的动态加载能不能让你想到些什么?是不是可以实现代码修改,不需要重新启动web容器?对的,就是这个原理,因为一个类的Class对象只有一个,所以不管你重新加载多少次,都是使用最后一次加载的class对象(上文讲过哦)。
优点:自由,使用灵活,不受类的访问权限限制。可以根据指定类名、方法名来实现方法调用,非常适合实现业务的灵活配置。在框架开发方面也有非常广泛的应用,特别是结合注解的使用。
缺点:
也正因为反射不受类的访问权限限制,其安全性低,很大部分的java安全问题都是反射导致的。
相对于正常的对象的访问调用,反射因为存在类和方法的实例化过程,性能也相对较低
破坏java类封装性,类的信息隐藏性和边界被破坏
The above is the detailed content of What are the Java reflection mechanism and common application scenarios?. For more information, please follow other related articles on the PHP Chinese website!