【java反射】反射——包含你所想要了解的一切(初学者必看,超详细)

目录

反射

常见的获取类的三种方式

暴力反射

小源码-spring(大致的实现功能,本节实现了后三条)

任务

实现步骤

初始化步骤

具体的实现过程——具体操作在这里

1、首先实现了一个传入文件的路径,然后得到其中路径下的所有文件的功能(作为循序渐进完成功能的第一步)

2、要模拟Spring,肯定要创建注解类,模拟Controller和RequestMapping注解

知识点1

知识点2——设置这个类的作用域(作用于类、方法...)

 3、将我们第一步写的扫描路径,返回该路径下的所有文件名,这一实现改写

扫描路径下的以.java后缀作为结尾的文件,并返回它的全类名


反射

反射:通俗来说就是没见过这个类,但是对这个类的内容做出分析

常见的获取类的三种方式

  • 类名.class()

  • 对象.getClass();

  • Class.forname("xxx.xxx.xxx")——这里的字符串是全限定名 包名+类名的形式

暴力反射

        使用反射机制,我们可以获取并修改一个类的私有字段的值、调用私有方法、创建实例等。这样做的好处是,我们可以在某些特殊情况下,获得对类内部的细节和操作的完全控制。

        假设有一个叫做BankAccount的类,它有一个私有字段balance和一个私有方法withdraw

public class BankAccount {
    private double balance;

    private void withdraw(double amount) {
        if (amount <= balance) {
            balance -= amount;
            System.out.println("成功取款:" + amount);
        } else {
            System.out.println("余额不足!");
        }
    }
}

按照正常的封装原则,无法直接访问和调用私有字段和方法。但是,借助暴力反射,可以实现绕过这些限制

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        BankAccount account = new BankAccount();
       
        // 获取私有字段并修改其值
        Field balanceField = account.getClass().getDeclaredField("balance");
        balanceField.setAccessible(true);
        balanceField.set(account, 1000.0);
        
        // 调用私有方法
        Method withdrawMethod = account.getClass().getDeclaredMethod("withdraw", double.class);
        withdrawMethod.setAccessible(true);
        withdrawMethod.invoke(account, 500.0);
    }
}

        上面就是具体实例,使用反射获取了BankAccount类中的私有字段balance和私有方法withdraw的引用。通过调用setAccessible(true)来解除对它们的访问限制,并修改了字段的值,并调用了私有方法。 

反射原理图

输出

输出

结果

结果

动态代理

小源码-spring(大致的实现功能,本节实现了后三条)

  • 接受网络请求

  • 使用线程池

  • 自定义注解

  • 根据自定义注解去创建对象

  • 方法执行调用

任务

https://www.bilibili.com/video/BV11M4y1s7KR/?vd_source=01869a74cea5a0261f82fcae7d1d0026

7-23 反射、暴力反射_哔哩哔哩_bilibili

  • 自定义注解(RequestMapping上加一个括号value)

    • 在 Java 中,编译时注解可以使用 @Retention 注解来控制注解的生命周期。@Retention 注解有一个属性 RetentionPolicy,它指定了注解的保留策略,可以包括三个值

    •  (使用实例如图所示)

    • 要实现一个类似于 @Controller@RequestMapping 的注解,并且使 @Controller 只能在类上使用,而 @RequestMapping 可以在类和方法上使用,可以按照以下步骤进行:

      1. 定义 @Controller 注解:

      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
      ​
      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.TYPE)
      public @interface Controller {
          // ...
      }

      在上述示例中,通过指定 @Target(ElementType.TYPE),将 @Controller 注解限定为只能在类上使用。

      1. 定义 @RequestMapping 注解:

      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
      ​
      @Retention(RetentionPolicy.RUNTIME)
      @Target({ElementType.TYPE, ElementType.METHOD})
      public @interface RequestMapping {
          String value();
      }

      在上述示例中,通过指定 @Target({ElementType.TYPE, ElementType.METHOD}),将 @RequestMapping 注解限定为可以在类和方法上使用。

      这样就可以使用 @Controller 注解来标识类,并使用 @RequestMapping 注解来定义类和方法的映射路径。例如:

      @Controller
      @RequestMapping("/example")
      public class ExampleController {
      ​
          @RequestMapping("/hello")
          public void hello() {
              // ...
          }
      }

      请注意,示例中的注解只是简单地模拟了 @Controller@RequestMapping 注解的功能。

  • 识别到自定义注解

    • 如何识别

    • 我们采用注解的形式

  • 通过反射的形式创建实例

  • 通过exec方法调用实例,执行实例中的方法

