当前位置: 首页 > news >正文

Java反射与注解

反射Reflect

常规我们使用类的方法都是在代码中创建指定类型的实例如Apple apple = new Apple(),这种写法要求我们在程序执行之初就确定好要使用的类和具体方法,扩展性和灵活性不足。
反射机制(Reflect)则是一种程序执行时冬天获取类和执行方法的能力,该方法突破了Java编译时确定类型的限制,为动态编程提供了底层支持,很多主流框架如Spring的底层都使用反射实现。

首先通过一个例子直观感受下正向和反射的区别:

public class Reflect {public static void main(String[] args) throws Exception{Person p = new Person("Kevin",19);System.out.println(p.toString());// 反向构造,获取类Class c = Class.forName("Reflect.Person");// 创建实例Object pe = c.newInstance();// 获取方法Method sout = c.getMethod("toString");System.out.println(sout.invoke(pe));}
}

下面详细介绍反射的使用方法。

创建class对象

JVM会为每一个类型生成一个元数据对象class,该对象存储了类型的完整信息,成员、方法等,反射机制通过该元数据对象实现对类的操作,进而实现动态编程,因为该过程需要借助class进行,好像一面镜子,所以该操作称为反射。

创建class对象有三种方法:

  1. Class.forName()全限定类名。示例为Class c=Class.forName("Reflect.Person");,全限定类名表示包含包位置的类路径,类似相对位置,但省略表示路径的符号和文件类型标记。
  2. 实例的getclass方法。示例Class p = new Person().getClass();使用实例对象的getClass方法获取元数据对象。
  3. 类的class属性。实例Class p1=Person.class;,使用类的class属性赋值。

获得构造信息及调用

// 获得构造函数,默认无参
Constructor c1=c.getConstructor();
// 针对重载函数,限定参数为指定类型的class
Constructor c2=c.getConstructor(String.class,int.class);
// 使用构造函数创建实例,默认无参构造
Object a=c2.newInstance("kevin",25);

获取类的属性及赋值

// 获取公开属性
Field name=c.getField("name");
// 获取私有属性
Field age=c.getDeclaredField("age");
// 获取属性列表
Field[] list=c.getFields();
Field[] list=c.getDeclaredFields()
// 打破私有封装
age.setAccessible(true);
// 修改实例的指定属性
age.set(a,26)

获取方法信息及调用

// 获取方法
Method getName=c.getMethod("getName");
// 执行方法
getName.invoke(对象,参数)
// 重载方法的获取,方法名,参数类型.class
Method setName=c.getMethod("setName",String.class);

在获取方法的getMethod源码我们看到是这样定义的public Method getMethod(String name, Class<?>... parameterTypes),所以这里补充一个知识,不定长参数
不定长参数可以表示为类型...参数名,这种定义方法表示输入该类型的参数有任意个都可以,简单示例如下:

