目录

 

什么是反射?

反射的实现原理

反射的使用

为什么需要反射?(反射的作用/应用场景)

反射的缺点


什么是反射?

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

 

反射的实现原理

java类加载:java类加载就是类加载器根据类的全限定名把.class的二进制字节码代表的静态存储结构转化为方法区的运行时数据结构,然后在内存中生成代表该类的Class对象,一个类有且只有一个Class对象。每次生成Java对象实际上都是通过这个Class对象获取整个类的结构并生成相应的java对象。(接口和抽象类也会被加载为class对象)

所以如果能够在运行时拿到Class对象,就可以生成java对象并进行调用,这就是java反射的本质。

 

反射的使用

Java 中的 java.lang.reflect 包提供了反射功能。java.lang.reflect 包中的类都没有 public 构造方法。

/*
        * 获取Class对象的3种方法:
        1、Class.forName("pacakge.className")
        2、Class.class
        3、javaObject.getClass()
        * */
        clazz = Class.forName("com.meituan.data.springbootdemo.User");
//        clazz = user.getClass();
//        clazz = User.class;
        System.out.println(clazz);

        /*
        * 判断是否为某个类的实例的2种方法:
        1、instanceof 关键字
        2、Class 对象的 isInstance 方法(它是一个 Native 方法)
        * */
        Boolean isInstance;
//        isInstance = user instanceof User;
        isInstance = clazz.isInstance(user);
        System.out.println("user is instance of User: "+isInstance);

        /*
        * 通过反射来创建实例对象的2种方法:
        1、Class 对象的 newInstance 方法。
        2、Constructor 对象的 newInstance 方法。
        * */
        User userNew;
//        userNew = (User) clazz.newInstance();
        Constructor constructor = clazz.getConstructor(String.class,Integer.class,String.class);
        userNew = (User) constructor.newInstance("ck",88,"234567");
        System.out.println(userNew);

        /*
        * 获取代理类的方法Method,并调用该方法(invoke)
        * */
        Method method = clazz.getMethod("getAge",null);
        System.out.println(method.invoke(user));
        
        /*
        * 获取代理类的构造器Constructor
        * */
        
        /*
         * 获取代理类的注解Annotation,并根据不同的注解进行不同的行为
         * */
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation:annotations){
            System.out.println(annotation);
            if(annotation instanceof MyPrintAnnotation){
                System.out.println(user);
            }
            if(annotation instanceof MyAnnotation){
                System.out.println(((MyAnnotation) annotation).msg());
            }
        }

 

为什么需要反射?(反射的作用/应用场景)

反射的作用可以用一句话概括:反射赋予了jvm动态编译的能力。动态编译可以最大限度的体现Java的灵活性(多态)。

否则类的元信息只能通过静态编译的形式实现(在编译期确定类型,绑定对象),而不能实现动态编译(在运行期确定类型,绑定对象)。也就是说在编译以后,程序在运行时的行为就是固定的了,如果要在运行时改变程序的行为,就需要动态编译,在Java中就需要反射机制。

 

情景一:不得已而为之

有的类是我们在编写程序的时候无法使用new一个对象来实例化对象的。例如:

  • 调用的是来自网络的二进制.class文件,而没有其.java代码;
  • 注解 - 注解本身仅仅是起到标记作用,它需要利用反射机制,根据注解标记去调用注解解释器,执行行为。如果没有反射机制,注解并不比注释更有用。

 

情景二:动态加载(可以最大限度的体现Java的灵活性,并降低类的耦合性:多态)

有的类可以在用到时再动态加载到jvm中,这样可以减少jvm的启动时间,同时更重要的是可以动态的加载需要的对象(多态)。例如:

  • 动态代理 - 在切面编程(AOP)中,需要拦截特定的方法,通常,会选择动态代理方式。这时,就需要反射技术来实现了。

 

情景三:避免将程序写死到代码里

因为java代码是先通过编译器将.java文件编译成.class的二进制字节码文件,因此如果我们使用new Person()来实例化对象person会出现的问题就是如果我们希望更换person的实例对象,就要在源代码种更改然后重新编译再运行,但是如果我们将person的实例对象类名等信息编写在配置文件中,利用反射的Class.forName(className)方法来实例化java对象(因为实例化java对象都是根据全限定名查找到jvm内存中的class对象,并根据class对象中的累信息实例化得到java对象,因此xml文件中只要包含了权限定类名就可以通过反射实例化java对象),那么我们就可以更改配置文件,无需重新编译。例如:

  • 开发通用框架 - 反射最重要的用途就是开发各种通用框架。很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 JavaBean、Filter 等),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射——运行时动态加载需要加载的对象。

 

反射的缺点

  • 性能开销 - 由于反射涉及动态解析的类型,因此无法执行某些 Java 虚拟机优化。因此,反射操作的性能要比非反射操作的性能要差,应该在性能敏感的应用程序中频繁调用的代码段中避免。
  • 破坏封装性 - 反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
  • 内部曝光 - 由于反射允许代码执行在非反射代码中非法的操作,例如访问私有字段和方法,所以反射的使用可能会导致意想不到的副作用,这可能会导致代码功能失常并可能破坏可移植性。反射代码打破了抽象,因此可能会随着平台的升级而改变行为。