Java反射角度理解spring

news2024/12/28 19:30:21

概述

Java反射(Reflection)是Java编程语言的一个特性,它允许在运行时对类、接口、字段和方法进行动态查询和操作。反射提供了一种在运行时查看和修改程序行为的能力,这通常用于实现一些高级功能,如框架(Spring)、ORM(对象关系映射)工具、IDE(集成开发环境)等。 
反射的主要用途
动态加载类:可以在运行时加载和使用类,而无需在编写/编译时知道它们。
检查类信息:可以获取类的名称、父类、实现的接口、字段、方法等。
动态调用方法:可以调用类的任何公共方法,即使该方法在编译时未知。
创建和操作对象:可以创建类的实例,并调用其方法或访问其字段。
修改字段值:可以修改类的私有字段的值(但通常不推荐这样做,因为它破坏了封装性,又名暴力反射)。 

反射使用场景及理解 

1.1. 动态加载类信息及动态调用类中的方法

 假设我们需要报考微软云的证书证书分别有如下几种,而且将来可能还会新增。
Azure900, Azure903, Azure300, Azure380

我们假设有这样一个需求,当用户传入的参数能模糊匹配上如下的方法名时,就算要报名该考试。如我传入 "30", 这个值正好能模糊匹配上方法名“singUpAzure930Exam”和“signUpAzure300Exam”,这时就报名930 和300两门考试,如我传入Azure,很显然,四门课程都会报名。具体需要传入什么样的参数根据用户需求动态而定。

我们这里用伪代码编写一个报名系统,首先我们申明一个报名类。

public class AzureCloudExam {
    public void AzureCloudExam(){
    }

    public String signUpAzure900Exam(){
        return "You signed up Azure 900!";
    }

    public String singUpAzure930Exam(){
        return "You signed up Azure 930!";
    }

    public String signUpAzure300Exam(){
        return "You signed up Azure 300!";
    }

    public String signUpAzure380Exam(){
        return "You signed up Azure 380!";
    }
}

显然这种场景,使用我们以往的先new 对象,再调用方法的普通模式是很难做到的,而且将来有新增加的考试方法后,我们也需要修改调用考试方法的服务类才能做到,继而需要修改服务代码,重新部署等麻烦。因为这种普通模式需要调用哪个方法是我们编写是就必须按照逻辑提前写好的,显然它不够灵活,然后在我们平时的开发过程中,普通方式new 对象调用方法已经足够用了,但是在面临一些更加灵活的需要时,就不行了,如spring框架等都有大量的使用反射解决,因为我们在使用spring框架开发时,显然编写阶段spring也是不知道需要加载哪些bean的。而是运行时根据注解动态生成的。

因而如果我们使用反射,就可以很好的解决动态调用方法的这个问题,模拟步骤如下:

编写服务代码用于调用如上的考试类。

package com.mycompany.myreflect.refDemo2;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MyReflectionServer {
    public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException {
        //接收用户传入参数
        String className = args[0];
        String examLike = args[1];
        //动态加载需要使用class字节码文件
        Class<?> clazz = Class.forName(className);
        //动态无参构造方法new 对象
        Object obj = clazz.getDeclaredConstructor().newInstance();
        //获取class字节码中对应的所有方法
        Method[] allMethods = clazz.getDeclaredMethods();
        //loop所有方法
        for (Method method:allMethods){
            //如果用户传入的参数能模糊匹配上当前方法名
            if (method.getName().contains(examLike)){
                //调用报名方法
                String result = (String) method.invoke(obj);
                System.out.println(result);
            }
        }
    }
}

如我们传入参数
运行结果

如上实现了动态决定调用哪个方法。
如将来还要加入Alicloud 的考试门类,我们也只需要添加Alicloud的报名类就可以了,而服务类的代码不需要做任何改动。这就是反射带来的动态调用方法的灵活性。

1.2. 动态创建类对象