    public static void out(String...para){for(int i=0;i<para.length;i++){System.out.println(para[i]);}}public static void main(String[] args) throws Exception{// 参数传多少个字符串都可以out("hello","world");}

可变长参数本质是基于列表实现的,该参数没有强制性,执行时可传可不传,但要放在参数列表的最后。

其他常用方法

作用用法
类加载器Classloader cl=class.getClassLoader()
获取类名getSimpleName()简类名,getName()获取全类名
获取修饰符getModifiers()获取修饰编号,Modifier.toString()转为修饰字符串
获取数据类型getReturnType()方法返回值类型、getParameterTypes()获得方法参数类型,getType()属性类型
获取父接口getInterfaces()
获取父类getSuperclass()

注解Annotation

注解是一种描述程序元素(类、属性、方法等)的机制,注解本身不影响程序运行,但程序可以获取注解以进行更改运行模式等操作,简单来说就是程序可读的注释

注解的使用方法为@注解名,我们在多线程部分重写run方法时,方法上的@override就是重写注解。

系统注解

编码过程中常用的注解有:
interface定义注解;
Override重写注解,有该注解标识的方法编译器会检查是否真的覆写了父类方法;
Deprecated不建议使用的方法,该注解标识的方法在调用时会有删除线显示,显示效果如下:
Deprecated不推荐使用的方法效果

自定义注解

注解是Java的基本文件之一,通过创建类给出的文件类型就能看出:
创建注解
自定义注解的方法是使用@interface关键字,直接给出示例:

public @interface MyAnnotation {// 注解内的成员变量需要(),可用default 设置默认值,否则需在添加注解时显式指定String value() default "nobody";
}
// 注解修饰Person类,当注解成员为value时可省
@MyAnnotation("person1")
public class Person {private String name;private int age;public static void main(String[] args) {Class clazz = Person.class;Annotation annotation=clazz.getAnnotation(MyAnnotation.class);System.out.println(annotation.toString());}
}
// 输出为@Annotation.MyAnnotation(value=person1)

元注解

即修饰注解的注解,元注解限制了注解能修饰哪些类型的元素,类、成员或者方法,同时规定了注解的作用域。

Target注解,表明注解的作用域。参数为ElementType,多个作用域需用{}包围,其关键字表示不同作用域,分别为:

ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
ElementType.CONSTRUCTOR 可以给构造方法进行注解
ElementType.FIELD 可以给属性进行注解
ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
ElementType.METHOD 可以给方法进行注解
ElementType.PACKAGE 可以给一个包进行注解
ElementType.PARAMETER 可以给一个方法内的参数进行注解
ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举

Retention注解,表明注解的声明周期。参数为RetentionPolicy,关键字表示的不同生命周期为:

RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃。
RetentionPolicy.CLASS 注解只被保留到编译进行的时候,即保存到class字节码文件中,它并不会被加载到 JVM 中。
RetentionPolicy.RUNTIME 注解可以保留到程序运行,它会被加载进入到 JVM 中,所以在程序运行时可以通过反射获取。

案例—反射获取添加Controller注解的私有成员

通过上面的学习我们了解到,注解应该是与反射共同作用,注解可作为程序元素的进一步补充说明,在反射过程中被获取执行,我们尝试结合反射与注解编写一个案例,首先自定义Controller注解,用于修饰指定的类,反射读取类的过程中检查是否有该注解修饰,有则获取其私有成员id

背景为有PersonAnimal两个类,Controller注解有一成员value默认为nobody,修饰两个类时该成员会变为personanimal,要求利用反射机制,找到Controller注解修饰后成员为person的类的有私有成员id的类,找到后给出提示。
实现的大致思路为:

  1. 读取配置文件,获取要识别类的信息,使用Properties和流对象FileReader实现。
  2. 将读取到的类信息按;分割成字符串数组,for循环分别判定。
  3. 根据类信息反射读取class对象,并获得注解信息。
  4. 注解内循环寻找类型为Controller的注解,找到后判定值是否为person
  5. 获取所有成员变量,循环检测权限修饰符是否有private,成员名称是否为id
  6. 找到符合条件的输出提示信息。

Animal类雷同,实现代码如下:

@Controller("person")
public class Person {private String name;private int age;private int id;public Person(String name, int age, int id) {this.name = name;this.age = age;this.id = id;}public String getName() {return name;}public int getAge() {return age;}public int getId() {return id;}public static void main(String[] args) {try {FileReader fr =new FileReader("config.properties");Properties prop = new Properties();prop.load(fr);String name = prop.getProperty("classname");String[] classNameArray = name.split(";");for(String className : classNameArray) {Class aclass=Class.forName(className);Annotation[] annotations = aclass.getAnnotations();for(Annotation annotation : annotations) {if(annotation instanceof Controller) {Controller myAnnotation = (Controller) annotation;if(myAnnotation.value().equals("person")) {Field[] declaredField=aclass.getDeclaredFields();for(Field field : declaredField) {if(field.getName().equals("id") && Modifier.toString(field.getModifiers()).contains("private")) {System.out.println("发现目标,当前类为:"+aclass.getTypeName());}}}}}}} catch (FileNotFoundException e) {throw new RuntimeException(e);} catch (IOException e) {throw new RuntimeException(e);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}
}

总结

本文介绍了Java动态操作类的方法反射,以及给类添加信息的注解,反射提供了不同于传统正向先定义再使用类的方法,允许开发者逆向构建实例,并根据注解选择要执行的操作,其本质仍然是基于提高扩展性的设计

提高扩展性比较简单的方法是使用多态,二者之间有什么区别和联系呢?

首先回顾多态。允许父类接受子类实例,这也允许我们无需精准定义类型即可允许程序,只是不能调用子类方法,我们通过instanceof判断类型并向下转型即可解决,好像也很方便,但多态的扩展性是有前提的——继承链

多态的类型宽松仅针对已知继承链内的类,这要求开发者在开发时就设计好所有类的继承关系,并体现在代码中,反射机制可以实现真正突破编译限制,允许在​​运行时动态操作任意类的元数据,直接调用子类方法而无需转型,甚至直接操作私有成员,是更自由的操作手段。

反射在提供自由的同时也带来了安全风险:

  1. 打破封装。封装性是面向对象三大核心特征之一,反射操作私有成员的能力直接打破了这一特性,私有成员的更改可能导致类的内部问题。
  2. 代码注入。恶意代码可能通过Class.forName("恶意类名")加载和执行其他类,绕过编译检查。
  3. 性能开销。反射结构需要动态解析类的结构,带来效率问题,性能敏感场景可能不符合要求。

所以总得来说,反射还是应该少用,不得已要用的地方也要做好校验。

http://www.dtcms.com/a/276117.html

相关文章:

  • 树形动态规划详解
  • 大数据时代UI前端的智能化服务升级:基于用户情境的主动服务设计
  • 【PycharmPyqt designer桌面程序设计】
  • 【学习新知识】用 Clang 提取函数体 + 构建代码知识库 + AI 问答系统
  • GD32 CAN1和TIMER0同时开启问题
  • 《通信原理》学习笔记——第一章
  • 细谈kotlin中缀表达式
  • H2在springboot的单元测试中的应用
  • skywalking镜像应用springboot的例子
  • try-catch-finally可能输出的答案?
  • Docker-镜像构建原因
  • C语言基础教程--从入门到精通
  • Spring Boot整合MyBatis+MySQL+Redis单表CRUD教程
  • STM32中的RTC(实时时钟)详解
  • R 语言绘制 10 种精美火山图:转录组差异基因可视化
  • JavaScript 常见10种设计模式
  • 码头智能哨兵:AI入侵检测系统如何终结废钢盗窃困局
  • Redis专题总结
  • MyBatis实现一对多,多对一,多对多查询
  • Golang操作MySQL json字段优雅写法
  • CPU缓存一致性协议:深入解析MESI协议与多核并发设计
  • HTML/JOSN复习总结
  • 7. JVM类加载器与双亲委派模型
  • PyQt5 — QTimeEdit 学习笔记
  • Java中的wait和notify、Condition接口的使用
  • 分类问题与多层感知机
  • pip国内镜像源一览
  • [es自动化更新] Updatecli编排配置.yaml | dockerfilePath值文件.yml
  • springboot+swagger2文档从swagger-bootstrap-ui更换为knife4j及文档接口参数不显示问题
  • 【高等数学】第三章 微分中值定理与导数的应用——第七节 曲率