java里面反射和动态代理的基础知识

反射

在Java中,反射(Reflection)是一种强大的工具,它允许程序在运行时检查和修改类、接口、字段和方法等元数据的行为。通过反射,你可以加载类、实例化对象、调用方法、获取和修改字段的值等,而无需在编译时知道这些类的详细信息。

如何使用反射

使用反射的基本步骤通常包括:

  1. 获取Class对象:通过类的.class属性、对象的getClass()方法或Class.forName(String className)方法获取Class对象。
Class<?> clazz = MyClass.class; // 通过.class属性
Class<?> clazz2 = obj.getClass(); // 通过对象的getClass()方法
Class<?> clazz3 = Class.forName("com.example.MyClass"); // 通过Class.forName方法
  1. 使用Class对象
  • 创建实例:使用newInstance()方法(如果类有一个无参构造方法)或getDeclaredConstructor()Constructor.newInstance()方法(用于带参数的构造方法)。
  • 获取方法:使用getMethod()getDeclaredMethod()方法获取Method对象,然后使用Method.invoke()方法调用该方法。
  • 获取字段:使用getField()getDeclaredField()方法获取Field对象,然后使用Field.get()Field.set()方法获取和设置字段的值。

注意事项

  1. 性能:反射通常比直接方法调用要慢得多,因为它涉及更多的检查和类型转换。因此,在性能敏感的代码段中应谨慎使用反射。
  2. 安全性:反射允许你访问和修改类的私有成员,这可能会破坏封装性并导致安全问题。确保你了解你正在访问的类的内部实现,并仅在必要时使用反射。
  3. 异常处理:反射API中的许多方法都会抛出异常,如NoSuchMethodExceptionIllegalAccessExceptionInvocationTargetException等。务必正确处理这些异常,以避免程序崩溃。
  4. 可移植性:虽然Java的反射API在所有支持Java的平台上都是相同的,但不同的Java虚拟机(JVM)可能对反射的实现有不同的限制或优化。确保你的代码在目标JVM上按预期工作。
  5. 代码清晰度:过度使用反射可能会使代码难以理解和维护。尽量只在必要时使用反射,并考虑使用其他设计模式或工具来简化代码。
  6. 访问限制:默认情况下,你不能通过反射访问或修改Java的核心类(如StringInteger等)的私有成员。这是因为这些类被标记为“final”或具有其他访问限制。尝试这样做会导致SecurityException或其他异常。
  7. 序列化:如果你正在使用反射来序列化和反序列化对象,请确保你了解Java的序列化机制并遵循相关的最佳实践。否则,你可能会遇到安全问题、性能问题或版本不兼容问题。

反射的作用

在Java中,反射(Reflection)是一种强大的工具,它允许程序在运行时进行自我检查(introspection)和修改(modification)。通过反射,Java代码能够获取类的内部信息(如类的成员变量、方法、构造器等),并能够在运行时动态地调用这些方法或访问这些成员变量。以下是反射的一些主要用途:

  1. 动态加载和实例化类
  • 使用Class.forName()方法,可以在运行时动态地加载类,并通过newInstance()方法(或getDeclaredConstructor().newInstance())实例化该类。这在插件式架构、框架或需要动态加载模块的场景中非常有用。
  1. 动态调用方法
  • 通过反射,可以在运行时动态地调用类的方法。这通常通过Method.invoke()方法实现,它允许你传递参数并接收返回值。这在实现动态代理、AOP(面向切面编程)或脚本化应用中非常有用。
  1. 访问和修改私有成员
  • 虽然通常不推荐直接访问或修改类的私有成员,但在某些特殊情况下(如框架、测试工具等),反射可以允许你这样做。通过getDeclaredField()方法获取字段,然后使用setAccessible(true)绕过Java的访问控制。
  1. 类库和其他API的扩展
  • 通过反射,你可以编写与类库或其他API交互的代码,而无需修改这些库或API的源代码。这允许你以更加灵活和可维护的方式扩展这些库的功能。
  1. 动态代理
  • 反射是Java动态代理机制的核心。通过实现InvocationHandler接口并创建Proxy类的实例,你可以创建在运行时动态实现的接口的实现类。这在实现AOP、远程方法调用(RMI)或日志记录等功能时非常有用。
  1. 调试和测试
  • 在开发和测试阶段,反射可以用于检查类的内部状态和行为。这有助于验证类的正确性、发现潜在的问题或进行性能分析。
  1. 框架和库的实现
  • 许多Java框架和库(如Spring、Hibernate、JUnit等)都使用了反射来提供其高级功能。例如,Spring框架使用反射来自动装配bean、创建代理对象或实现依赖注入。

