Java 反射机制与Spring框架的那点事

news2024/9/9 6:17:19

Java 反射机制是 Java 语言提供的一种能力,允许程序在运行时查询、访问和修改它自己的结构和行为。反射机制非常有用,但同时也需要谨慎使用,因为它可能会带来性能开销和安全风险。

以下是 Java 反射机制的一些关键概念和用法:

  1. Class 类:Java 中的每个类都有一个与之对应的 Class 类对象。通过这个 Class 对象,你可以获取类的名称、访问修饰符、字段、方法、构造函数等信息。

  2. 获取 Class 对象

    • 直接使用类名调用 .classClass<?> clazz = MyClass.class;
    • 使用 .class 属性:Class<?> clazz = MyClass.class;
    • 通过实例调用 getClass() 方法:Class<?> clazz = myObject.getClass();
    • 使用 forName() 方法:Class<?> clazz = Class.forName("MyClass"); 这个方法会通过类的全名来查找并加载类。
  3. 访问字段

    • 获取字段对象:Field field = clazz.getField("fieldName");
    • 设置字段值:field.set(object, value);
    • 获取字段值:Object value = field.get(object);
  4. 访问方法

    • 获取方法对象:Method method = clazz.getMethod("methodName", parameterTypes);
    • 调用方法:Object result = method.invoke(object, args);
  5. 访问构造函数

    • 获取构造函数对象:Constructor<?> constructor = clazz.getConstructor(parameterTypes);
    • 创建实例:Object instance = constructor.newInstance(args);
  6. 注解(Annotations)

    • 反射可以用来读取类的注解信息,这在很多框架中被广泛使用,比如 Spring。
  7. 泛型和数组

    • 反射同样可以处理泛型和数组类型。
  8. 安全问题

    • 反射可以绕过编译时的访问控制,因此可能会访问到一些本应受保护的成员。
  9. 性能问题

    • 反射操作通常比直接代码调用要慢,因此在性能敏感的应用中应谨慎使用。
  10. 动态代理

    • 反射机制是 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() {
                @Override
                public 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() 方法来创建代理对象。这个方法需要三个参数:

  1. 类加载器:someService.getClass().getClassLoader()
  2. 接口数组:someService.getClass().getInterfaces()
  3. 一个实现了 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();
        // 设置父类为 SomeServiceImpl
        enhancer.setSuperclass(SomeServiceImpl.class);
        // 设置回调函数,即拦截器
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public 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 类来创建代理对象:

  1. 创建 Enhancer 对象。
  2. 使用 setSuperclass() 方法指定要代理的类。
  3. 使用 setCallback() 方法设置一个实现了 MethodInterceptor 接口的匿名类。这个拦截器会在代理对象的方法被调用时执行。
  4. 使用 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 {
    @Override
    public 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;
    }

    @Override
    public Method getMethod() {
        return method;
    }

    @Override
    public 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;
    }

    @Override
    public 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 反射机制在软件开发中有着广泛的应用,以下是一些常见的实际应用场景:

  1. 框架开发:许多框架如 Spring 使用反射来实现依赖注入、AOP(面向切面编程)等核心功能。

  2. 动态代理:通过反射可以创建动态代理,用于实现方法拦截、日志记录、事务管理等功能。

  3. 单元测试:在进行单元测试时,反射可以用来访问和修改私有成员,以便测试通常无法直接访问的内部逻辑。

  4. 插件系统:应用程序可以利用反射动态加载和使用插件,从而扩展应用程序的功能。

  5. 配置文件解析:反射可以用于将配置文件中的设置映射到程序中的类和对象上。

  6. 对象序列化和反序列化:一些序列化框架(如 JSON、XML 序列化库)使用反射来动态读取和设置对象的属性。

  7. 泛型和集合操作:反射可以用于操作泛型类型,以及在运行时动态地操作集合。

  8. 注解处理:Java 反射可以用来读取和处理注解,这在编译时和运行时都很常见,例如用于生成代码或配置元数据。

  9. 动态类加载:在需要动态加载类的情况下,反射可以用来加载字节码并创建类的实例。

  10. 类型检查和类型转换:反射可以用来在运行时检查对象的类型,或者将对象转换为不同的类型。

  11. 访问和修改私有成员:在某些情况下,可能需要访问或修改类的私有成员,反射提供了这样的能力。

  12. 实现反射 API:Java 提供了丰富的反射 API,可以用来查询类的信息、创建对象实例、调用方法、访问字段等。

  13. 动态调用方法:在某些应用中,可能需要根据方法名字符串来动态调用方法,反射提供了这样的机制。

  14. 实现通用的数据处理:反射可以用来编写通用的数据访问层,处理不同实体的 CRUD 操作,而不需要为每个实体编写特定的代码。

  15. 实现工厂模式:反射可以用于实现工厂模式,根据字符串标识来创建对象实例。