假设我们有这样一个需求。还是如上的报名考试系统,但是由于业务的扩展,现在不仅要支持微软云认证报名,还需要支持阿里云报名。将来还会新增厂家。报名的用户也在爆发增长。
显然我们每次客户请求都new一个对应厂商的的类,然后再在该类中去调用对应厂商的服务接口是报名,会带来不必要的对象创建和销毁。我们这里只需要一个单例的bean对象就可以了。

  • 在创建报名类之前我们先创建一个注解@MySupportExam, 用于注解某厂商的某类考试我们是否支持在我们这里报名,因为有的门类报名,对应厂商有授权考虑。
    package com.mycompany.myreflect.refDemo2;
    
    import java.lang.annotation.*;
    
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MySupportExam {
    }
    
  • 创建@MyComponent注解,用于标准这是我们需要注入的厂商bean
    package com.mycompany.myreflect.refDemo2;
    
    import java.lang.annotation.*;
    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyComponent {
    }
  • 创建对应厂商的报名类。
    package com.mycompany.myreflect.refDemo2;
    
    @MyComponent
    public class AzureCloudExam {
        public void AzureCloudExam(){}
        @MyEnableSupport
        public String signUpAzure900Exam(){return "You signed up Azure 900!";}
        @MyEnableSupport
        public String singUpAzure930Exam(){return "You signed up Azure 930!";}
    
        public String signUpAzure300Exam(){return "You signed up Azure 300!";}
    
        public String signUpAzure380Exam(){return "You signed up Azure 380!";}
    }
    
    package com.mycompany.myreflect.refDemo2;
    
    @MyComponent
    public class AliCloudExam {
        public void AliCloudExam(){
        }
    
        public String signUpAli900Exam(){return "You signed up Ali 900!";}
        @MyEnableSupport
        public String singUpAli930Exam(){return "You signed up Ali 930!";}
        @MyEnableSupport
        public String signUpAli300Exam(){return "You signed up Ali 300!";}
    
        public String signUpAli380Exam(){return "You signed up Ali 380!";}
    }
    
  •  创建一个ClassScanner 扫描添加了@MyCompany注解的厂商类
    package com.mycompany.myreflect.refDemo2;
    import java.io.File;
    import java.net.URL;
    import java.net.URLDecoder;
    import java.util.Enumeration;
    import java.util.LinkedHashSet;
    import java.util.List;
    import java.util.Set;
    
    public class ClassScanner {
        public static Set<Class<?>> scanPackage(String packageName) {
            Set<Class<?>> classSet = new LinkedHashSet<>();
            try {
                Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packageName.replace(".", "/"));
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    String protocol = url.getProtocol();
                    if ("file".equals(protocol)) {
                        String filePath = URLDecoder.decode(url.getPath(), "UTF-8");
                        findClassByPackageName(new File(filePath), packageName, classSet);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return classSet;
        }
    
        private static void findClassByPackageName(File file, String packageName, Set<Class<?>> classSet) throws ClassNotFoundException {
            if (file.isFile() && file.getName().endsWith(".class")) {
                String className = file.getName().substring(0, file.getName().length() - 6);
                String fullClassName = packageName + "." + className;
                Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(fullClassName);
                classSet.add(clazz);
            } else if (file.isDirectory()) {
                File[] files = file.listFiles();
                for (File f : files) {
                    findClassByPackageName(f, packageName, classSet);
                }
            }
        }
    
        public static List<Class<?>> getExamCompanyClazz(List<Class<?>> examCompany,String packageName) {
            Set<Class<?>> classes = scanPackage(packageName);
            for (Class<?> clazz : classes) {
                if (clazz.getDeclaredAnnotation(MyComponent.class) !=null){
                    examCompany.add(clazz);
                }
            }
            return examCompany;
        }
    }
    
  •  创建一个容器类用于管理所有的厂商bean,和方法bean
    package com.mycompany.myreflect.refDemo2;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class ExamContainer {
        private Map<Class<?>,List<Method>> methodsSupportMap;
        private Map<Class<?>,Object> servicesMap;
        private List<Class<?>> examCompanies;
    
        public void init() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException {
            examCompanies = new ArrayList<>();
            servicesMap = new HashMap<>();
            methodsSupportMap = new HashMap<>();
            ClassScanner.getExamCompanyClazz(examCompanies,"com.mycompany.myreflect.refDemo2");
            for (int i = 0;i<examCompanies.size();i++){
                Class<?> clazz = examCompanies.get(i);
                //将获取到的厂商字节码,判断容器中是否存在该对象,如果不存在则创建该对象并放入容器中
                setServiceInstanceByClass(clazz);
                setMethod(clazz);
            }
        }
    
        public void setServiceInstanceByClass(Class<?> clazz) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException {
            Object obj = clazz.getDeclaredConstructor().newInstance();
            servicesMap.put(clazz,obj);
        }
    
        public void setMethod(Class<?> clazz){
            Method[] methods =clazz.getDeclaredMethods();
            // 如果方法加了@MySupportExam注解,说明我们这里支持该科目考试,加入容器
            for (Method method: methods){
                if (method.getDeclaredAnnotation(MySupportExam.class) != null){
                    if (methodsSupportMap.get(clazz)!=null){
                        methodsSupportMap.get(clazz).add(method);
                    }else {
                        List<Method> methodList = new ArrayList<>();
                        methodList.add(method);
                        methodsSupportMap.put(clazz,methodList);
                    }
                }
            }
        }
    
        public void signUpExam(String examLike) throws InvocationTargetException, IllegalAccessException {
            for (Class<?> clazz:examCompanies) {
                Object object = servicesMap.get(clazz);
                List<Method> methodList = methodsSupportMap.get(clazz);
                for (Method method : methodList) {
                    if (method.getName().contains(examLike)) {
                        System.out.println(method.invoke(object));
                    }
                }
            }
    
        }
    
    }
    
  • 用户请求服务方法调用 
    package com.mycompany.myreflect.refDemo2;
    
    import java.lang.reflect.InvocationTargetException;
    
    public class MyReflectionServer {
        public static void main(String[] args) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException {
            ExamContainer examContainer = new ExamContainer();
            examContainer.init();
            String examLike = args[0];
            examContainer.signUpExam(examLike);
        }
    }
    

    请求参数
    运行结果

    请求参数

    运行结果

 1.3. 暴力反射

最近在开发过程中使用到暴力反射的一个案例就是,当我去给一个service类写UT实,发现某些方法是private的,但是这些private的方法,又是需要写UT的,这个时候我们就可以利用反射的暴力反射去为这个方法写UT调用。只需要设置如下属性即可,这里不在代码举案例。

​​​​​​​method.setAccessible(true);

注意事项

性能:反射通常比直接方法调用要慢得多,因为涉及到了额外的类型检查和安全性检查。因此,在性能敏感的代码中应谨慎使用。
安全性:反射允许绕过访问控制检查(例如,可以访问和修改私有字段),这可能导致安全问题。因此,在使用反射时应格外小心。
可读性:使用反射的代码通常比直接代码更难理解和维护。因此,在可以使用直接代码的情况下,应避免使用反射。 

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

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

相关文章

考研计组chap1计算机系统概述

目录 一、计算机发展历程(不考了) 二、计算机硬件的基本组成 3 1.五个部分 &#xff08;1&#xff09;输入设备 &#xff08;2&#xff09;控制器 &#xff08;3&#xff09;运算器 &#xff08;4&#xff09;&#xff08;主&#xff09;存储器 &#xff08;5&#xff0…

探索Python技巧:零基础学习缩进与逻辑关系

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、理解Python的缩进语法 缩进规则详解 二、缩进在逻辑关系中的应用 逻辑块示例 三、实…

Leecode热题100---55:跳跃游戏(贪心算法)

题目&#xff1a; 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 贪心算…

从机械尘埃到智能星河:探索从工业心脏到AI大脑的世纪跨越(一点个人感想)...

全文预计1400字左右&#xff0c;预计阅读需要8分钟。 近期&#xff0c;人工智能领域呈现出前所未有的活跃景象&#xff0c;各类创新成果如雨后春笋般涌现&#xff0c;不仅推动了科技的边界&#xff0c;也为全球经济注入了新的活力。 这不&#xff0c;最近报道16家国内外企业在A…

操作系统实验四:多线程与信号量编程

操作系统实验上机 更多技术请访问&#xff1a;www.xuanworld.top 部分审核不通过的文章将发至个人博客&#xff1a;www.xuanworld.top 欢迎来52破解论坛阅读帖子&#xff1a;https://www.52pojie.cn/thread-1891208-1-1.html 实验名称实验序号实验日期实验人多线程与信号量…

2024年电工杯数学建模竞赛A题完整解析 | 代码 论文分享

A 题 问题一1.1问题分析1.2第一问1.2.1指标定义1.2.2结果计算1.2.3关键因素分析 1.3第二问1.3.1模型建立1.3.2算法求解1.3.3求解结果 1.4第三问1.4.1模型建立1.4.2计算结果 第二题2.1 问题分析2.2第一问2.2.1指标计算 数据与代码代码分享完整资料 A题的问题一和问题二终于完成啦…

React useState基本类型变量的使用

在 React 中&#xff0c;useState 是一个 Hook&#xff0c;用于在函数组件中添加状态&#xff0c;它可以让函数组件拥有状态。基本使用方法如下&#xff1a; // App.jsx import React, { useState } from reactfunction App() {// 使用 useState 创建一个状态变量&#xff0c;初…

vulhub——ActiveMQ漏洞

文章目录 一、CVE-2015-5254(反序列化漏洞)二、CVE-2016-3088&#xff08;任意文件写入漏洞&#xff09;2.1 漏洞原理2.2 写入webshell2.3 写入crontab 三、CVE-2022-41678&#xff08;远程代码执行漏洞&#xff09;方法一方法2 四、CVE-2023-46604&#xff08;反序列化命令执行…

状压dp 例题

终于在洛谷上发布题解了QWQ P10447 最短 Hamilton 路径 题解 分析题目&#xff1a; 一张 n n n 个点的带权无向图&#xff0c;求起点 0 0 0 至终点 n − 1 n-1 n−1 的最短 Hamilton 路径&#xff08;从 0 ∼ n − 1 0\sim n-1 0∼n−1 不重复地经过每个点一次&#xff…

springboot2+mybatis-plus+vue3创建入门小项目[学生管理系统]02[实战篇]

创建一个 vue 项目 创建这个新的文件夹 创建前端项目 eggbox 数据库 SQL CREATE DATABASE IF NOT EXISTS egg DEFAULT CHARACTER SET utf8 COLLATE utf8_bin; USE egg;CREATE TABLE stu (id INT AUTO_INCREMENT, -- 自增主键name VARCHAR(64) NOT NULL, -- 非空姓名字段&a…

DataGear 制作服务端分页的数据可视化看板

DataGear 2.3.0 版本新增了附件图表数据集特性&#xff08;在新建图表时将关联的数据集设置为 附件 &#xff0c;具体参考官网文档定义图表章节&#xff09;&#xff0c;在制作看板时&#xff0c;可以基于此特性&#xff0c;结合dg-chart-listener&#xff0c;利用服务端数据扩…

HTTP 请求的完整过程

HTTP 请求的完整过程 当用户在浏览器输入网址回车之后&#xff0c;网络协议都做了哪些工作呢? 首先工作的是 浏览器应用程序&#xff0c;他要解析出 URL中的域名 根据域名获取对应的ip地址&#xff0c;首先从浏览器缓存中査看&#xff0c;如下可以査看浏览器中域名对应ip的解…

WPF中MVVM架构学习笔记

MVVM架构是一种基于数据驱动的软件开发架构&#xff0c;它将数据模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;和视图模型&#xff08;ViewModel&#xff09;三者进行分离&#xff0c;使得开发者可以更加专注于各自领域的开发。其中&#xff0c;Model负…

C++入门:从C语言到C++的过渡(2)

目录 1.缺省参数 1.1缺省参数的概念 1.2缺省参数的分类及使用 1.3注意点 2.函数重载 2.1函数重载的定义 2.2函数重载的情况分类 2.3注意 2.4函数名修饰规则 3.引用 3.1引用的概念 3.2注意事项 3.3常引用 4.4引用的使用场景 4.4.1作为函数的参数 4.4.2做函数返回…

计算机网络安全控制技术

1.防火墙技术 防火墙技术是近年来维护网络安全最重要的手段&#xff0c;但是防火墙不是万能的&#xff0c;需要配合其他安全措施来协同 2.加密技术 目前加密技术主要有两大类&#xff1a;对称加密和非对称加密 3.用户识别技术 核心是识别网络者是否是属于系统的合法用户 …

CSS基础(第二天)

Emmet语法 快速生成HTML结构语法 1. 生成标签 直接输入标签名 按tab键即可 比如 div 然后tab 键&#xff0c; 就可以生成 <div></div> 2. 如果想要生成多个相同标签 加上 * 就可以了 比如 div*3 就可以快速生成3个div 3. 如果有父子级关系的标签&#xff0c;可以…

进制转换【野路子改造】

非科班&#xff0c;一直都是自己的野路子&#xff0c;现在要回炉重造 十进制->二进制 基本思想&#xff1a; 开始写的&#xff08;80%&#xff09;&#xff1a; #include<stdio.h> using namespace std; int main(){ int n; scanf("%d",&n); int a[1…

Linux(Ubuntu)下MySQL5.7的安装

文章目录 1.看系统本身有没有MySQL2.安装MySQL3.登录MySQL4.修改配置文件my.cnf/mysqld.cnf5.开启远程访问功能5.1 允许其他主机通过root访问数据库5.2 Ubuntu下配置文件修改说明 1.看系统本身有没有MySQL mariadb也是mysql所以要先检查一下系统有没有MySQL 我这台机子是新机子…

FreeRTOS_任务通知_学习笔记

原文链接 任务通知 使用队列、信号量、事件组等等方法时&#xff0c;并不知道对方是谁。使用任务通知时&#xff0c;可以明确指定&#xff1a;通知哪个任务。 任务通知结构体中只有一个任务通知值&#xff0c;只能保持一个数据。 数据智能给目标任务独享。 任务通知只能一个…

win10右键没有默认打开方式的选项的处理方法

问题描述 搞了几个PDF书籍学习一下&#xff0c;不过我不想用默认的WPS打开&#xff0c;因为WPS太恶心人了&#xff0c;占用资源又高。我下载了个Sumatra PDF&#xff0c;这时候我像更改pdf文件默认的打开程序&#xff0c;发现右击没有这个选项。 问题解决 右击文件–属性–…