作为Java开发者,你认为反射这个知识点重要水平,在你心里是什么样的呢?

以前我也只感觉反射十分重要,但总归是听这个文章说,听那个敌人说,学是学了,但却没怎么利用。

当我正式进入到社会当 cv 仔的时候,须要思考的问题多了,缓缓思考问题了,就感觉反射是个力大无穷的货色,更会感觉反射是个无所不能的货色,如各种各样的框架的底层,各种各样的拦截器的实现,反射都是其中少不了的一部分~

如果平时着重于开发业务的话,那么的确会较少应用到反射机制,但并非是说反射它不重要,反射它可是搭建脚手架的根底的根底勒~

文章大抵思路:

全文共 7500 字左右,案例均可运行,浏览工夫大概须要20分钟左右,如有问题,请留言或发送邮件(mailto:nzc_wyh@163.com)。

编写的过程中,即便书写完已浏览过,但不免可能会呈现脱漏,如有发现问题请及时分割修改,非常感谢你的浏览,心愿咱们都能成为技术路线上的敌人。

一、反射是什么?

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

不过要想解剖一个类,就要先获取到该类的字节码文件对应的Class类型的对象.

稍后就会讲到~

"反射之所以被称为框架的灵魂",次要是因为它赋予了咱们在运行时剖析类以及执行类中办法的能力,这种能力也就是咱们常说的动态性,利用这种性质能够使编写的程序更灵便更通用。

反射机制能够用来:

  • 在运行时剖析类的能力,如能够结构任意一个类,能够获取任意一个类的全副信息,
  • 在运行时查看对象,如在运行时判断任意一个对象所属的类
  • 实现泛型数组操作代码,因为在运行时能够获取泛型信息
  • 利用Method对象,如咱们常常应用的动静代理,就是应用Method.invoke()来实现办法的调用。

反射是一种功能强大且简单的机制,在开发Java工具或框架方面,反射更是不可短少的一部分。

二、Class 对象详解

之前说到了,如果要剖析一个类,就必须要获取到该类的字节码文件对应 Class 类型对象。

另外如果有听到类模板对象,这个说的其实就是Class对象,大家不要误会了。

2.1、如何获取到Class对象呢?

