### 前言 反射和注解在平时的业务开发中极少会用到,这些技术都是属于学习框架或者开发框架时的底层源码,学习这些知识的目的是为大家今后 理解框架甚至自己开发框架做铺垫的。 #### 一、认识反射 反射就是:加载类,并允许以编程方式解剖类中的各种成分(成员变量、方法、构造器等), 指的就是把某个类的整个字节码文件加载到内存中,并允许以编程的方式解析出类中的各种成员,比如说把类中的成员变量给解析出来, 把类中的构造器解析出来,解析出来之后就可以对它们进行相应的操作了。 反射还学什么? 学习获取类的信息、操作它们: 1、反射第一步:加载类,获取类的字节码:Class对象【类对象】 2、获取类的构造器:Constructor对象【构造方法】 3、获取类的成员变量:Field对象【字段】 4、获取类的成员方法:Method对象【方法】 ![1](1.png) #### 二、使用反射 ##### 2.1 获取Class对象的三种方式 - Class c1=类名.class - 调用Class提供方法:public static Class ForName(String pageage); - Object提供的方法:public Class getClass(); Class c3=对象.getClass(); 代码示例: 先在cn.hxzy包下创建一个Student类 ~~~java package cn.hxzy; public class Student { private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } /** * 获取 * @return name */ public String getName() { return name; } /** * 设置 * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * @return age */ public int getAge() { return age; } /** * 设置 * @param age */ public void setAge(int age) { this.age = age; } public String toString() { return "demo{name = " + name + ", age = " + age + "}"; } public String study(){ return "我爱学习Java"; } public String play(String game){ return "我喜欢打"+game+"游戏"; } } ~~~ 然后创建Student类测试 ~~~java //获取class的三种方法 package cn.hxzy; public class demo { public static void main(String[] args) throws Exception{ //第一种方式获取Class对象 Class c1 = Student.class; System.out.println(c1.getName());//获得全类名:cn.hxzy.Student System.out.println(c1.getSimpleName());//获得简名:Student(类名) //第二种方式获取Class对象 Class c2=Class.forName("cn.hxzy.Student"); //第三种方式获取Class对象 Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。 Class c3 = stu1.getClass();//获取Class对象 } } ~~~ 因为字节码文件是一份,所以拿到的c1、c2、c3三个对象是同一个对象。 ##### 2.2 获取类中的构造器 Class 提供的从类中获取构造器的方法: ![2](2.png) 测试类: ~~~java package cn.hxzy; import org.apache.tomcat.util.bcel.Const; import java.lang.reflect.Constructor; public class DemoTest { public static void main(String[] args) { //1.反射第一步先获取类的class对象 Class c=Student.class; //2.获取类的全部构造器、创建一个构造器类型的数组来接受类中的所有构造器 Constructor[] constructors=c.getConstructors(); //3.遍历数组中的每一个构造器、获取构造器的名字和构造器的参数个数 for (Constructor constructor : constructors) { System.out.println(constructor.getName()+"="+constructor.getParameterCount()); } } } ~~~ 上面的代码可以得到无参构造和有参构造的名字和参数个数。 注意:getConstructors()只能拿到public修饰的构造器,建议使用getDeclaredConstructors(); ~~~java package cn.hxzy; import org.apache.tomcat.util.bcel.Const; import java.lang.reflect.Constructor; public class DemoTest { public static void main(String[] args) { //1.反射第一步先获取类的class对象 Class c=Student.class; //2.获取类的全部构造器、创建一个构造器类型的数组来接受类中的所有构造器 Constructor[] constructors=c.getDeclaredConstructors(); //3.遍历数组中的每一个构造器、获取构造器的名字和构造器的参数个数 for (Constructor constructor : constructors) { System.out.println(constructor.getName()+"="+constructor.getParameterCount()); } } } ~~~ getConstructor(参数类型)获取单个构造器,需要写参数类型来定位构造器 ~~~java package cn.hxzy; import org.apache.tomcat.util.bcel.Const; import java.lang.reflect.Constructor; public class DemoTest { public static void main(String[] args) { //1.反射第一步先获取类的class对象 Class c=Student.class; //2.获取无参构造器 Constructor constructor=c.getConstructor(); //3.获取构造器的名字和构造器的参数个数 System.out.println(constructor.getName()+"="+constructor.getParameterCount()); } } ~~~ 注意:getConstructor()只能拿到public修饰的构造器,建议使用getDeclaredConstructor(); ~~~java package cn.hxzy; import org.apache.tomcat.util.bcel.Const; import java.lang.reflect.Constructor; public class DemoTest { public static void main(String[] args) { //1.反射第一步先获取类的class对象 Class c=Student.class; //2.获取无参构造器 Constructor constructor=c.getDeclaredConstructor(); System.out.println(constructor.getName()+"="+constructor.getParameterCount()); //3.获取有参数构造器 Constructor constructor2=c.getDeclaredConstructor(String.class,int.class) System.out.println(constructor2.getName()+"="+constructor2.getParameterCount()); } } ~~~ **暴力反射** 获取类构造器的作用:依然是初始化对象返回。 ![3](3.png) ~~~java //得到class对象 Class c=Student.class; //获取无参构造器 Constructor constructor = c.getDeclaredConstructor(); //如果构造器私有化,进行暴力反射 constructor.setAccessible(true); //禁止检查访问权限,可以破环封装性 //调用此构造器 Student s=(Student)constructor.newInstance(); System.out.println(s); ~~~ ##### 2.3 获取类的成员变量 Class提供了从类中获取成员变量的方法。 ![4](4.png) ~~~java //1、反射第一步:先得到类的Class对象 Class c=Student.class; //2、获取类的全部成员变量 Field[] fields=c.getDeclaredFields(); //3、遍历成员变量数组 for(Field field:fields){ System.out.println(field.getName()+"="+field.getType()); } //4、获取特定的成员变量 Field fname=c.getDeclaredField("name"); System.out.println(fname.getName()+"="+fname.getType()); ~~~ 获取成员变量的作用依然是赋值,取值 ![6](6.png) ~~~java //赋值 Student student=new Student(); fname.setAccessible(true); fname.set(student,"小王"); System.out.print(student); //取值 String name=(String)fname.get(student); System.out.print(name); ~~~ 获取成员变量访问修饰符: ~~~java Field field = c.getDeclaredField("address"); int modifiers = field.getModifiers(); System.out.println(modifiers); String str = Modifier.toString(modifiers); ~~~ ##### 2.4 获取类的成员方法 Class提供了从类中获取成员方法的API ![5](5.png) ~~~java //1、反射第一步:先得到类的Class对象 Class c=Student.class; //2、获取类的全部成员方法 Method [] methods=c.getDeclareMethods(); //3、遍历这个数组中的每个方法对象 for(Method method : methods){ System.out.print(method.getName()+"=="+method.getParameterCount()+"="+method.getReturnType());//获取参数个数与返回结果数据类型 } //4、获取某个特定的方法对象 Method m1=c.getDeclaredMethod("play", String.class); System.out.print(m1.getName()+"=="+m1.getParameterCount()+"="+m1.getReturnType()); ~~~ 成员方法的作用依然是执行 ![7](7.png) ~~~java //5、执行方法对象 Student student=new Student(); m1.setAccessible(true); String gameName=m1.invoke(student,"王者荣耀"); System.out.print(gameName); ~~~ #### 三、反射的作用 (1)基本作用:得到一个类的全部成分并操作(IDEA获取成员方法,变量) (2)破坏封装性 (3)最重要是:适合做Java的框架,基本上主流框架都会基于反射设计出一些通用的功能 案例:使用反射做一个简易版的框架 需求:对于任意一个对象,该框架都可以把对象的字段名和对应的值,保存到文件中去。 实现步骤: 1、定义一个方法,可以用来接收任意对象 2、每收到一个对象之后,使用反射获取该对象的 Class对像,获取全部成员变量 3、得到这个类的全部成员变量,然后提取成员变量在该对象中的具体 4、把成员变量名和值写出文件中去 定义几个类,具体步骤省略 然后定义一个框架类 实体类 ~~~java package cn.hxzy.entity;/** @author:mengshujun @createTime: 2024-03-21 15:02:40 星期四 */ public class Goods { private String goodId; private String goodName; private double price; private int count; private String goodType; public Goods() { } public Goods(String goodId, String goodName, double price, int count, String goodType) { this.goodId = goodId; this.goodName = goodName; this.price = price; this.count = count; this.goodType = goodType; } public String getGoodId() { return goodId; } public void setGoodId(String goodId) { this.goodId = goodId; } public String getGoodName() { return goodName; } public void setGoodName(String goodName) { this.goodName = goodName; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } public String getGoodType() { return goodType; } public void setGoodType(String goodType) { this.goodType = goodType; } @Override public String toString() { return "Goods{" + "goodId='" + goodId + '\'' + ", goodName='" + goodName + '\'' + ", price=" + price + ", count=" + count + ", goodType='" + goodType + '\'' + '}'; } } ~~~ 框架实现类: ~~~java public class ObjectFrame{ // 目标:保存任意对象的字段和其数据到文件中去 public static void saveObject(Object obj){ FileOutputStream fos=new FileOutputStream("d:/obj.txt"); PrintStream ps=new PrintStream(fos); //1、obj是任意对象,到底有多少个字段要保存,使用反射 Class c=obj.getClass(); String cname= c.getSimpleName(); ps.pritnln("=========="+cname+"============="); //2、从这个类中提取它的全部成员变量 Field [] fields= c.getDeclaredFields(); //3、遍历每个成员变量 for(Field field : fields){ //4、拿到成员变量的名字 String name=field.getName(); //5、拿到这个成员变量在对象中的数据 field.setAccessible(true); //禁止检查访问控制 String value=(String) field.get(obj); ps.println(name+"="+value); } ps.close(); fos.close(); } } ~~~ 测试类 ~~~java package cn.hxzy.mycase; import cn.hxzy.entity.Goods; import cn.hxzy.entity.Student; import java.io.IOException; /** @author:mengshujun @createTime: 2024-03-21 15:05:03 星期四 */ public class CaseDemo { public static void main(String[] args) { try { //Goods goods = new Goods("1001","iphone15pro max",8900.00,10,"手机"); Student student=new Student("张三丰",156,10000); //ObjectFrame.saveObject(goods); ObjectFrame.saveObject(student); } catch (IOException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } ~~~ **使用反射的原因** Java中使用反射的原因包括提高程序的灵活性、屏蔽实现细节、实现动态加载类、动态创建对象、访问私有成员、扩展性和灵活性、类型分析和调试。 提高程序的灵活性。反射允许在运行时确定和加载类,而不是在编译时,这使得Java能够像动态语言那样在运行时改变程序结构和变量类型。 屏蔽实现细节。反射允许开发者在不需要知道具体类的情况下操作对象,从而提供了更大的灵活性和便捷性。 实现动态加载类。反射允许在运行时动态加载类,而不需要在编译时将类引入到代码中。 动态创建对象。反射允许在运行时动态创建对象的实例,而不需要在编译时确定对象的类型。 访问私有成员。反射可以绕过访问权限限制,获取和修改私有成员变量,以及调用私有方法。 扩展性和灵活性。反射使得程序更加灵活和可扩展,可以在运行时动态地获取和操作类的信息。 类型分析和调试。反射可以用来分析类的结构,获取类的属性、方法等信息,以及在调试过程中动态获取和修改类的信息。 在框架中使用。在构建大型应用程序和框架时,反射是实现通用性和灵活性的关键工具, 例如,Spring框架就大量使用了反射机制。 #### 四、注解 ##### 4.1 注解的概念 Annotation表示注解,是JDK1.5的新特性 注解的主要作用:对我们的程序进行标注,通过注解可以给类增加额外的信息,注解是给编译器或JVM看的,编译器或JVM可以根据注解 来完成对应的功能,让其他程序根据注解信息来决定怎么执行该程序。 Java中已经存在的注解: ① @Override:表示方法的重写 ② @Deprecated:表示修饰的方法已过时 ③ @SuppressWarnings("all"):压制警告 ,镇压警告 除此之外,还需要掌握第三方框架中提供的注解,后续框架知识会学习很多注解。 ##### 4.2 自定义注解 自定义注解单独存在是没有什么意义的,一般会跟反射结合起来使用,会用反射去解析注解。针对于注解,只要掌握别人已经写好的注解 即可。关于注解的解析,一般是在框架的底层已经写好了 自定义注解就是自己做一个注解来使用 ~~~java public @interface 注解名称 { public 属性类型 属性名() default 默认值; } ~~~ 这里的属性类型只可以是:基本数据类型、String、Class、注解、枚举以及以上类型的一维数组 使用: ~~~java @注解名(属性名1 = 值1, 属性名2 = 值2) ~~~ 注意: ① 使用自定义注解时要保证注解每个属性都有值 ② 注解可以使用默认值 示例: ~~~java public @interface MyAnno { public String name(); public int age(); } ~~~ **特殊属性:** ① value属性,如果只有一个value属性,使用value属性的时候可以省略value名称不写 ② 但是如果有多个属性,且多个属性没有默认值,那么value名称是不能省略的 ##### 4.3 元注解 元注解就是注解的注解,也就是写在注解上面的注解 元注解常用的有两个: ① @Target:约束自定义注解只能在哪些地方使用 ② @Retention:申明注解的生命周期 @Target中可使用的值定义在ElementType中,常用值如下: ~~~ ① Type:类、接口 ② FIELD:成员变量 ③ METHOD:成员方法 ④ PARAMETER:方法参数 ⑤ CONSTRUCTOR:构造器 ⑥ LOCAL_VARIABLE:局部变量 ~~~ @Retention中可使用的值定义在RetentionPolicy枚举类中,常用值如下: ~~~ ① SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在 ② CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值 ③ RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用) ~~~ 示例: ~~~java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnno { public String age(); public String name() default "2"; } ~~~ ##### 4.4 注解的解析 注解的操作中经常需要进行解析,注解的解析就是判断类上,方法上、成员变量上是否存在注解,并把注解里面的内容给解析出来 如何解析注解,主要就是要解析“谁”上面的注解,就应该拿到“谁”。 比如说:要解析类上的注解,则应该先获取该类的Class对象,再通过Class对象解析其上面的注解。 再比如说:要解析成员方法上的注解,则应该先获取到该成员方法的Method对象,再通过Method对象解析其上面的注解。 所有的类成分Class,Method,Field,Constructor,都实现了AnnotatedElement接口,他们都拥有解析注解的能力 **与注解解析相关的接口:** Annotation:注解的顶级接口 可以利用反射解析注解: <table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td><code>Annotation[] getDeclaredAnnotations()</code></td><td>获得当前对象上使用的所有注解,返回注解数组</td></tr><tr><td><code>T getDeclaredAnnotations(Class<T> annotationClass)</code></td><td>根据注解类型获得对应注解对象</td></tr><tr><td><code>boolean isAnnotationPresent(Class<Annotation> annotationClass)</code></td><td>判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false</td></tr></tbody></table> **案例:** 限制注解使用的位置:类和成员方法上 指定注解的有效范围:一直到运行时 ~~~java package cn.hxzy.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAno { String value(); String name(); int b() default 10; String [] arr(); } ~~~ 定义类 ~~~java package cn.hxzy.annotation; import java.lang.annotation.Annotation; /** * @author:mengshujun * @createTime: 2024-03-21 15:49:26 星期四 */ @MyAno(value = "qq", name = "腾讯", b = 15,arr = {"x","y"}) public class MyClass { @MyAno(value = "微信", name = "腾讯", b = 25,arr={"java","spring"}) public void select(int num) { } } ~~~ 测试类 ~~~java Class c = MyClass.class; if(c.isAnnotationPresent(MyAno.class)){ //父类 父类对象=new 子类(); MyAno myAno = (MyAno) c.getDeclaredAnnotation(MyAno.class); System.out.println(myAno.b()); System.out.println(myAno.name()); System.out.println(myAno.value()); System.out.println(Arrays.toString(myAno.arr())); } ~~~ 解析注解的技巧: ① 注解在哪个成分上,我们就先拿哪个成分对象 ② 比如注解作用成员方法,则要获得该成员方法对应的Method对象,再来拿上面的注解 ③ 比如注解作用在类上,则要获得该类的Class对象,再来拿上面的注解 ④ 比如注解作用在成员变量上,则要获得该成员变量对应的Field对象,再来拿上面的注解 ##### 4.5 注解的使用场景 案例:模拟Junit框架 需求:定义若干个方法,只要加了MyTest注解,就可以在启动时被触发执行 分析: ① 定义一个自定义注解MyTest,只能注解方法,存活范围是一直都在 ② 定义若干个方法,只要有@MyTest注解的方法就能在启动时被触发执行,没有这个注解的方法不能执行 代码: MyTest注解: ~~~java //表示着我们的注解可以写在方法上面,其他地方不能写 @Target(ElementType.METHOD) //表示我们的注解可以在任意时期都存在 //如果写source,那么只能在源码阶段存在,利用反射无法解析,因为反射是通过字节码文件执行的 @Retention(RetentionPolicy.RUNTIME) public @interface MyTest { } ~~~ 使用该注解的类(测试类): ~~~java public class MyTestDemo { @MyTest public void method1() { System.out.println("method1"); } @MyTest public void method2() { System.out.println("method2"); } public void method3() { System.out.println("method3"); } } ~~~ 类似Junit底层的实现: ~~~java public class MyAnnoDemo { public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException { //1.获取main这个类的字节码文件对象 Class<?> clazz = Class.forName("p1.MyTestDemo"); //对象 main mtd = new MyTestDemo(); //2.获取所有方法 Method[] methods = clazz.getDeclaredMethods(); //3.遍历得到每一个方法 for (Method method : methods) { //4.临时修改权限 method.setAccessible(true); //5.判断当前方法上面有没有MyTest注解 if(method.isAnnotationPresent(MyTest.class)) { method.invoke(mtd); } } } } ~~~
03-02
2514
03-14
423
“相关推荐”对你有帮助么?
-
非常没帮助
-
没帮助
-
一般
-
有帮助
-
非常有帮助
提交