目录
1、首先实现了一个传入文件的路径,然后得到其中路径下的所有文件的功能(作为循序渐进完成功能的第一步)
2、要模拟Spring,肯定要创建注解类,模拟Controller和RequestMapping注解
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
-
自定义注解(RequestMapping上加一个括号value)
-
在 Java 中,编译时注解可以使用
@Retention
注解来控制注解的生命周期。@Retention
注解有一个属性RetentionPolicy
,它指定了注解的保留策略,可以包括三个值 -
(使用实例如图所示)
-
要实现一个类似于
@Controller
和@RequestMapping
的注解,并且使@Controller
只能在类上使用,而@RequestMapping
可以在类和方法上使用,可以按照以下步骤进行:-
定义
@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
注解限定为只能在类上使用。-
定义
@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
,它指定了注解的保留策略,可以包括三个值:
-
RetentionPolicy.SOURCE
:该注解仅存在于源代码中,编译时会被编译器忽略,不会包含在生成的.class
文件中。 -
RetentionPolicy.CLASS
:该注解会被编译器保留在生成的.class
文件中,默认值。 -
RetentionPolicy.RUNTIME
:该注解会在.class
文件中保留,并且可以在运行时通过反射获取到注解的信息。
我们想让其在运行时也能通过反射获取到这个类的信息,所以要设置成RUNTIME参数,代码示例如下(在注解类的上方加上@Retention注解并设置参数即可)
知识点2——设置这个类的作用域(作用于类、方法...)
在这里,@Target({ElementType.TYPE, ElementType.METHOD})
指定了包含两个元素类型的数组:ElementType.TYPE
和ElementType.METHOD
。它表明被注解标记的自定义注解既可以应用于类、接口或枚举类型,也可以应用于方法。
示例如下
3、将我们第一步写的扫描路径,返回该路径下的所有文件名,这一实现改写
改写后的类实现的功能是——
扫描路径下的以.java后缀作为结尾的文件,并返回它的全类名
全类名
对于这样一个目录结构,我们有一个名为src
的目录,其中包含一个名为com.example
的包。包内又包含两个类文件:StringUtil.java
和Main.java
。
StringUtil.java
的全类名是com.example.utils.StringUtil
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