实现步骤

初始化步骤

  • 通过扫描某路径下的文件夹和文件,通过字符串处理后得到全限定名

    • 传入地址 d:test/abc/

    • 输出List<String>,返回的是里边的所有文件

  • 反射创建对象

    • url->对象 Map<String,Object>

  • 识别是否有我们所需的注解,并获取注解内的值

  • 需要构建,每个类、每个方法对应url的映射结构

具体的实现过程——具体操作在这里

1、首先实现了一个传入文件的路径,然后得到其中路径下的所有文件的功能(作为循序渐进完成功能的第一步)

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class Scanner {
    /*
     * 递归扫描指定目录下的所有文件,并将文件名添加到列表中
     * @param directoryPath 目录路径
     * @return 文件名列表
     */
    public static List<String> scanFiles(String directoryPath) {
        // 创建存储文件名的列表
        List<String> fileNames = new ArrayList<>();
        
        // 创建目录对象
        File directory = new File(directoryPath);
        
        // 调用递归方法扫描文件
        scanFiles(directory, fileNames);
        
        // 返回文件名列表
        return fileNames;
    }

    /*
     * 递归扫描文件夹及其子文件夹下的所有文件
     * @param directory 目录对象
     * @param fileNames 文件名列表
     */
    private static void scanFiles(File directory, List<String> fileNames) {
        // 判断是否为目录
        if (directory.isDirectory()) {
            // 获取目录下的所有文件和文件夹
            File[] files = directory.listFiles();
            
            // 如果文件列表不为空
            if (files != null) {
                // 遍历文件列表
                for (File file : files) {
                    // 递归调用该方法,继续扫描文件
                    scanFiles(file, fileNames);
                }
            }
        } 
        // 如果是文件
        else if (directory.isFile()) {
            // 获取文件的绝对路径并添加到文件名列表中
            String fileName = directory.getAbsolutePath();
            fileNames.add(fileName);
        }
    }

    /*
     * 主方法,用于测试
     */
    public static void main(String[] args) {
        // 指定目录路径
        String directoryPath = "F:\\bin";
        
        // 调用scanFiles方法扫描文件并获取文件名列表
        List<String> fileNames = scanFiles(directoryPath);
        
        // 遍历文件名列表并打印文件名
        for (String fileName : fileNames) {
            System.out.println(fileName);
        }
    }
}

        以上代码实现了,首先定义了第一个ScanFiles方法传入的是要搜索文件的路径,然后创建了一个列表,然后根据目录去创建了目录对象,接下来将目录的对象和新创建的列表作为参数传给了重载的scanFiles方法,传入之后获取该目录下的所有文件或文件夹,然后如果是目录的话就继续遍历文件的列表,递归调用第二个重载后的scanFiles方法;如果是文件的话就将其加入列表当中。最后第一个scanFiles返回的类型就是这个列表。

2、要模拟Spring,肯定要创建注解类,模拟Controller和RequestMapping注解

知识点1

        有些编写的注解,只对.java文件生效,有时候编译成.class文件的时候注解就不起作用了

这时需要用到的是@Retention 注解来控制注解的生命周期。

        @Retention 注解有一个属性 RetentionPolicy,它指定了注解的保留策略,可以包括三个值:

  1. RetentionPolicy.SOURCE:该注解仅存在于源代码中,编译时会被编译器忽略,不会包含在生成的 .class 文件中。

  2. RetentionPolicy.CLASS:该注解会被编译器保留在生成的 .class 文件中,默认值。

  3. RetentionPolicy.RUNTIME:该注解会在 .class 文件中保留,并且可以在运行时通过反射获取到注解的信息。

