暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

细读源码之JAVA反射

马士兵 2021-07-15
230
 
细读源码是《马士兵教育》旗下赏析源码的栏目。我们赏析源码的目的不是为了炫技,而是为了去理解作者的设计思想,并取其精华,去其糟粕,从而写出更加优秀的代码。
另一方面,也可以给面试加分。代码好坏的评价,不可避免地会代入个人的主观色彩,大家和而不同。
在上一篇文章《细读源码之JDK动态代理》一文中,我们讲解了动态代理的实现原理,其中生成的动态代理类中,使用了JAVA反射技术,所以本文就补充一下JAVA反射的相关知识点,主要从以下两个方面进行讲解:
  1. JAVA反射介绍
  2. AVA反射相关API

一.JAVA反射介绍

1.JAVA反射简介

JAVA反射通常在运行时需要检查或者修改应用程序行为的时候使用,反射是一种强大的技术,可以实现一些在非反射条件下不可能实现的功能。

2.JAVA反射特性和典型使用场景
A.扩展性支持
应用程序可以使用反射,通过类的完全限定名,来获取外部的、用户定义的类的Class对象,然后反射创建对象的实例,以实现应用程序的动态扩展。
B.类浏览和可视化开发环境
利用反射,我们可以浏览一个类的所有成员,而可视化开发环境可以利用反射中获取的成员信息,来帮助开发人员快速,正确地编写代码,如IDE中的成员智能提示,类型自动补全等高级功能,都可以使用反射来实现。
C.Debug和测试工具

Debug调试工具利用反射,能够看到类上的私有成员的值。测试工具可以利用反射,系统地调用类上定义的所有方法,以确保白盒测试,达到高水平的代码覆盖率。