反射机制虽然强大,但使用时需要注意性能开销和安全问题。在设计系统时,应权衡反射带来的灵活性和潜在的负面影响。

5. 最后

初学者在学习反射时,会无从下手,这很正常,因为在学习的过程中,没有实际的应用场景可以训练,这就是为什么我们要去学习优秀框架源码的原因,因为反射多数用在构建框架底层结构中被使用到,在应用开发时见不到,都被封装了,那我们为什么还要去了解呢,这个原因是因为很多公司会自定义满足自身要求的框架,而大多数都是基于开源框架做二次开发,这就需要充分理解开源框架的实现原理,也就会用到反射,在当下这个环境下,你懂的。欢迎关注威哥爱编程,我们一起成长。

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

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

相关文章

【Python】 ValueError: too many values to unpack 解决方案

【Python】 ValueError: too many values to unpack 解决方案 在Python编程中&#xff0c;ValueError: too many values to unpack是一个常见的错误&#xff0c;通常出现在使用解包操作时。本文将深入探讨这个错误的原因、解决思路、解决方法&#xff0c;并通过具体案例帮助大…

【Python学习手册(第四版)】学习笔记09.3-Python对象类型-分类、引用VS拷贝VS深拷贝、比较、相等、真假值等详解

个人总结难免疏漏&#xff0c;请多包涵。更多内容请查看原文。本文以及学习笔记系列仅用于个人学习、研究交流。 这部分稍杂&#xff0c;视需要选择目录读取。 主要讲的是对之前的所有对象类型作复习&#xff0c;以通俗易懂、由浅入深的方式进行介绍&#xff0c;所有对象类型…

『康之泉活水馆』手游:打造夏日梦幻水世界

设计背景 夏日的热浪与城市的喧嚣困扰着忙碌奔波的人群&#xff0c;康之泉活水馆&#xff0c;作为多功能的室内水上乐园&#xff0c;以其独特的魅力&#xff0c;成为夏日避暑的理想之地&#xff0c;让身心得以彻底放松。 设计理念 优联前端以康之泉品牌IP形象“康康”为灵感&a…

[GYCTF2020]FlaskApp (pin码,jinja2绕过注入)

题目就是flask 下面是判断模版注入的方法 a{*comment*}b和{{7*7}}base64编码后解码都报错no&#xff0c;无法判断模版引擎 直接用下jinja2的试一试&#xff0c;把编码后的密文拿去解码&#xff0c;payload&#xff1a; {{"".__class__mro(2)__subclasses__()}} 报…

英文文献翻译方法哪个好?高效率的翻译方法分享

三伏天的酷热也抵挡不住学术人探索知识的脚步&#xff0c;阅读和翻译英文文献几乎已经成为了许多研究者和学者的日常。然而在面对浩如烟海的英文资料时&#xff0c;如何高效准确地进行翻译&#xff0c;成为了亟待解决的问题。 今天我便挖掘到了5款实用的英文文献翻译工具&…

4.1.1、操作系统的概述