失去Class的形式总共有四种:

  • 通过对象调用 getClass()办法来获取
  • 间接通过类名.class 的形式失去
  • 通过 Class对象的 forName() 静态方法来获取
  • Classloader,通过类加载器进行获取
 /**  * @description:  * @author: Ning Zaichun  * @date: 2022年09月15日 22:49  */ public class ClassDemo01 {      @Test     public void test1() throws Exception {         //1、通过对象调用 getClass() 办法来获取         //  类型的对象,而我不晓得你具体是什么类,用这种办法         Student student = new Student();         Class studentClass1 = student.getClass();         System.out.println(studentClass1);         // out: class com.nzc.Student          //2、间接通过`类名.class` 的形式失去         // 任何一个类都有一个隐含的动态成员变量 class         // Class studentClass2 = Student.class;         Class<?> studentClass2 = Student.class;         System.out.println(studentClass2);         // out: class com.nzc.Student         System.out.println("studentClass1和studentClass2 是否相等==>" + (studentClass1 == studentClass2));         // studentClass1和studentClass2 是否相等==>true          //3、通过 Class 对象的 forName() 静态方法来获取,应用的最多         //   但须要抛出或捕捉 ClassNotFoundException 异样         Class<?> studentClass3 = Class.forName("com.nzc.Student");         System.out.println(studentClass3);         // out: class com.nzc.Student         System.out.println("studentClass1和studentClass3 是否相等==>" + (studentClass1 == studentClass3));         //studentClass1和studentClass3 是否相等==>true          //4、 应用类的加载器:ClassLoader 来获取Class对象         ClassLoader classLoader = ClassDemo01.class.getClassLoader();         Class studentClass4 = classLoader.loadClass("com.nzc.Student");         System.out.println(studentClass4);         System.out.println("studentClass1和studentClass4 是否相等==>" + (studentClass1 == studentClass4));         //studentClass1和studentClass4 是否相等==>true     } }

在这四种形式中,最常应用的是第三种形式,第一种都间接new对象啦,齐全没有必要再应用反射了;第二种形式也曾经明确了类的名称,相当于曾经固定下来,失去了一种动静抉择加载类的成果;而第三种形式,只有传入一个字符串,这个字符串能够是本人传入的,也能够是写在配置文件中的。

像在学 JDBC 连贯的时候,大家必定都应用过 Class对象的 forName() 静态方法来获取Class对象,再加载数据库连贯对象,但可能那时候只是匆匆而过罢了。

留神:不晓得大家有没有察看,我把各种形式所获取到的class对象,都进行了一番比拟,并且后果都为true,这是因为一个类在 JVM 中只会有一个 Class 实例,为了解释此点,我把类加载过程也简略的做了一个陈说。

2.2、类的加载过程

当咱们须要应用某个类时,如果该类还未被加载到内存中,则会经验上面的过程对类进行初始化。

即类的加载 ---> 链接 ---> 初始化三个阶段。

在这里我只着眼于类的加载过程了,想要理解更为具体的,就须要大家去找找材料看看啦~

加载过程

1、在咱们进行编译后(javac.exe命令),会生成一个或多个字节码文件(就是我的项目中编译完会呈现的 target 目录下的以.class结尾的文件)

2、接着当咱们应用 java.exe 命令对某个字节码文件进行解释运行时。

3、加载过程

  • 就相当于将 class 文件字节码内容加载到内存中,并将这些静态数据转换成办法区的运行时数据结构;
  • 并生成一个代表这个类的 java.lang.Class 对象,这个加载到内存中的类,咱们称为运行时类,此运行时类,就是 Class的一个实例,所有须要拜访和应用类数据只能通过这个 Class 对象。
  • 所谓Class对象,也称为类模板对象,其实就是 Java 类在 JVM 内存中的一个快照,JVM 将从字节码文件中解析出的常量池、 类字段、类办法等信息存储到模板中,这样 JVM 在运行期便能通过类模板而获 取 Java 类中的任意信息,可能对 Java 类的成员变量进行遍历,也能进行 Java 办法的调用。
  • 反射的机制即基于这一根底。如果 JVM 没有将 Java 类的申明信息存储起来,则 JVM 在运行期也无奈进行反射。

4、这个加载的过程还须要类加载器参加,对于类加载器的类型大家能够去理解理解,还有双亲委派机制等,此处我便不再多言

2.3、为了更便于记忆的图

(图片说明:为更好的形容JVM中只有一个Class对象,而画下此图,心愿通过这张简图,让你记忆更为粗浅)

2.4、Class 罕用的API

通过 Class 类获取成员变量、成员办法、接口、超类、构造方法等

  • getName():取得类的残缺名字。
  • getFields():取得类的public类型的属性。
  • getDeclaredFields():取得类的所有属性。包含private申明的和继承类
  • getMethods():取得类的public类型的办法。
  • getDeclaredMethods():取得类的所有办法。包含private申明的和继承类
  • getMethod(String name, Class[] parameterTypes):取得类的特定办法,name参数指定办法的名字,parameterTypes 参数指定办法的参数类型。
  • getConstructors():取得类的public类型的构造方法。
  • getConstructor(Class[] parameterTypes):取得类的特定构造方法,parameterTypes参数指定构造方法的参数类型。
  • newInstance():通过类的不带参数的构造方法创立这个类的一个对象。

另外就还有反射包下的几个罕用的对象Constructor、Filed、Method等,别离示意类的结构器、字段属性、办法等等

这些都会在下文缓缓陈说进去~

三、获取运行时类残缺信息并应用

所谓运行时类,就是程序运行时所创立进去的类,你间接了解为通过反射获取到的类也可。大体意思是如此。

在讲述这一大节时,先要了解Java中所有皆对象这句话。

咱们平时编写一个类,咱们会将它称为一个Java对象,然而在反射这里将此概念再次向上形象了。

类的根本信息:构造方法、成员变量,办法,类上的注解,办法注解,成员变量注解等等,这些都是Java对象,也是证实了Java中所有皆对象这句话。

其中Constructor就示意构造方法的对象,他蕴含了构造方法的所有信息,

Field、Method等等都是如此。

不要太过于麻烦和反复书写,我将案例中操作的所有相干代码,都放在此处了,案例中的依赖全副基于此。

 public interface TestService { }
 @Target({ElementType.TYPE,ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MarkAnnotation {      String value() default "";  }
 public class Generic<T> { } 
 public interface GenericInterface<T> { } 
 // TYPE 示意能够标记在类上 //  PARAMETER 示意能够标记在办法形式参数 //  METHOD 办法 //  FIELD 成员属性上 @Target({ElementType.TYPE,ElementType.METHOD, ElementType.FIELD,ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) // 这里表明是运行时注解 @Documented public @interface LikeAnnotation {      String value() default "";       String[] params() default {}; }
 @Data @MarkAnnotation public class Person implements TestService{     private String sex;      public Integer age;      private void mySex(String sex){         System.out.println("我的性别"+sex);     }      public void myAge(Integer age){         System.out.println("我的年龄"+age);     }   }
 @ToString(callSuper = true) // 减少这行是为了打印时将父类属性也打印进去,不便查看~ @Data @LikeAnnotation(value = "123") public class Student extends Person implements Serializable {     @LikeAnnotation     private String username;      @LikeAnnotation     public String school;      private Double score;      public Student() {     }      private Student(String username) {         this.username = username;     }      public Student(String username, String school) {         this.username = username;         this.school = school;     }      @LikeAnnotation     public void hello() {         System.out.println("世界,你好");     }      public void say( String username) {         System.out.println("你好,我叫" + username);     }      private void myScore(Double score) {         System.out.println("我的分数是一个私密货色," + score);     }      public void annotationTest(@LikeAnnotation  String username,@MarkAnnotation String str){         System.out.println( "测试获取办法参数中的注解信息");     } }

3.1、反射获取运行时类构造方法并应用

class获取构造方法的相干API

 // 获取所有的构造函数 Constructor<?>[] getConstructors()  // 获取 public或 private 润饰的狗赞函数,只有参数匹配即可 Constructor<?>[] getDeclaredConstructors()  // 获取所有 public 润饰的 构造函数 Constructor<T> getConstructor(Class<?>... parameterTypes)      //调用此办法,创立对应的运行时类的对象。 public T newInstance(Object ... initargs)

newInstance():调用此办法,创立对应的运行时类的对象。外部调用了运行时类的空参的结构器。

要想此办法失常的创立运行时类的对象,要求:

  • 运行时类必须提供空参的结构器;
  • 空参的结构器的拜访权限得够。通常,设置为 public。

为什么要 javabean 中要求提供一个 public 的空参结构器?

起因: 1、便于通过反射,创立运行时类的对象;2、便于子类继承此运行时类时,默认调用 super() 时,保障父类有此构 造器。

想要更具体的理解,倡议去看生成的字节码文件,在那里可能给出你答案。

测试

 /**  * @description:  * @author: Ning Zaichun  * @date: 2022年09月17日 1:17  */ public class ConstructorTest {      /**      * 获取私有、公有的构造方法 并调用创建对象      */     @Test     public void test1() throws Exception {         Class<?> aClass = Class.forName("com.nzc.Student");          System.out.println("======获取全副public 润饰的构造方法=========");         Constructor<?>[] constructors = aClass.getConstructors();         for (Constructor<?> constructor : constructors) {             System.out.println(constructor);         }         System.out.println("======获取 public、private 润饰的构造方法,只有参数匹配即可=========");         /**          * 这里的参数 Class<?>... parameterTypes 填写的是参数的类型,而并非某个精确的值信息          */         Constructor<?> constructor = aClass.getDeclaredConstructor(String.class);         System.out.println(constructor);         // 因为此构造函数是 private 润饰,如果不设置暴力反射,则没有权限拜访。         // 这里setAccessible(true) 是设置暴力反射,如果不设置,则会报错,         constructor.setAccessible(true);         // 这里调用的有参结构,所以在调用 newInstance 办法时,也须要填写参数         Object o1 = constructor.newInstance("宁在春");         constructor.setAccessible(false);         System.out.println("o1===>"+o1);          /**          * 如果须要获取有参结构,只须要填写对应的参数类型即可,          * 获取无参结构,填null或不填都可。          */         Constructor<?> constructor1 = aClass.getConstructor();         Constructor<?> constructor2 = aClass.getConstructor(String.class,String.class);         System.out.println("无参结构==>"+constructor1);         System.out.println("有参结构==>"+constructor2);         Object o2 = constructor1.newInstance();         Object o3 = constructor2.newInstance("宁在春2","xxxx社会");         System.out.println("o2===>"+o2);         System.out.println("o3===>"+o3);     } }

既然可能获取到构造方法,那么也就是能够应用的,用Constructor.newInstance()办法来调用构造方法即可,在下列的打印信息中,也能够看进去的确如此,如果明确要获取为Student对象的话,进行强转即可。

 ======获取全副public 润饰的构造方法========= public com.nzc.Student(java.lang.String,java.lang.String) public com.nzc.Student() ======获取 public、private 润饰的构造方法,只有参数匹配即可========= private com.nzc.Student(java.lang.String) o1===>Student(username=宁在春, school=null, age=null) 无参结构==>public com.nzc.Student() 有参结构==>public com.nzc.Student(java.lang.String,java.lang.String) o2===>Student(username=null, school=null, age=null) o3===>Student(username=宁在春2, school=xxxx社会, age=null)

3.2、反射获取运行时类成员变量信息

class对象中获取类成员信息应用到的API

 Field[] getFields();   Field[] getDeclaredFields();  Field getDeclaredField(String name);  public native Class<? super T> getSuperclass();

这里的 Field 类对象,其实就是示意类对象中的成员属性,不过还有多了很多其余在反射时须要用到的属性和API罢了~

3.2.1、获取公有私有类成员信息

为了有更好的比照,我先编写了一段不应用反射时的失常操作。

 /**      * 不应用反射的进行操作      */ @Test public void test1() {     Student student = new Student();     student.setUsername("username");     student.setSchool("xxxx社会");     student.setScore(100.0);     // 永远三岁的小伙子 哈哈      student.setAge(3);     student.setSex("男");      System.out.println("student信息===>" + student);     // out:student信息===>Student(super=Person(sex=男, age=3), username=username, school=xxxx社会, score=100.0) }

当初我再应用反射来实现上述操作~

在实现之前,还是先来看看如何获取私有、公有的成员变量吧

 @Test public void test2() throws Exception {     Class<?> stuClass = Class.forName("com.nzc.Student");      System.out.println("========获取所有 public 润饰的成员属性(包含父类属性)=====");     Field[] fields = stuClass.getFields();     for (Field field : fields) {         System.out.println(field);     }     //public java.lang.String com.nzc.Student.school     //public java.lang.Integer com.nzc.Person.age      System.out.println("========获取所有(public、private、protected等润饰的)属性成员(不包含父类成员属性)=====");     Field[] fields1 = stuClass.getDeclaredFields();     for (Field field : fields1) {         System.out.println(field);     }     //private java.lang.String com.nzc.Student.username     //public java.lang.String com.nzc.Student.school     //private java.lang.Double com.nzc.Student.score      System.out.println("========通过反射,获取对象指定的属性=====");     Field username = stuClass.getDeclaredField("username");     System.out.println("username===>" + username); }

然而你发现没有,这无奈获取到父类的成员变量信息,父类的信息,咱们该如何获取呢?

3.2.2、获取父类成员属性信息

其实一样的,咱们也是要获取到父类的Class对象,在Class API中有一个getSuperClass()办法能够获取到父类的class对象,其余的操作都是一样的~

 @Test public void test5() throws ClassNotFoundException {     Class<?> stuClass = Class.forName("com.nzc.Student");     System.out.println("========获取所有属性成员(包含父类成员属性)=====");     Class clazz = stuClass;     List<Field> fieldList = new ArrayList<>();     while (clazz != null) {         fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));         clazz = clazz.getSuperclass();     }     Field[] fields2 = new Field[fieldList.size()];     fieldList.toArray(fields2);     for (Field field : fields2) {         System.out.println(field);     }     //private java.lang.String com.nzc.Student.username     //public java.lang.String com.nzc.Student.school     //private java.lang.Double com.nzc.Student.score     //private java.lang.String com.nzc.Person.sex     //public java.lang.Integer com.nzc.Person.age }

到这里咱们曾经晓得如何获取到类的成员变量信息了,就能够来看看如何给它们设置值,或者通过反射的形式来获取到值了。

3.2.3、设置或批改类成员变量值

 @Test public void test3() throws Exception {     Class<?> stuClass = Class.forName("com.nzc.Student");     System.out.println("获取到 filed 后,设置值或批改值");     Constructor<?> constructor = stuClass.getConstructor();     Object o = constructor.newInstance();      //操作pubilc 属性     Field school = stuClass.getDeclaredField("school");     school.set(o,"xxxx社会");     System.out.println(o);     //Student(super=Person(sex=null, age=null), username=null, school=xxxx社会, score=null)     // 另外既然能够设置,那么天然也是能够获取的     System.out.println(school.get(o));     //xxxx社会      // 操作 private 润饰的成员属性     Field name = stuClass.getDeclaredField("username");     // setAccessible(true) 因为是获取private 润饰的成员变量     // 不设置暴力反射 是无奈获取到的,会间接报 IllegalAccessException 异样     name.setAccessible(true);     name.set(o,"Ningzaichun");     name.setAccessible(false);     System.out.println(o);     //Student(super=Person(sex=null, age=null), username=Ningzaichun, school=xxxx社会, score=null) }

其实看到这里对于反射曾经有个大略的印象了,并且这都是比拟平时且实用的一些办法。

3.2.4、获取成员变量注解信息

 @Test public void test4() throws Exception {     Class<?> stuClass = Class.forName("com.nzc.Student");     Field school = stuClass.getDeclaredField("school");      LikeAnnotation annotation = school.getAnnotation(LikeAnnotation.class);     System.out.println(annotation);     //@com.nzc.LikeAnnotation(params=[], value=)      Annotation[] annotations = school.getAnnotations();     for (Annotation annotation1 : annotations) {         System.out.println(annotation1);     }     //@com.nzc.LikeAnnotation(params=[], value=)       LikeAnnotation declaredAnnotation = school.getDeclaredAnnotation(LikeAnnotation.class);     System.out.println(declaredAnnotation);     //@com.nzc.LikeAnnotation(params=[], value=)      Annotation[] declaredAnnotations = school.getDeclaredAnnotations();     for (Annotation declaredAnnotation1 : declaredAnnotations) {         System.out.println(declaredAnnotation1);     }     //@com.nzc.LikeAnnotation(params=[], value=) }

对于getAnnotation、getDeclaredAnnotation 的剖析大家能够看看上面这篇文章@Repeatable详解-getAnnotation、getDeclaredAnnotation获取不到对象

对于想要更具体的理解Java注解的敌人,大家能够持续找找相干材料。

3.3、反射获取运行时类对对象办法信息并调用

Class对象中应用的相干API

 Method[] getMethods();  Method getMethod(String name, Class<?>... parameterTypes);  Method[] getDeclaredMethods();  Method getDeclaredMethod(String name, Class<?>... parameterTypes);  // 获取办法返回值类型 Class<?> getReturnType();  // obj – 调用底层办法的对象  // args – 用于办法调用的参数  // return 应用参数args在obj上调度此对象示意的办法的后果 Object invoke(Object obj, Object... args)

3.3.1、获取对象办法

获取私有、公有办法

 @Test public void test1() throws Exception {     Class<?> stuClass = Class.forName("com.nzc.Student");      System.out.println("========获取所有public润饰的办法======");     Method[] methods = stuClass.getMethods();     for (Method method : methods) {         System.out.println(method);     }     //public boolean com.nzc.Student.equals(java.lang.Object)     //public java.lang.String com.nzc.Student.toString()     //public int com.nzc.Student.hashCode()     //public void com.nzc.Student.hello()     //public void com.nzc.Student.say(java.lang.String)     //public java.lang.Double com.nzc.Student.getScore()     //public void com.nzc.Student.setScore(java.lang.Double)     //public void com.nzc.Student.setSchool(java.lang.String) .... 还有一些没写进去了      System.out.println("========获取对象【指定的public润饰的办法】======");     // 第一个参数为办法名,此处是获取public润饰的无参办法     Method hello = stuClass.getMethod("hello");     System.out.println("hello===>"+hello);     // 带参办法,第二个参数填写【参数类型】     Method say = stuClass.getMethod("say", String.class);     System.out.println("say===>"+say);     //hello===>public void com.nzc.Student.hello()     //say===>public void com.nzc.Student.say(java.lang.String)       // 参数为 办法名,此处是获取 private 润饰的无参办法     // stuClass.getDeclaredMethod("");     Method myScore = stuClass.getDeclaredMethod("myScore", Double.class);     System.out.println("myScore==>"+myScore);     //myScore==>private void com.nzc.Student.myScore(java.lang.Double)      System.out.println("=======获取所有的办法=======");     Method[] declaredMethods = stuClass.getDeclaredMethods();     for (Method declaredMethod : declaredMethods) {         System.out.println(declaredMethod);     }     //public void com.nzc.Student.say(java.lang.String)     //private void com.nzc.Student.myScore(java.lang.Double)     //public java.lang.Double com.nzc.Student.getScore()     //public void com.nzc.Student.setScore(java.lang.Double)     //protected boolean com.nzc.Student.canEqual(java.lang.Object) }

既然能获取到,那么也是可能调用的啦~,

3.3.2、调用对象办法

还记得常常能看到的一个invoke()办法吗,这里就是调用method.invoke()办法来实现办法的调用。

 @Test public void test2() throws Exception {     Class<?> stuClass = Class.forName("com.nzc.Student");     Constructor<?> constructor = stuClass.getConstructor();     Object o = constructor.newInstance();      Method myScore = stuClass.getDeclaredMethod("myScore", Double.class);     myScore.setAccessible(true);     // 调用办法     myScore.invoke(o, 99.0);     // 相当于 Student student= new Student();     // student.myScore(99.0);     myScore.setAccessible(false); }

之前论述了 Method它自身就记录了办法的所有信息,咱们实现调用也就是第一步罢了,说它能够获取到过后定义方法的所有都不为过,接下来一步一步来说吧。

3.3.3、获取办法上的注解信息

 @Test public void test2() throws Exception {     Class<?> stuClass = Class.forName("com.nzc.Student");     System.out.println("==== 获取成员变量上指定的注解信息===");      Field username = stuClass.getDeclaredField("username");     System.out.println(username);     //private java.lang.String com.nzc.Student.username     Annotation annotation = username.getAnnotation(LikeAnnotation.class);     System.out.println(annotation);     //@com.nzc.LikeAnnotation(params=[], value=)      Method hello = stuClass.getDeclaredMethod("hello");     LikeAnnotation annotation1 = hello.getAnnotation(LikeAnnotation.class);     System.out.println(hello+"===="+annotation1);     // public void com.nzc.Student.hello()====@com.nzc.LikeAnnotation(params=[], value=) }

3.3.4、获取办法参数及参数注解信息

不过在写我的项目时,有可能还会要获取办法参数上的注解,那该如何获取办法参数呢?又该如何获取办法参数的注解信息呢?

 @Test public void test3() throws Exception {     Class<?> stuClass = Class.forName("com.nzc.Student");     System.out.println("==== 获取办法参数中的注解信息===");      Method annotationTest = stuClass.getDeclaredMethod("annotationTest",String.class,String.class);      // 获取办法的返回值类型     Class<?> returnType = annotationTest.getReturnType();     // 获取权限修饰符         System.out.println(Modifier.toString(annotationTest.getModifiers()) );     // 获取到全副的办法参数     Parameter[] parameters = annotationTest.getParameters();     for (Parameter parameter : parameters) {         Annotation annotation = parameter.getAnnotation(LikeAnnotation.class);         if(annotation!=null){             // 参数类型             Class<?> type = parameter.getType();             // 参数名称             String name = parameter.getName();             System.out.println("参数类型"+type+"  参数名称==>"+name+" 参数上的注解信息"+annotation);             //参数类型class java.lang.String  参数名称==>arg0 参数上的注解信息@com.nzc.LikeAnnotation(params=[], value=)         }     }     // 获取参数上全副的注解信息     Annotation[][] parameterAnnotations = annotationTest.getParameterAnnotations();     for (int i = 0; i < parameterAnnotations.length; i++) {         for (int i1 = 0; i1 < parameterAnnotations[i].length; i1++) {             System.out.println(parameterAnnotations[i][i1]);         }     }     // @com.nzc.LikeAnnotation(params=[], value=)     //@com.nzc.MarkAnnotation(value=)     int parameterCount = annotationTest.getParameterCount();     System.out.println("获取参数个数==>"+parameterCount); }  

3.3.5、获取办法返回参数和办法权限修饰符

 @Test public void test33() throws Exception {     Class<?> stuClass = Class.forName("com.nzc.Student");      Method annotationTest = stuClass.getDeclaredMethod("annotationTest",String.class,String.class);     // 获取办法的返回值类型     Class<?> returnType = annotationTest.getReturnType();     System.out.println(returnType);     //void     // 获取权限修饰符     System.out.println(Modifier.toString(annotationTest.getModifiers()) );     //public }

3.4、反射获取运行时类信息、接口信息、包信息

3.4.1、获取运行时类的接口信息

 /**      *  获取运行时类实现的接口      */ @Test public void test5() {     Class clazz = Student.class;     Class[] interfaces = clazz.getInterfaces();     for (Class c : interfaces) {         System.out.println(c);     }     System.out.println("====================");     // 获取运行时类的父类实现的接口     Class[] interfaces1 = clazz.getSuperclass().getInterfaces();     for (Class c : interfaces1) {         System.out.println(c);     } }

3.4.2、获取类所在包的信息

 /**      * 获取运行时类所在的包      */ @Test public void test6() {     Class clazz = Person.class;     Package pack = clazz.getPackage();     System.out.println(pack);     // out:package com.nzc }

3.4.3、获取类上注解信息

 @Test public void test1() throws Exception {     Class<?> stuClass = Class.forName("com.nzc.Student");     System.out.println("==== 获取类上指定的注解信息===");      LikeAnnotation annotation = stuClass.getAnnotation(LikeAnnotation.class);     System.out.println(annotation);     //@com.nzc.LikeAnnotation(params=[], value=123)     System.out.println("获取注解上的值信息==>"+annotation.value());     //获取注解上的值信息==>123     //annotation.params(); 注解有几个属性,就能够获取几个属性      System.out.println("====  获取类上全副注解信息===");      Annotation[] annotations = stuClass.getAnnotations();     for (Annotation annotation1 : annotations) {         System.out.println(annotation1);         //@com.nzc.LikeAnnotation(params=[], value=123)     }      LikeAnnotation declaredAnnotation = stuClass.getDeclaredAnnotation(LikeAnnotation.class);     System.out.println("declaredAnnotation==>"+declaredAnnotation);     //declaredAnnotation==>@com.nzc.LikeAnnotation(params=[], value=123)     Annotation[] declaredAnnotations = stuClass.getDeclaredAnnotations();  }

留神:lombok 相干的注解是无奈获取到的,因为 lombok 注解为编译时注解,并非是运行时注解,在编译实现后,lombok 注解并不会保留于class文件中,因而是无奈通过反射获取到的。

@Data 也表明了它的存在级别为源码级别,而运行时存在注解@Retention@Retention(RetentionPolicy.RUNTIME)

3.5、反射获取运行时类的父类的泛型信息、接口泛型信息

 @Test public void test8(){     Class clazz = Person.class;      // 获取泛型父类信息     Type genericSuperclass = clazz.getGenericSuperclass();     System.out.println(genericSuperclass);     // com.nzc.Generic<java.lang.String>     // 获取泛型接口信息     Type[] genericInterfaces = clazz.getGenericInterfaces();      for (Type genericInterface : genericInterfaces) {         System.out.println(genericInterface);         //interface com.nzc.TestService         //com.nzc.GenericInterface<java.lang.String>     } }

四、反射利用场景及实战案例

5.1、那到底什么时候会应用反射

一句话说它的利用场景就是:确定不下来到底应用哪个类的时候,比方你要开发一个通用工具类,为了达到通用性,传入的参数对象,个别都是无奈限度的,这个时候就是要用到反射啦~。

反射的特色:动态性

5.2、AOP + 自定义注解 批改前端传递的参数信息

需要如下:我当初引入了一个第三方 jar 包,外面有一个 MyBatis-Plus 查问结构器,其中结构 LIKE条件查问的条件是以后端传过来的参数带有逗号时,拼接为LIKE查问条件。

要害代码:

标识在对象的某个成员属性上

 @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LikeAnnotation {      String value() default ""; } 

标识在Controller层上,以此来判断那些申请是须要被切入的。

 @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MarkAnnotation {      String value() default "";  }

一个非常简单的Javabean

 @Data public class Login {      @LikeAnnotation(value = "username")     private String username;      private String password;      public Login() {     }      public Login(String username, String password) {         this.username = username;         this.password = password;     } } 

Controller 类

 @Slf4j @RestController public class HelloController {      @MarkAnnotation     @GetMapping("/search")     public Login getLikeLogin(Login login){         System.out.println(login);         return login;     }  }

重点重点,切面类 LikeAnnotationAspect,解决逻辑全副在此处

 @Aspect @Component @Slf4j public class LikeAnnotationAspect {      // 标记了 @MarkAnnotation 注解的才切入 升高性能耗费     @Pointcut("@annotation(com.nzc.annotation.MarkAnnotation)")     public void pointCut() {      }      // 获取以后类及父类所有的成员属性     private static Field[] getAllFields(Object object) {         Class<?> clazz = object.getClass();         List<Field> fieldList = new ArrayList<>();         while (clazz != null) {             fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));             clazz = clazz.getSuperclass();         }         Field[] fields = new Field[fieldList.size()];         fieldList.toArray(fields);         return fields;     }      @Around("pointCut()")     public Object doAround(ProceedingJoinPoint pjp) throws Throwable {         long start = System.currentTimeMillis();         Object[] args = pjp.getArgs();         // 获取到第一个参数         Object parameter = args[0];          log.info("parameter==>{}", parameter);         if (parameter == null) {             return pjp.proceed();         }          Field[] fields = getAllFields(parameter);          for (Field field : fields) {             log.debug("------field.name------" + field.getName());             if (field.getAnnotation(LikeAnnotation.class) != null) {                 try {                     field.setAccessible(true);                     Object username = field.get(parameter);                     field.setAccessible(false);                     if (username != null && !username.equals("")) {                         field.setAccessible(true);                         field.set(parameter, "," + username + ",");                         field.setAccessible(false);                     }                 } catch (Exception e) {                 }             }         }         // 调用办法         Object result = pjp.proceed();         long end = System.currentTimeMillis();         log.debug("批改耗时==>" + (end - start) + "ms");         return result;     }  }

实现成果:

5.3、Mybatis 拦截器实现主动填充创建人、批改人信息

看似如同写业务的咱们,没有怎么接触Java反射,但实际上可能处处都隐含着反射。

应用过 Mybatis-Plus 的敌人,应该晓得,能够设置主动填充数据(创立工夫、更新工夫、创建人、更新人等),不过那个是实现MetaObjectHandler接口进行解决的。

然而明天的话,我用Mybatis 原生的拦截器来进行一番实现,实现每次更新、增加时主动填充创建人、更新人等,表里没工夫字段,就没演示工夫了,但实现原理都统一。

 import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.binding.MapperMethod.ParamMap; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.plugin.*; import org.springframework.stereotype.Component;  import java.lang.reflect.Field; import java.util.*;  /**  * mybatis拦截器,主动注入创建人、创立工夫、批改人、批改工夫  * @author Ning Zaichun  */ @Slf4j @Component @Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }) }) public class MybatisInterceptor implements Interceptor {          public static Field[] getAllFields(Object object) {         Class<?> clazz = object.getClass();         List<Field> fieldList = new ArrayList<>();         while (clazz != null) {             fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));             clazz = clazz.getSuperclass();         }         Field[] fields = new Field[fieldList.size()];         fieldList.toArray(fields);         return fields;     }      @Override     public Object intercept(Invocation invocation) throws Throwable {         MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];         String sqlId = mappedStatement.getId();         log.info("------sqlId------" + sqlId); //2022-09-17 17:16:50.714  INFO 14592 --- [nio-8080-exec-1] com.nzc.tree.commons.MybatisInterceptor  : ------sqlId------com.nzc.tree.mapper.CategoryMapper.updateById         SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();         Object parameter = invocation.getArgs()[1];         log.info("------sqlCommandType------" + sqlCommandType); //2022-09-17 17:16:50.714  INFO 14592 --- [nio-8080-exec-1] com.nzc.tree.commons.MybatisInterceptor  : ------sqlCommandType------UPDATE          if (parameter == null) {             return invocation.proceed();         }         if (SqlCommandType.INSERT == sqlCommandType) {             Field[] fields = getAllFields(parameter);             for (Field field : fields) {                 log.info("------field.name------" + field.getName());                 try {                     if ("createBy".equals(field.getName())) {                         field.setAccessible(true);                         Object local_createBy = field.get(parameter);                         field.setAccessible(false);                         if (local_createBy == null || local_createBy.equals("")) {                                 field.setAccessible(true);                                 field.set(parameter, "nzc-create");                                 field.setAccessible(false);                             }                     }                 } catch (Exception e) {                     e.printStackTrace();                 }             }         }         if (SqlCommandType.UPDATE == sqlCommandType) {             Field[] fields = null;             if (parameter instanceof ParamMap) {                 ParamMap<?> p = (ParamMap<?>) parameter;                 if (p.containsKey("et")) {                     parameter = p.get("et");                 } else {                     parameter = p.get("param1");                 }                 if (parameter == null) {                     return invocation.proceed();                 }                  fields = getAllFields(parameter);             } else {                 fields = getAllFields(parameter);             }              for (Field field : fields) {                 log.info("------field.name------" + field.getName());                 try {                     if ("updateBy".equals(field.getName())) {                             field.setAccessible(true);                             field.set(parameter,"nzc-update");                             field.setAccessible(false);                     }                 } catch (Exception e) {                     e.printStackTrace();                 }             }         }         return invocation.proceed();     }      @Override     public Object plugin(Object target) {         return Plugin.wrap(target, this);     }      @Override     public void setProperties(Properties properties) {         // TODO Auto-generated method stub     } }

外面牵扯到的一些 Mybatis 中的一些对象,我没细说了,大家打印进去的时候都能够看到的。

测试:

执行的SQL语句打印信息

后果:

反射的个性,看似和咱们天天写业务没啥关系,然而它其实始终随同着咱们,这也是 Java 开发者的基础知识,根底不牢,地动山摇~

五、反射的优缺点

长处: 反射进步了程序的灵活性和扩展性,升高耦合性,进步自适应能力。 它容许程序创立和管制任何类的对象,无需提前硬编码指标类;对于任意一个类,都可能晓得这个类的所有属性和办法;对于任意一个对象,都可能调用它的任意一个办法

毛病 :让咱们在运行时有了剖析操作类的能力,这同样也减少了平安问题。比方能够忽视泛型参数的安全检查(泛型参数的安全检查产生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说理论是影响不大的。

反射是否真的会让你的程序性能升高吗?

1.反射大略比间接调用慢50~100倍,然而须要你在执行100万遍的时候才会有所感觉

2.判断一个函数的性能,你须要把这个函数执行100万遍甚至1000万遍

3.如果你只是偶然调用一下反射,请遗记反射带来的性能影响

4.如果你须要大量调用反射,请思考缓存。

5.你的编程的思维才是限度你程序性能的最次要的因素

小结

仔细阅读下来你会发现,

正如文中所说,所谓Class对象,也称为类模板对象,其实就是 Java 类在 JVM 内存中的一个快照,JVM 将从字节码文件中解析出的常量池、 类字段、类办法等信息存储到模板中,这样 JVM 在运行期便能通过类模板而获取 Java 类中的任意信息,可能对 Java 类的成员变量进行遍历,也能进行 Java 办法的调用,获取到类的任意信息。

反射也就是这样啦,不晓得你会应用啦吗,如果你还没有的话,我感觉能够再读上一遍,顺带本人验证一遍,心愿你能有所播种。

后记

其实想写这篇文章工夫曾经不短了,但偶然发生(我又常常偶然),所以总是一拖再拖,趁着这个周六,终于把它实现了,我也不晓得你会不会读到此处,看到我发的牢骚。

只是非常简单的心愿每一位阅读者可能有所播种,这应该是我继续写文的高兴吧~

明天又是好值的一天啊~

各位下次见!

写于 2022 年 9 月 17 日,作者:宁在春

本文参加了思否技术征文,欢送正在浏览的你也退出。