我们想让其在运行时也能通过反射获取到这个类的信息,所以要设置成RUNTIME参数,代码示例如下(在注解类的上方加上@Retention注解并设置参数即可)

知识点2——设置这个类的作用域(作用于类、方法...)

        在这里,@Target({ElementType.TYPE, ElementType.METHOD})指定了包含两个元素类型的数组:ElementType.TYPEElementType.METHOD。它表明被注解标记的自定义注解既可以应用于类、接口或枚举类型,也可以应用于方法。

        示例如下

 3、将我们第一步写的扫描路径,返回该路径下的所有文件名,这一实现改写

改写后的类实现的功能是——

扫描路径下的以.java后缀作为结尾的文件,并返回它的全类名

全类名

对于这样一个目录结构,我们有一个名为src的目录,其中包含一个名为com.example的包。包内又包含两个类文件:StringUtil.javaMain.java

  1. StringUtil.java的全类名是com.example.utils.StringUtil
  2. Main.java的全类名是com.example.Main

全类名由包名和类名组成,用于唯一标识一个类在Java程序中的位置。通俗点说就是从src的下一级开始,逐步顺到所要的那个文件。逐级按照包名的层次结构,从src目录开始,可以准确地找到目标类文件。

功能实现代码:

    public static List<String> scanFiles(String directoryPath) {
        List<String> classNames = new ArrayList<>();
        File directory = new File(directoryPath);
        scanFiles(directory, "", classNames);
        return classNames;
    }

    private static void scanFiles(File directory, String packageName, List<String> classNames) {
        if (directory.isDirectory()) {
            File[] files = directory.listFiles();
            if (files != null) {
                for (File file : files) {
                    if (file.isDirectory()) {
                        String subPackageName = packageName + file.getName() + ".";
                        scanFiles(file, subPackageName, classNames);
                    } else if (file.getName().endsWith(".java")) {
                        String className = packageName + file.getName().replace(".java", "");
                        classNames.add(className);
                    }
                }
            }
        }
    }

        将扫描完成获取的全类名的列表拿到之后,逐步遍历,获取到每个全类名然后利用全类名去获取到Class,之后创造类的实例并判断是否有Controller注解和RequestMapping注解,如果有则获取类上的@RequestMapping注解的value作为url,然后将 url 和类的实例存储到 controllerMap (public static Map<String, Object> controllerMap = new HashMap<>();)中。

代码实现:

    // 查找所有controller,并创建对象装入Map里(“url”:Object)
    private static void chooseController(List<String> fileNames)
            throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
            InvocationTargetException, InstantiationException {
        System.out.println("接收到的filename是\n" + fileNames);
        for (String fileName : fileNames) {
            // 通过完整类名获取 Class 对象
            Class<?> aClass = Class.forName(fileName);

            // 检查类是否带有 @Controller 注解
            if (aClass.isAnnotationPresent(Controller.class)) {
                Controller controller=aClass.getDeclaredAnnotation(Controller.class);
                System.out.println(controller);
                // 创建类的实例
                Object o = aClass.getDeclaredConstructor().newInstance();
                // 获取类上的 @RequestMapping 注解的 value 值
                System.out.println("******"+aClass.getDeclaredAnnotation(RequestMapping.class));
                RequestMapping[] requestMappings = aClass.getDeclaredAnnotationsByType(RequestMapping.class);
                System.out.println("Mapping是"+ Arrays.toString(requestMappings));
                if (requestMappings.length > 0) {
                    String url = requestMappings[0].value();
                    // 将 url 和类的实例存储到 controllerMap 中
                    controllerMap.put(url, o);
                }
            }
        }
    }
本节内容对应的Gitee网址SpringLike: 模拟Spring框架中的一些功能 - Gitee.com

更标准的答案heaboy/仿照springmvc

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值