操作系统的作用:通过资源管理提高计算机系统的效率;改善人机界面向用户提供友好的工作环境。 操作系统的特征:并发性、共享性、虚拟性、不确定性。 操作系统的功能:进程管理、存储管理、文件管理、设备管理、作业管理。 操作系统的分类:批处理操作系统、分时操作系统(轮流使…

美股:苹果选择谷歌芯片支持人工智能技术

最近的研究报告显示&#xff0c;苹果公司在其新一代人工智能工具和功能套件中选择依赖谷歌设计的芯片&#xff0c;而非市场领导者 Nvidia。这一决定引发了业界的关注&#xff0c;尤其是考虑到Nvidia在人工智能处理器市场的主导地位。 谷歌云的TPU在苹果的AI基础设施中发挥关键作…

计算机再过几年会没落?

大部分人卷的计算机&#xff1a;Java web 实际上的计算机&#xff1a;web&#xff0c;图形学&#xff0c;Linux系统开发&#xff0c;一系列嵌入式开发&#xff0c;数据库&#xff0c;高性能服务器&#xff0c;中间件开发&#xff0c;三维建模&#xff0c;网络安全&#xff0c;…

vue2 封装弹框组件

安装 element-ui npm install element-ui --save ---force main.js 导入 import Vue from vue; import ElementUI from element-ui; import element-ui/lib/theme-chalk/index.css; import App from ./App.vue; Vue.use(ElementUI); new Vue({ el: #app, render: h > h(Ap…

Minio、MySQL、Redis、Milvus 安装

CPU&#xff1a;2核↑&#xff0c;内存&#xff1a;4GB↑ 开发工具&#xff1a;eclipse-jee、MySQL Workbench、MobaXterm、Redis Insight... 操作系统&#xff1a;CentOS Stream 9&#xff08;生产环境&#xff09;、Windos 11 Ubuntu 22.04.3&#xff08;开发环境&#xf…

使用 Kibana 和 Vega 构建高级可视化

作者&#xff1a;来自 Carly Richmond 为了解释在 Kibana 中构建 Vega 可视化的基础知识&#xff0c;我将使用此 GitHub 存储库中的 2 个示例。具体来说&#xff0c;我将介绍&#xff1a; 使用 Elasticsearch 聚合进行数据采购轴和标记事件和信号&#xff08;例如工具提示和更…

工具使用备忘录

npm npm是node包管理工具。yarn是npm的替代品&#xff0c;看起来使用更加广泛。cnpm是中国镜像。 cnpm安装时&#xff0c;曾经出现过错误。不进行安全验证后运行成功。 目前在WSL上可以全局使用cnpm 当时解决的方案是将报错信息copy下来&#xff0c;直接看通义千问的结果。 …

时间序列中多维度、多变量、多元、多尺度

目录 多尺度 多维度 多变量 多元 区别 举例&#xff1a; 多尺度 多尺度时间序列分析是指在不同的时间尺度上对数据进行分析。例如&#xff0c;某些现象可能在短期内表现出一种模式&#xff0c;而在长期内表现出另一种模式。多尺度分析可以帮助我们捕捉这些不同时间尺度上…

北斗RTK高精度定位系统介绍

北斗RTK高精度定位系统是一种高精度、高可靠、高效率的定位系统。它采用北斗卫星导航系统&#xff0c;结合实时动态差分RTK技术&#xff0c;能够实现亚米级的定位准确度&#xff0c;适用于石油化工、工厂、工地、园区、环卫等领域。 北斗RTK的核心技术是RTK技术&#xff0c;即实…

C++ | string

前言 本篇博客讲解c中的string类的使用(常用接口) &#x1f493; 个人主页&#xff1a;普通young man-CSDN博客 ⏩ 文章专栏&#xff1a;C_普通young man的博客-CSDN博客 ⏩ 本人giee:普通小青年 (pu-tong-young-man) - Gitee.com 若有问题 评论区见&#x1f4dd; &#x1f389…

在 Elasticsearch 中实现采集自动扩展

作者&#xff1a;来自 Elastic Pooya Salehi, Henning Andersen, Francisco Fernndez Castao 正确调整 Elasticsearch 集群的大小并不容易。集群的最佳大小取决于集群正在经历的工作负载&#xff0c;而工作负载可能会随着时间的推移而变化。自动扩展会自动调整集群大小以适应工…

【JavaScript】详解JavaScript语法

文章目录 一、变量和数据类型二、运算符三、条件语句四、循环语句五、函数六、对象和数组七、ES6新特性八、实际应用案例 JavaScript是一门广泛应用于Web开发的编程语言。掌握JavaScript语法是成为前端开发者的第一步。本文将详细介绍JavaScript的基本语法&#xff0c;包括变量…

“Assistants“ has no attribute “files“ in openAI assistants

题意&#xff1a;在 OpenAI 的助手&#xff08;assistants&#xff09;中&#xff0c;“Assistants” 没有 “files” 这个属性。 问题背景&#xff1a; assistant_file client.beta.assistants.files.create(assistant_id st.session_state.assistant_id,file_id st.sessi…

2024年最佳骨传导耳机推荐:五款不容错过的选择!

作为音乐爱好者的我&#xff0c;也一直在寻找一款好的骨传导耳机&#xff0c;听音乐对我来说不仅仅是一种消遣方式&#xff0c;更多是一种对生活、工作上压力和困难的舒缓&#xff0c;所以今天给大家推荐几款骨传导耳机。今天推荐这几款骨传导耳机都是比较有性价比&#xff0c;…

Oracle VM VirtualBox 异常退出,如何解决??

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…