3.JAVA反射的缺点
虽然反射的功能非常强大,但是应该避免滥用。如果可以在不使用反射的情况下,就能够完成相应的功能,那就最好避免使用反射。因为反射的使用,主要会带来以下几个问题:
A.性能问题
反射调用时,使用的类型需要动态解析,导致无法执行某些JVM优化,最终反射操作的性能比非反射操作要慢,一定要避免在性能敏感应用程序中大量使用反射。
B.安全限制
反射调用需要运行时权限,在安全管理器下运行可能不存在该权限,导致反射执行失败。举例说明:
下面的代码,使用反射对字段进行赋值:
    package org.example.sourcecode.reflect;
    import java.lang.reflect.Field;


    public class FinalFieldClass {
    public int Value = 5;


    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
    FinalFieldClass finalField = new FinalFieldClass();
    Field field = FinalFieldClass.class.getDeclaredField("Value");
    field.setAccessible(true);
    field.set(finalField, 100);
    System.out.println(finalField.Value);
    }
    }

    直接执行java -cp . org.example.sourcecode.reflect.FinalFieldClass可以正常运行,打印结果100。

    但是如果在安全管理器下运行,执行java  -Djava.security.manager -cp . org.example.sourcecode.reflect.FinalFieldClass,则会出现下面的结果:
      Exception in thread "mainjava.security.AccessControlExceptionaccess denied ("java.lang.reflect.ReflectPermission" "suppressAccessChecks")  
      at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
      at java.security.AccessController.checkPermission(AccessController.java:886)
      at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
      at java.lang.reflect.AccessibleObject.setAccessible(AccessibleObject.java:128)
      at org.example.sourcecode.reflect.FinalFieldClass.main(FinalFieldClass.java:15)

      调用Field.setAccessible时,SecurityManager会调用checkPermission进行权限检查,没有权限则抛出AccessControlException。SecurityManager的内容这里就不展开了,后面会单写一篇文章进行讲解。

      C.私有成员暴露
      有些在非反射代码中的非法操作,在使用反射后就可以正常执行,如:访问私有字段和方法。因此使用反射可能会导致意外的副作用,可能导致代码功能失调,并可能破坏程序的封装特性。

      二.JAVA反射相关API

      1.获取类的Class对象
      测试类定义如下:
        package org.example.sourcecode.reflect;
        public class ReflectClass {
        static {
        System.out.println("enter static block");
        }
        }
        A.通过.class获取
          public static void main(String[] args) throws Exception {
          Class clazz = ReflectClass.class;
          System.out.println(clazz);
          System.out.println(clazz.getClassLoader());
          }
          执行结果:
            class org.example.sourcecode.reflect.ReflectClass
            sun.misc.Launcher$AppClassLoader@18b4aac2
            通过.class获取类的Class对象,并没有触发类初始化,ReflectClass的静态代码块没有执行。
            B.通过Object.getClass获取
              public static void main(String[] args) throws Exception {
              Class clazz = new ReflectClass().getClass();
              System.out.println(clazz);
              System.out.println(clazz.getClassLoader());
              }
              执行结果:
                enter static block
                class org.example.sourcecode.reflect.ReflectClass
                sun.misc.Launcher$AppClassLoader@18b4aac2
                执行new ReflectClass时,会触发类的初始化,执行ReflectClass的静态代码块。
                C.通过Class.forName获取
                Class.forName有两个重载函数 ,我们先看3个参数的方法:
                  public static void main(String[] args) throws Exception {
                  Class clazz = Class.forName("org.example.sourcecode.reflect.ReflectClass", false, ClassLoader.getSystemClassLoader());
                  System.out.println(clazz);
                  System.out.println(clazz.getClassLoader());
                  }
                  执行结果:
                    class org.example.sourcecode.reflect.ReflectClass
                    sun.misc.Launcher$AppClassLoader@18b4aac2
                    Class.forName方法的第二个参数initialize用来指定,是否需要执行类的初始化过程。第3个参数,用来指定,进行类加载的classloader。
                    接着看一下Class.forName只有一个参数的重载方法:
                      public static void main(String[] args) throws Exception {
                      Class clazz = Class.forName("org.example.sourcecode.reflect.ReflectClass");
                      System.out.println(clazz);
                      System.out.println(clazz.getClassLoader());
                      }
                      执行结果:
                        enter static block
                        class org.example.sourcecode.reflect.ReflectClass
                        sun.misc.Launcher$AppClassLoader@18b4aac2
                        只有一个参数Class.forName方法initialize设置为true,ClassLoader设置为 Reflection.getCallerClass().getClassLoader()。
                        D.通过Classload.loadClass获取
                          public static void main(String[] args) throws Exception {
                          Class clazz = ClassLoader.getSystemClassLoader().loadClass("org.example.sourcecode.reflect.ReflectClass");
                          System.out.println(clazz);
                          System.out.println(clazz.getClassLoader());
                          }
                            执行结果:
                            class org.example.sourcecode.reflect.ReflectClass
                            sun.misc.Launcher$AppClassLoader@18b4aac2
                            Classload.loadClass获取Class对象,并没有触发类初始化,ReflectClass静态代码块没有执行。

                            2.获取类的字段Field对象
                            A.Field字段获取
                            Class类上有4个方法可以获取类的字段Field,如下表所示:

                            方法说明
                            getField
                            根据字段名称获取该类及其父类上定义的public字段,如果没有找到则抛出NoSuchFieldException。
                            getDeclaredField
                            根据字段名称获取该类上定义的字段,如果没有找到则抛出NoSuchFieldException。
                            getFields
                            获取该类及其父类上定义的所有的public的字段。
                            getDeclaredFields
                            获取该类上定义的所有的字段。
                            示例代码:
                              package org.example.sourcecode.reflect;


                              import java.lang.reflect.Field;


                              public class FieldTest {


                              public static void main(String[] args) throws Exception {
                              Class clazz = Child.class;
                              display(clazz.getFields());
                              display(clazz.getDeclaredFields());
                              }


                              private static void display(Field[] fields) {
                              for (int i = 0; i < fields.length; i++) {
                              System.out.println(fields[i].getName());
                              }
                              System.out.println();
                              }


                              private static class Parent {
                              private String privateParentField;
                              protected String protectParentField;
                              String defaultParentField;
                              public String publicParentField;


                              }


                              private static class Child extends Parent {
                              private String privateChildField;
                              protected String protectChildField;
                              String defaultChildField;
                              public String publicChildField;
                              }
                              }
                              上面代码执行结果:
                                publicChildField
                                publicParentField


                                privateChildField
                                protectChildField
                                defaultChildField
                                publicChildField
                                getFields得到的是该类Child和父类Parent上定义的两个public的字段,getDeclaredFields得到的是该类Child上定义的4个字段,这4个字段的访问权限各不相同。
                                B.字段取值和赋值
                                测试类定义:
                                  package org.example.sourcecode.reflect;


                                  public class FieldValue {
                                  private int value;


                                  private static int staticValue;
                                  }
                                  测试代码如下:
                                    package org.example.sourcecode.reflect;
                                    import java.lang.reflect.Field;


                                    public class FieldValueTest {
                                    public static void main(String[] args) throws Exception {
                                    FieldValue fieldValue = new FieldValue();
                                    Field valueField = FieldValue.class.getDeclaredField("value");
                                    Field staticValueField = FieldValue.class.getDeclaredField("staticValue");
                                    valueField.setAccessible(true);
                                    valueField.set(fieldValue, 100);
                                    System.out.println(valueField.get(fieldValue));


                                    staticValueField.setAccessible(true);
                                    staticValueField.set(null, 50);
                                    System.out.println(staticValueField.get(null));
                                    }
                                    }
                                    代码执行结果:
                                      100
                                      50
                                      说明:
                                      1.如果想操作FieldValue类中private的字段,需要先调用Field.setAccessible(true),否则会抛出IllegalAccessException;
                                      2.可以通过Field.set方法设置字段的值,Field.get方法获取字段的值;
                                      3.对static静态变量,调用set和get的方法操作的目标对象,传入null即可。

                                      3.获取类的方法Method对象
                                      A.Method方法对象获取
                                      Class类上有4个方法可以获取类的方法Method,和上面获取Field的逻辑类似,如下表所示:

                                      方法说明
                                      getMethod
                                      根据方法名称和参数列表获取该类及其父类上public的方法,如果没有找到则抛出NoSuchMethodException。
                                      getDeclaredMethod
                                      根据方法名称和参数列表获取该类上定义的方法,如果没有找到则抛出NoSuchMethodException。
                                      getMethods
                                      获取该类及其父类上定义的所有的public的方法。
                                      getDeclaredMethods
                                      获取该类上定义的所有的方法。
                                      示例代码:
                                        package org.example.sourcecode.reflect;


                                        import java.lang.reflect.Method;


                                        public class MethodTest {
                                        public static void main(String[] args) {
                                        Class clazz = Child.class;
                                        display(clazz.getMethods());
                                        display(clazz.getDeclaredMethods());
                                        }


                                        private static void display(Method[] methods) {
                                        for (int i = 0; i < methods.length; i++) {
                                        System.out.println(methods[i].getName());
                                        }
                                        System.out.println();
                                        }


                                        private static class Parent {
                                        private void privateParentMethod() {
                                        }


                                        protected void protectParentMethod() {
                                        }


                                        void defaultParentMethod() {
                                        }


                                        public void publicParentMethod() {
                                        }


                                        }


                                        private static class Child extends Parent {
                                        private void privateChildMethod() {
                                        }


                                        protected void protectChildMethod() {
                                        }


                                        void defaultChildMethod() {
                                        }


                                        public void publicChildMethod() {
                                        }
                                        }
                                        }
                                        代码执行结果如下:
                                          publicChildMethod
                                          publicParentMethod
                                          wait
                                          wait
                                          wait
                                          equals
                                          toString
                                          hashCode
                                          getClass
                                          notify
                                          notifyAll


                                          protectChildMethod
                                          privateChildMethod
                                          defaultChildMethod
                                          publicChildMethod
                                          getMethods得到的是该类Child和父类Parent和Object上定义的public方法,getDeclaredMethods得到的是该类Child上定义的4个方法。
                                          B.方法执行
                                          测试类定义:
                                            package org.example.sourcecode.reflect;


                                            public class MethodInvoke {
                                            private void run(String cmd) {
                                            System.out.println("private run method : " + cmd);
                                            }


                                            private static void staticRun(String cmd) {
                                            System.out.println("private static run method : " + cmd);
                                            }
                                            }
                                            测试程序:
                                              package org.example.sourcecode.reflect;


                                              import java.lang.reflect.Method;


                                              public class MethodInvokeTest {
                                              public static void main(String[] args) throws Exception {
                                              MethodInvoke methodClass = new MethodInvoke();
                                              Method run = methodClass.getClass().getDeclaredMethod("run", new Class[]{String.class});
                                              run.setAccessible(true);
                                              run.invoke(methodClass, "delete");


                                              Method staticRun = methodClass.getClass().getDeclaredMethod("staticRun", new Class[]{String.class});
                                              staticRun.setAccessible(true);
                                              staticRun.invoke(null, "query");
                                              }
                                              }
                                              上面代码执行结果:
                                                private run method : delete
                                                private static run method : query
                                                说明:
                                                1.如果想操作MethodInvoke类中private的方法,需要先调用Method.setAccessible(true),否则会抛出IllegalAccessException;
                                                2.可以通过Method.invoke进行方法的调用;
                                                3.对static静态方法,调用invoke的目标对象,传入null即可。

                                                4.获取类的构造函数Constructor对象
                                                A.Constructor对象获取
                                                Class类上有4个方法可以获取类的构造函数Constructor,如下表所示:

                                                方法说明
                                                getConstructor
                                                根据参数列表获取该类上public的构造函数,如果没有找到则抛出NoSuchMethodException。
                                                getDeclaredConstructor
                                                根据参数列表获取该类上该类上定义的构造函数,如果没有找到则抛出NoSuchMethodException。
                                                getConstructors
                                                获取该类上定义的所有的public的构造函数。
                                                getDeclaredConstructors
                                                获取该类上定义的所有的构造函数。
                                                根据参数列表获取该类上该类上定
                                                示例代码:
                                                  package org.example.sourcecode.reflect;


                                                  import java.lang.reflect.Constructor;


                                                  public class ConstructorTest {
                                                  public static void main(String[] args) {
                                                  Class clazz = Child.class;
                                                  display(clazz.getConstructors());
                                                  display(clazz.getDeclaredConstructors());
                                                  }


                                                  private static void display(Constructor[] constructors) {
                                                  for (int i = 0; i < constructors.length; i++) {
                                                  System.out.print(constructors[i].getName() + " : ");
                                                  displayParameterTypes(constructors[i].getParameterTypes());
                                                  }
                                                  System.out.println();
                                                  }


                                                  private static void displayParameterTypes(Class[] parameterTypes) {
                                                  for (int i = 0; i < parameterTypes.length; i++) {
                                                  System.out.print(parameterTypes[i] + "\t");
                                                  }
                                                  System.out.println();
                                                  }

                                                  private static class Parent {
                                                  private Parent() {}
                                                  protected Parent(byte a) {}
                                                  Parent(short a) {}
                                                  public Parent(int a) {}
                                                  }


                                                  private static class Child extends Parent {
                                                  private Child() {}
                                                  protected Child(byte a) {}
                                                  Child(short a) {}
                                                  public Child(int a) {}
                                                  }
                                                  }
                                                  上面代码执行结果:

                                                    org.example.sourcecode.reflect.ConstructorTest$Child : int


                                                    org.example.sourcecode.reflect.ConstructorTest$Child : int
                                                    org.example.sourcecode.reflect.ConstructorTest$Child : short
                                                    org.example.sourcecode.reflect.ConstructorTest$Child : byte
                                                    org.example.sourcecode.reflect.ConstructorTest$Child :
                                                    构造函数的是用来创建相应的对象的。上面代码,使用Child类不同的构造函数,都可以创建出对象。另外构造函数Contructor不像获取Method一样,还去父类进行查找,因为构造函数是不能被子类继承的。
                                                    B.构造函数创建对象
                                                      package org.example.sourcecode.reflect;


                                                      import java.lang.reflect.Constructor;


                                                      public class ConstructorInvokeTest {
                                                      public static void main(String[] args) throws Exception {
                                                      Class clazz = Child.class;
                                                      Constructor constructorDefault = clazz.getDeclaredConstructor(new Class[0]);
                                                      constructorDefault.setAccessible(true);
                                                      Child child = (Child) constructorDefault.newInstance();
                                                      System.out.println(child);


                                                      Constructor constructorInt = clazz.getDeclaredConstructor(new Class[]{int.class});
                                                      Child childInt = (Child) constructorInt.newInstance(1);
                                                      System.out.println(childInt);


                                                      Constructor constructorShort = clazz.getDeclaredConstructor(new Class[]{short.class});
                                                      Child childShort = (Child) constructorShort.newInstance((short) 1);
                                                      System.out.println(childShort);
                                                      }


                                                      private static class Child {
                                                      private Child() {}
                                                      protected Child(byte a) {}
                                                      Child(short a) {}
                                                      public Child(int a) {}
                                                      }
                                                      }
                                                      说明:
                                                      1.如果想使用Child类中private的构造函数,需要先调用Constructor.setAccessible(true),否则会抛出IllegalAccessException;
                                                      2.可以通过Constructor.newInstance创建对象,得到的结果是Object类型,在进行强制类型转换即可。
                                                      到此,反射的基本用法就讲完了。由于反射调用的性能问题,JDK对其进行了优化,下一章讲解反射调用优化,敬请期待。
                                                      ——————
                                                      进行业交流群
                                                      👇推荐关注👇
                                                      有趣的行业资讯
                                                      干货技术分享
                                                      程序员的日常生活
                                                      ......
                                                      干就完了

                                                      文章转载自马士兵,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                                      评论