虽然反射功能强大且灵活,但它也有一些缺点和限制:

  • 性能开销:反射操作通常比直接方法调用或字段访问要慢得多,因为它们涉及更多的底层操作和类型检查。
  • 安全性问题:使用反射可以绕过Java的访问控制机制,这可能导致潜在的安全问题。例如,恶意代码可能利用反射来访问或修改不应该被访问的类成员。
  • 代码可读性和可维护性:过度使用反射可能导致代码难以理解和维护。在可能的情况下,最好使用直接的方法调用和字段访问来保持代码的清晰和简洁。

获取class对象的三种方式

  • Class.forName(“全类名”) 在源代码阶段使用

  • 类名.class 在加载阶段使用

  • 对象.getClass() 在运行阶段使用

例子代码1

package com.mohuanan.test;import java.lang.reflect.Field;public class Test02 {public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {//使用反射去获取类里面的成员变量Class clazz = Class.forName("com.mohuanan.test.Student");//1. 获取Student类里面的所有成员变量(包括私有)/*Field[] declaredFields = clazz.getDeclaredFields();for (Field f : declaredFields) {System.out.println(f);}*///获取指定的成员变量(不包括私有)/*Field f = clazz.getField("gender");System.out.println(f);*///获取指定的一个成员变量(包括私有)Field name = clazz.getDeclaredField("name");System.out.println(name);//2. 拿到成员变量后,进行解析//获取其的权限修饰符int modifiers = name.getModifiers();System.out.println(modifiers);//获取其的类型Class<?> type = name.getType();System.out.println(type);///获取其的名字String str = name.getName();System.out.println(str);//获取其里面记录的值Student s = new Student("莫华南", 18, "男");//因为其是私有的,所以要临时把他的权限修饰符,扩大name.setAccessible(true);String o = (String) name.get(s);System.out.println(o);//修改里面记录的值name.set(s, "莫华棋");System.out.println(s);}
}

例子代码2(保存任意数据)

package com.mohuanan.exercise;import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.reflect.Field;public class Demo01 {public static void main(String[] args) throws IOException, IllegalAccessException {//分别创建teacher和student的对象Teacher t = new Teacher("花花老师",25);Student s = new Student("小明","男",18,"202309140225");writeInfo(s);writeInfo(t);}/** 作用:*       将对象的信息写到文件里面* 参数一:*       要写到文件里面的那个对象** */public static void writeInfo(Object o) throws IllegalAccessException, IOException {//获取对象的Class对象Class clazz = o.getClass();BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt",true));//获取Class对象里面所有的成员变量(包括私有)Field[] declaredFields = clazz.getDeclaredFields();for (Field f : declaredFields) {//将权限修饰符扩大 以便获取私有的成员变量f.setAccessible(true);//获取名字String name = f.getName();//获取里面的值Object value = f.get(o);//将信息写到文件里面bw.write(name+"="+value);bw.newLine();}//释放资源bw.close();}}

动态代理

在Java中,动态代理是一种代理模式的实现方式,它允许在运行时动态地创建代理类并动态地处理方法调用。以下是关于Java动态代理的详细回答:

1. 定义

动态代理是Java提供的一种机制,通过该机制可以在运行时为任意实现了接口的类动态生成一个代理类,并在这个代理类中实现一些额外的功能,如日志记录、事务控制等。

2. 用处

动态代理的主要用处包括:

  • 解耦和:在访问原始对象时增加额外功能,如访问前或访问后添加一些额外的行为。
  • 控制访问:控制对原始对象的访问,当客户端调用代理对象的方法时,代理对象可以将请求转发到实际对象,并在必要时添加额外的功能。
  • 应用在各种框架中:如AOP(面向切面编程)、过滤器、拦截器等。在Spring框架中,AOP的实现就大量使用了动态代理。

3. 如何使用

使用Java动态代理主要需要以下步骤:

  1. 定义接口:首先,需要定义一个或多个接口,这些接口将被原始类和代理类共同实现。
  2. 实现原始类:创建一个类来实现上述接口,这个类将包含业务逻辑。
  3. 创建InvocationHandler:创建一个实现了InvocationHandler接口的类,这个类将负责在代理对象上调用方法时执行额外的操作。在invoke方法中,可以调用原始对象的方法,并在调用前后添加额外的逻辑。
  4. 创建代理对象:使用Proxy.newProxyInstance方法创建代理对象。这个方法需要三个参数:类加载器、原始对象实现的接口数组和InvocationHandler实例。
  5. 调用方法:通过代理对象调用方法,这些方法将被转发到InvocationHandlerinvoke方法。

4. 注意事项

在使用Java动态代理时,需要注意以下几点:

  • 原始对象必须实现接口:由于Java的动态代理是基于接口的,因此原始对象必须实现一个或多个接口。如果原始类没有实现任何接口,则无法使用动态代理。
  • 类型安全:由于代理对象是Proxy类的子类,并且实现了原始对象实现的所有接口,因此在使用代理对象时,需要将其强制转换为正确的接口类型。
  • 性能考虑:虽然动态代理在运行时创建代理类,但这个过程通常比静态代理更快,因为不需要手动编写和编译代理类。然而,如果频繁地创建和销毁代理对象,可能会对性能产生影响。
  • 异常处理:在InvocationHandlerinvoke方法中,需要处理可能抛出的异常。如果原始对象的方法抛出了异常,并且没有在invoke方法中适当地处理,那么这个异常将被传递到调用代理对象的方法的客户端。
  • 线程安全:如果多个线程同时访问同一个代理对象,并且InvocationHandler的状态不是线程安全的,那么可能会出现并发问题。在这种情况下,需要确保InvocationHandler的实现是线程安全的。

例子代码

package com.mohuanan.test02;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** 类的作用:*      创建一个代理**/public class ProxyUtil {/** 方法的作用:*           给一个明星对象创建一个代理* 形参:*       被代理的明星对象* 返回值:*       给明星代理的对象** */public static Star createProxy(BigStar bigStar){Star star = (Star) Proxy.newProxyInstance(//指定用那个类加载器,去加载生成的代理类ProxyUtil.class.getClassLoader(),//指定接口,这些接口用于指定生成的代理长什么样子,也就是有什么方法new Class[]{Star.class},//用来指定生成的代理去干什么事情new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//参数一: 代理的对象//参数二: 要运行的方法//参数三: 传递的实参if("sing".equals(method.getName())){System.out.println("准备话筒,收钱");}else if("dance".equals(method.getName())){System.out.println("准备场地,收钱");}//method: 就是那个方法//invoke: 就是去执行这个方法//参数一: 调用者,即对象(使用BigStar的对象去调用方法)//参数二: 方法的形参return method.invoke(bigStar,args);}});return star;}
}

代码解释

Star是一个接口,但是Star star声明了一个类型为Star的变量star。由于 star是一个接口类型的变量,它不能直接指向一个具体的对象实例(因为接口不能被实例化),但它可以指向一个实现了Star` 接口的对象。

这里,Proxy.newProxyInstance 方法返回一个实现了 Star 接口的代理对象。这个代理对象是在运行时动态生成的,并且它重写了 Star 接口中定义的所有方法。当你调用这个代理对象上的任何 Star 接口方法时,实际上会调用你在 InvocationHandlerinvoke 方法中定义的逻辑。

我们来详细解释一下为什么返回 star

  1. 动态代理的目的:动态代理的主要目的是在不修改现有类代码的情况下,增强类的功能。在这个例子中,你可能有一个实现了 Star 接口的类 BigStar,但是你想在调用 BigStarsingdance 方法之前或之后添加一些额外的逻辑(比如打印消息或执行其他操作)。通过使用动态代理,你可以在不修改 BigStar 类的情况下实现这一点。
  2. 返回代理对象:当你创建了一个代理对象并配置好它的行为后,你需要将这个代理对象返回给调用者,以便调用者可以使用它。在这个例子中,createProxy 方法返回了 star,它是一个指向代理对象的引用。调用者可以使用这个引用来调用 Star 接口的方法,而实际上会执行你在 InvocationHandlerinvoke 方法中定义的逻辑。
  3. 类型安全:由于 star 的类型是 Star,调用者可以将其视为一个实现了 Star 接口的对象,并调用其上的任何方法。这是类型安全的,因为代理对象确实实现了 Star 接口,并且重写了其中的所有方法。

总结一下,Star star 声明了一个 Star 类型的变量 star,而 Proxy.newProxyInstance 方法返回了一个实现了 Star 接口的代理对象。这个代理对象被赋值给 star 变量,并被返回给调用者,以便调用者可以使用它。这样,你就可以在不修改现有类代码的情况下,通过代理对象来增强类的功能了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/16239.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Fortran: stdlib标准库

Fortran 标准库 stdlib_logger,stdlib_error, stdlib_sorting,stdlib_optval模块挺好用 封装 stdlib_logger和stdlib_error: M_logger.F90 module M_loggeruse stdlib_loggeruse stdlib_error containssubroutine info(message,module,procedure)character(len*),intent(in):…

2024.5.25期末测试总结

成绩&#xff1a; 配置&#xff1a; 可能与实际有些出入 题目&#xff1a; 第一题&#xff1a; 代码思路&#xff1a; 一道模拟题&#xff0c;按照公式计算出sumpow(2,i)&#xff0c;判断sum>H&#xff0c;输出 代码&#xff1a; #include<bits/stdc.h> using name…

Java—内部类

Java—内部类 一、内部类二、应用特点三、分类3.1、普通内部类&#xff1a;直接将一个类的定义放在另外一个类的类体中3.2、静态内部类3.3、局部内部类 一、内部类 一个类的定义出现在另外一个类&#xff0c;那么这个出现的类就叫内部类(Inner)。 内部类所在的类叫做外部类(Ou…

Java匿名内部类的使用

演示匿名内部类的使用&#xff0c;很重要 package com.shedu.Inner;/*** 演示匿名内部类的使用*/ public class AnonymousInnerClass {//外部其他类public static void main(String[] args) {Outer04 outer04 new Outer04();outer04.method();} }class Outer04{//外部类priva…

在线软件包管理

1.APT工作原理 APT&#xff08;Advanced Packaging Tool&#xff09;是Debian系列Linux操作系统中广泛使用的包管理工具&#xff0c;它为用户提供了从软件仓库搜索、安装、升级和卸载软件包的功能。其工作原理具体分析如下&#xff1a; 1. **集中式软件仓库机制**&#xff1a…

Linux之Nginx

1、Nginx 1.1、什么是Nginx Nginx最初由Igor Sysoev开发&#xff0c;最早在2004年公开发布。它被设计为一个轻量级、高性能的服务器&#xff0c;能够处理大量并发连接而不消耗过多的系统资源。Nginx的架构采用了事件驱动的方式&#xff0c;能够高效地处理请求。它的模块化设计使…

python-情报加密副本

【问题描述】某情报机构采用公用电话传递数据&#xff0c;数据是5位的整数&#xff0c;在传递过程中是加密的。加密规则如下&#xff1a;每位数字都加上8,然后用和除以7的余数代替该数字&#xff0c;再将第1位和第5位交换&#xff0c;第2位和第4位交换。请编写程序&#xff0c;…

Denoising Diffusion Probabilistic Models 全过程概述 + 论文总结

标题&#xff1a;Denoising&#xff08;&#x1f31f;去噪&#xff09;Diffusion Probabilistic Models&#xff08;扩散概率模型&#xff09; 论文&#xff08;NeurIPS会议 CCF A 类&#xff09;&#xff1a;Denoising Diffusion Probabilistic Models 源码&#xff1a;hojona…

卡特兰数-

是组合数学中一种常出现于各种计数问题中的数列。 一、简单介绍 卡特兰数是一个数列&#xff0c;其前几项为&#xff08;从第零项开始&#xff09; : 1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440, 9694845, 35357670, 129644790, 47763…

QT---JSON使用

一、json 文件概述 JSON(JavaScript 0bject Notation)是一种轻量级的数据交换格式。易于人阅读和编写,可以在多种语言之间进行数据交换。同时也易于机器解析和生成,并有效地提升网络传输效率。采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 J…

lllyasviel /Fooocus图像生成软件(基于 Gradio)

一、简介 1、Fooocus 是一款图像生成软件(基于 Gradio)。 Fooocus is an image generating software (based on Gradio). Fooocus 是一款图像生成软件(基于 Gradio)。 Fooocus is a rethinking of Stable Diffusion and Midjourney’s designs: Fooocus 是对 Stable Diff…

澳大利亚.德国-新闻媒体投放通稿:发表新闻稿需要留意哪些地方-大舍传媒

概述 当我们想要发布新闻稿时&#xff0c;了解目标媒体的特点和要求是至关重要的。本文将介绍澳大利亚和德国的新闻媒体&#xff0c;以及在撰写和投放新闻稿时需要注意的要点&#xff0c;以帮助您更好地与目标受众沟通。 澳大利亚媒体 澳大利亚是一个多元化的国家&#xff0…

Android ListView鼠标模式下ListView回滚问题

概述 在 Android 应用程序中&#xff0c;ListView 是一种常用的控件&#xff0c;用于显示可滚动列表数据。然而&#xff0c;当在鼠标操作模式下使用 ListView 时&#xff0c;可能会遇到一个问题&#xff1a;点击列表项时&#xff0c;列表会回滚到指定位置&#xff0c;这可能会导…

什么叫图像的椒盐噪声?并附添加椒盐噪声的代码

图像的椒盐噪声是一种常见的图像噪声类型&#xff0c;通常是由于图像采集、传输或处理过程中引入的随机干扰所导致的。它的特点是在图像中出现随机的亮或暗的像素点&#xff0c;这些像素点看起来就像是图像中撒了一些盐和胡椒一样&#xff0c;因此得名。 椒盐噪声可能是由于传…

【ML Olympiad】预测地震破坏——根据建筑物位置和施工情况预测地震对建筑物造成的破坏程度

文章目录 Overview 概述Goal 目标Evaluation 评估标准 Dataset Description 数据集说明Dataset Source 数据集来源Dataset Fields 数据集字段 Data Analysis and Visualization 数据分析与可视化Correlation 相关性Hierarchial Clustering 分层聚类Adversarial Validation 对抗…

【OpenCV 基础知识 13】高斯平滑处理图像

函数 cvSmooth 可使用简单模糊、简单无缩放变换的模糊、中值模糊、高斯模糊、双边滤波的任何一种方法平滑图像。每一种方法都有自己的特点以及局限。 没有缩放的图像平滑仅支持单通道图像&#xff0c;并且支持8位到16位的转换&#xff08;与cvSobel和cvLaplace相似&#xff09;…

OFDM通信中的部分内容

纠错编码&#xff1a;在无线通信过程中由于传输过程存在噪声等各种非理想因素&#xff0c;在接收端接收到的信息往往相对于发射信息存在误码&#xff0c;通过纠错编码方式可以对少数非连续的误码进行判断和纠正。举个简单的例子&#xff0c;发射端可能发射的信息为00,01,10,11,…

二叉搜索树的后序遍历序列

二叉搜索树的后序遍历序列 背景题目描述题解 背景 每次重复刷到这题都没有思路,看答案也总需要理解一会,但是下次又忘了,哈哈哈,因此记录一下思路. 题目描述 牛客地址&#xff1a; https://www.nowcoder.com/practice/a861533d45854474ac791d90e447bafd 描述 输入一个整数数…

网络应用层之(1)DHCPv6协议

网络应用层之(1)DHCPv6协议 Author: Once Day Date: 2024年5月26日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: 通信网络技术_Once-Day的博客-C…

如何从头搭建一个自己的java库并上传到maven官方仓库

创建代码 在代码库根目录执行maven命令&#xff0c;用于快速生成一个基础的Maven项目 mvn archetype:generate \-DgroupIdcom.mycompany \-DartifactIdmy-maven-project \-Dversion1.0.0 \-DarchetypeArtifactIdmaven-archetype-quickstart \-DinteractiveModefalse 这个命令…