Java 反射机制与Spring框架的那点事
Java 反射机制是 Java 语言提供的一种能力,允许程序在运行时查询、访问和修改它自己的结构和行为。反射机制非常有用,但同时也需要谨慎使用,因为它可能会带来性能开销和安全风险。
以下是 Java 反射机制的一些关键概念和用法:
-
Class 类:Java 中的每个类都有一个与之对应的
Class类对象。通过这个Class对象,你可以获取类的名称、访问修饰符、字段、方法、构造函数等信息。 -
获取 Class 对象:
- 直接使用类名调用
.class:Class<?> clazz = MyClass.class; - 使用
.class属性:Class<?> clazz = MyClass.class; - 通过实例调用
getClass()方法:Class<?> clazz = myObject.getClass(); - 使用
forName()方法:Class<?> clazz = Class.forName("MyClass");这个方法会通过类的全名来查找并加载类。
- 直接使用类名调用
-
访问字段:
- 获取字段对象:
Field field = clazz.getField("fieldName"); - 设置字段值:
field.set(object, value); - 获取字段值:
Object value = field.get(object);
- 获取字段对象:
-
访问方法:
- 获取方法对象:
Method method = clazz.getMethod("methodName", parameterTypes); - 调用方法:
Object result = method.invoke(object, args);
- 获取方法对象:
-
访问构造函数:
- 获取构造函数对象:
Constructor<?> constructor = clazz.getConstructor(parameterTypes); - 创建实例:
Object instance = constructor.newInstance(args);
- 获取构造函数对象:
-
注解(Annotations):
- 反射可以用来读取类的注解信息,这在很多框架中被广泛使用,比如 Spring。
-
泛型和数组:
- 反射同样可以处理泛型和数组类型。
-
安全问题:
- 反射可以绕过编译时的访问控制,因此可能会访问到一些本应受保护的成员。
-
性能问题:
- 反射操作通常比直接代码调用要慢,因此在性能敏感的应用中应谨慎使用。
-
动态代理:
- 反射机制是 Java 动态代理的基础,允许在运行时创建接口的代理实例。
反射机制在很多高级应用场景中非常有用,比如框架的实现、依赖注入、单元测试等。然而,由于它可能带来的安全和性能问题,开发者在使用时应权衡其利弊。
1. 使用反射实现一个动态代理案例
在 Java 中,动态代理是一种在运行时创建代理对象的技术,它允许我们为接口的实现类添加额外的行为。以下是使用 Java 反射实现动态代理的一个简单例子。
假设我们有一个接口 SomeService 和它的实现类 SomeServiceImpl:
public interface SomeService {void doSomething();
}public class SomeServiceImpl implements SomeService {public void doSomething() {System.out.println("Doing something...");}
}
现在,我们想要创建一个代理类,它在调用 SomeService 的方法之前和之后添加一些额外的日志信息:
import java.lang.reflect.*;public class DynamicProxyExample {public static void main(String[] args) {// 创建 SomeServiceImpl 的实例SomeService someService = new SomeServiceImpl();// 创建代理对象SomeService proxyInstance = (SomeService) Proxy.newProxyInstance(someService.getClass().getClassLoader(),someService.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Before method: " + method.getName());// 调用原始方法Object result = method.invoke(someService, args);System.out.println("After method: " + method.getName());return result;}});// 使用代理对象调用方法proxyInstance.doSomething();}
}
在这个例子中,我们使用了 Proxy.newProxyInstance() 方法来创建代理对象。这个方法需要三个参数:
- 类加载器:
someService.getClass().getClassLoader() - 接口数组:
someService.getClass().getInterfaces() - 一个实现了
InvocationHandler接口的匿名类:这个类重写了invoke()方法,该方法会在代理对象的方法被调用时执行。
invoke() 方法有三个参数:
proxy:代理对象的实例。method:被调用的方法的Method对象。args:传递给被调用方法的参数数组。
在 invoke() 方法中,我们可以添加任何我们想要的额外行为,比如日志记录,然后通过调用原始对象的相应方法来执行实际的操作。
请注意,这个例子中的代理只能用于实现了接口的对象。如果你需要代理一个类而不是接口,你可能需要使用其他技术,比如 CGLIB 库。
2. 使用CGLIB库实现动态代码案例
CGLIB(Code Generation Library)是一个强大的高性能代码生成库,用于在运行时扩展 Java 类和实现接口。与 Java 原生的动态代理不同,CGLIB 可以代理没有实现接口的类,并且可以添加方法拦截。
以下是使用 CGLIB 实现动态代理的一个简单例子:
首先,你需要将 CGLIB 库添加到你的项目中。如果你使用 Maven,可以在 pom.xml 文件中添加以下依赖:
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version> <!-- 请使用最新的版本号 -->
</dependency>
然后,我们可以创建一个 CGLIB 动态代理的示例:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.InvocationHandler;public class CglibDynamicProxyExample {public static void main(String[] args) {// 创建目标对象SomeServiceImpl target = new SomeServiceImpl();// 创建 Enhancer 对象Enhancer enhancer = new Enhancer();// 设置父类为 SomeServiceImplenhancer.setSuperclass(SomeServiceImpl.class);// 设置回调函数,即拦截器enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("Before method: " + method.getName());// 调用原始方法Object result = proxy.invokeSuper(obj, args);System.out.println("After method: " + method.getName());return result;}});// 创建代理对象SomeServiceImpl proxyInstance = (SomeServiceImpl) enhancer.create();// 使用代理对象调用方法proxyInstance.doSomething();}
}interface SomeService {void doSomething();
}class SomeServiceImpl implements SomeService {public void doSomething() {System.out.println("Doing something...");}
}
在这个例子中,我们使用了 CGLIB 的 Enhancer 类来创建代理对象:
- 创建
Enhancer对象。 - 使用
setSuperclass()方法指定要代理的类。 - 使用
setCallback()方法设置一个实现了MethodInterceptor接口的匿名类。这个拦截器会在代理对象的方法被调用时执行。 - 使用
create()方法创建代理对象。
intercept() 方法有四个参数:
obj:被代理的对象。method:被调用的方法的Method对象。args:传递给被调用方法的参数数组。proxy:一个MethodProxy对象,可以用来调用原始方法。
在 intercept() 方法中,我们可以添加任何我们想要的额外行为,然后通过调用 proxy.invokeSuper(obj, args) 来执行原始方法。
CGLIB 动态代理提供了一种强大的机制,可以在不修改原始类代码的情况下,为类添加额外的功能。这在很多框架中被广泛使用,比如 Spring 的 AOP 模块。
3. 使用动态代理实现模拟 Spring 的 AOP 实现案例
在 Spring 框架中,AOP(面向切面编程)是一种编程范式,它允许你将横切关注点(如日志记录、安全性、事务管理等)与业务逻辑分离。Spring AOP 通过代理机制实现,可以是 JDK 动态代理或 CGLIB 代理。
这里我们将通过 Java 原生的动态代理来模拟一个简单的 Spring AOP 实现。假设我们有一个服务接口 SomeService 和它的实现 SomeServiceImpl:
public interface SomeService {void doSomething();
}
public class SomeServiceImpl implements SomeService {@Overridepublic void doSomething() {System.out.println("Doing something...");}
}
接下来,我们将创建一个切面(Aspect),其中包含一个方法,该方法在目标方法执行前后添加日志:
public class LoggingAspect {public void beforeAdvice(JoinPoint joinPoint) {System.out.println("Before " + joinPoint.getMethod().getName());}public void afterAdvice(JoinPoint joinPoint) {System.out.println("After " + joinPoint.getMethod().getName());}
}
为了模拟 Spring AOP 的行为,我们定义一个 JoinPoint 接口,它将用于传递方法调用的相关信息:
public interface JoinPoint {Method getMethod();Object getTarget();
}
然后,我们定义一个 JoinPointImpl 类来实现 JoinPoint 接口:
import java.lang.reflect.Method;public class JoinPointImpl implements JoinPoint {private Method method;private Object target;public JoinPointImpl(Method method, Object target) {this.method = method;this.target = target;}@Overridepublic Method getMethod() {return method;}@Overridepublic Object getTarget() {return target;}
}
最后,我们将创建一个 AspectProxy 类来生成代理对象,并在代理对象的方法调用中应用切面逻辑:
import java.lang.reflect.*;public class AspectProxy implements InvocationHandler {private Object target;private LoggingAspect loggingAspect;public AspectProxy(Object target, LoggingAspect loggingAspect) {this.target = target;this.loggingAspect = loggingAspect;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {JoinPoint joinPoint = new JoinPointImpl(method, target);loggingAspect.beforeAdvice(joinPoint);Object result = method.invoke(target, args);loggingAspect.afterAdvice(joinPoint);return result;}public static Object createProxy(Object target, LoggingAspect aspect) {return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new AspectProxy(target, aspect));}
}
现在,我们可以创建一个测试类来演示如何使用这个模拟的 AOP 实现:
public class AopDemo {public static void main(String[] args) {SomeService someService = new SomeServiceImpl();LoggingAspect loggingAspect = new LoggingAspect();// 创建代理对象SomeService proxy = (SomeService) AspectProxy.createProxy(someService, loggingAspect);// 使用代理对象调用方法proxy.doSomething();}
}
在这个模拟的 AOP 实现中,我们通过 AspectProxy 类的 invoke() 方法在目标方法调用前后添加了日志记录。这种方式展示了如何在不修改原始类代码的情况下,通过代理机制实现 AOP 功能。
请注意,这只是一个简单的示例,真实的 Spring AOP 实现更为复杂,支持更多的功能如切入点表达式、通知类型(前置、后置、异常抛出、环绕等)、切面优先级等。
4. 总结反射在开发中的实际应用场景
Java 反射机制在软件开发中有着广泛的应用,以下是一些常见的实际应用场景:
-
框架开发:许多框架如 Spring 使用反射来实现依赖注入、AOP(面向切面编程)等核心功能。
-
动态代理:通过反射可以创建动态代理,用于实现方法拦截、日志记录、事务管理等功能。
-
单元测试:在进行单元测试时,反射可以用来访问和修改私有成员,以便测试通常无法直接访问的内部逻辑。
-
插件系统:应用程序可以利用反射动态加载和使用插件,从而扩展应用程序的功能。
-
配置文件解析:反射可以用于将配置文件中的设置映射到程序中的类和对象上。
-
对象序列化和反序列化:一些序列化框架(如 JSON、XML 序列化库)使用反射来动态读取和设置对象的属性。
-
泛型和集合操作:反射可以用于操作泛型类型,以及在运行时动态地操作集合。
-
注解处理:Java 反射可以用来读取和处理注解,这在编译时和运行时都很常见,例如用于生成代码或配置元数据。
-
动态类加载:在需要动态加载类的情况下,反射可以用来加载字节码并创建类的实例。
-
类型检查和类型转换:反射可以用来在运行时检查对象的类型,或者将对象转换为不同的类型。
-
访问和修改私有成员:在某些情况下,可能需要访问或修改类的私有成员,反射提供了这样的能力。
-
实现反射 API:Java 提供了丰富的反射 API,可以用来查询类的信息、创建对象实例、调用方法、访问字段等。
-
动态调用方法:在某些应用中,可能需要根据方法名字符串来动态调用方法,反射提供了这样的机制。
-
实现通用的数据处理:反射可以用来编写通用的数据访问层,处理不同实体的 CRUD 操作,而不需要为每个实体编写特定的代码。
-
实现工厂模式:反射可以用于实现工厂模式,根据字符串标识来创建对象实例。
反射机制虽然强大,但使用时需要注意性能开销和安全问题。在设计系统时,应权衡反射带来的灵活性和潜在的负面影响。
5. 最后
初学者在学习反射时,会无从下手,这很正常,因为在学习的过程中,没有实际的应用场景可以训练,这就是为什么我们要去学习优秀框架源码的原因,因为反射多数用在构建框架底层结构中被使用到,在应用开发时见不到,都被封装了,那我们为什么还要去了解呢,这个原因是因为很多公司会自定义满足自身要求的框架,而大多数都是基于开源框架做二次开发,这就需要充分理解开源框架的实现原理,也就会用到反射,在当下这个环境下,你懂的。欢迎关注威哥爱编程,我们一起成长。
相关文章:
Java 反射机制与Spring框架的那点事
Java 反射机制是 Java 语言提供的一种能力,允许程序在运行时查询、访问和修改它自己的结构和行为。反射机制非常有用,但同时也需要谨慎使用,因为它可能会带来性能开销和安全风险。 以下是 Java 反射机制的一些关键概念和用法: Cl…...
计算机网络面试题3
四次挥手 断开连接需要四次挥手 1.客户端发送一个FIN(SEQx)标志的数据包到服务端,用来关闭客户端到服务端的数据传送, 然后客户端进入FIN-WAIT-1状态。 2.服务端收到一个FIN(SEQx)标志的数据包,它…...
day54|110.字符串接龙, 105.有向图的完全可达性, 106.岛屿的周长
110.字符串接龙 110. 字符串接龙 (kamacoder.com) #include<iostream> #include<vector> #include<unordered_set> #include<unordered_map> #include<string> #include<queue>using namespace std;int main(){int n 0;cin >> n;…...
使用docker在CentOS 7上安装php+mysql+nginx环境教程并运行WordPress
文章目录 一、安装docker1、切换yum源并更新系统2、卸载旧版docker3、配置Docker的yum库4、安装Docker5、启动和校验Docker6、配置镜像加速6.1、注册阿里云账号6.2、开通镜像服务6.3、配置镜像加速二、部署php+mysql+nginx环境1、准备目录结构2、拉取镜像3、运行容器并从中拷贝…...
vite tsx项目的element plus集成 - 按需引入踩坑
前面我们进行了开源组件的自研,很多组件可直接用现成的开源组件库,并不需要自己重复造轮子,为此我们讲如何在当前vite vitepress tsx技术整合的项目中实现element plus组件的按需引入,同时解决遇到的一些坑。 安装Element Plus…...
Android GreenDao 升级 保留旧表数据
Android GreenDao 升级 保留旧表数据 大川的川关注IP属地: 北京 0.2052019.08.05 11:54:36字数 270阅读 363 瓦力和伊娃 GreenDao升级库版本号之后,以前的旧数据没有了,为啥,因为GreenDao在升级的时候会删除旧库,创建新库&#…...
记一次证书站有趣的SQL注入
一、确定站点 按照以前文章中提到的寻找可进站测试的思路,找到了某证书站的一处站点,通告栏中写明了初始密码的结构,因此我们可通过信息搜集进入该站点(可以考虑去搜集比较老的学号,因为这样的账号要么被冻结,要么就是…...
1_初识pytorch
之前完全没有了解过深度学习和pytorch,但现在因为某些原因不得不学了。不得不感叹,深度学习是真的火啊。纯小白,有错的欢迎指正~ 参考视频:PyTorch深度学习快速入门教程(绝对通俗易懂!)【小土堆…...
c++typeid()的使用
用处: typeid()函数主要用来获取对应类型或者变量的类型信息,其返回一个std::type_info的对象,这个对象中存放了对应类型的具体信息。 所以typeid()函数就是获取一个type_info的类型,然后可以通过此类型来获取到相应的类型信息。 type_info的…...
【面向就业的Linux基础】从入门到熟练,探索Linux的秘密(十四)-租云服务器及配环境、docker基本命令
主要介绍了租云服务器和docker配置、基本命令!!! 文章目录 前言 一、云平台 二、租云服务器及安装docker 1.阿里云 2.安装docker 三、docker命令 将当前用户添加到docker用户组 镜像(images) 容器(container) 四、实战…...
实现一个全栈模糊搜索匹配的功能
提供一个全栈实现的方案,包括 Vue 3 前端、Express 后端和 MySQL 数据库的分类模糊搜索功能。让我们逐步来看: 1. 数据库设计 (MySQL) 首先,我们需要一个存储分类的表: CREATE TABLE categories (id INT AUTO_INCREMENT PRIMAR…...
智慧景区导览系统小程序开发
智慧景区导览系统小程序的开发是一个综合性的过程,旨在通过先进的技术手段提升游客的游览体验。以下是开发智慧景区导览系统小程序的主要步骤和关键点: 一、需求分析 市场调研:了解旅游市场的最新趋势和游客的实际需求,包括游客…...
HIVE调优方式及原因
3.HIVE 调优: 需要调优的几个方面: 1.HIVE语句执行不了 2.HIVE查询语句,在集群中执行时,数据无法落地 HIVE执行时,一开始语句检查没有问题,生成了多个JOB, …...
deploy local llm ragflow
CPU > 4 cores RAM > 16 GB Disk > 50 GB Docker > 24.0.0 & Docker Compose > v2.26.1 下载docker: 官方下载方式:https://docs.docker.com/desktop/install/ubuntu/ 其中 DEB package需要手动下载并传输到服务器 国内下载方式&…...
测桃花运(算姻缘)的网站系统源码
简介: 站长安装本源码后只要有人在线测算,就可以获得收入哦。是目前市面上最火的变现利器。 本版本无后台,无数据。本版本为开发的逗号联盟接口版本。直接对接逗号联盟,修改ID就可以直接运营收费赚钱。 安装环境:PH…...
电商平台优惠券
优惠券业务逻辑 优惠券的发放: 来源:优惠券可以由平台统一发放,也可以由商家自行发放。平台优惠券的优惠由平台承担,而店铺优惠券则由商家承担。类型:优惠券可以分为满减优惠券、无门槛优惠券等,根据使用限…...
内衣洗衣机多维度测评对比,了解觉飞、希亦、鲸立哪款内衣洗衣机更好
想要代替手洗内衣物,那么一台内衣专用的小型洗衣机就必不可少啦,不仅能够为我们节约更多的时间以及精力,还能大大提高内衣物的卫生,面对于市面上各种各样的小型内衣洗衣机,相信很多小伙伴都无从下手! 为一…...
数据结构和算法入门
1.了解数据结构和算法 1.1 二分查找 二分查找(Binary Search)是一种在有序数组中查找特定元素的搜索算法。它的基本思想是将数组分成两半,然后比较目标值与中间元素的大小关系,从而确定应该在左半部分还是右半部分继续查找。这个…...
基于OpenCV C++的网络实时视频流传输——Windows下使用TCP/IP编程原理
1.TCP/IP编程 1.1 概念 IP 是英文 Internet Protocol (网络之间互连的协议)的缩写,也就是为计算机网络相互连接进行通信而设计的协议。任一系统,只要遵守 IP协议就可以与因特网互连互通。 所谓IP地址就是给每个遵循tcp/ip协议连…...
(BS ISO 11898-1:2015)CAN_FD 总线协议详解6- PL(物理层)规定3
目录 6.4 AUI 规范 6.4.1 一般规定 6.4.2 PCS 到 PMA 消息 6.4.2.1 输出消息 6.4.2.2 Bus_off 消息 6.4.2.3 Bus_off 释放消息 6.4.2.4 FD_Transmit 消息 6.4.2.5 FD_Receive 消息 6.4.3 PMA 到 PCS 消息 6.4.3.1 输入消息 如果有不懂的问题可在评论区点赞后留言&…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...
关于easyexcel动态下拉选问题处理
前些日子突然碰到一个问题,说是客户的导入文件模版想支持部分导入内容的下拉选,于是我就找了easyexcel官网寻找解决方案,并没有找到合适的方案,没办法只能自己动手并分享出来,针对Java生成Excel下拉菜单时因选项过多导…...
Chrome 浏览器前端与客户端双向通信实战
Chrome 前端(即页面 JS / Web UI)与客户端(C 后端)的交互机制,是 Chromium 架构中非常核心的一环。下面我将按常见场景,从通道、流程、技术栈几个角度做一套完整的分析,特别适合你这种在分析和改…...
go 里面的指针
指针 在 Go 中,指针(pointer)是一个变量的内存地址,就像 C 语言那样: a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10,通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...
springboot 日志类切面,接口成功记录日志,失败不记录
springboot 日志类切面,接口成功记录日志,失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...
