目录
- 简介
- 思路
- 实践
- 一、自定义注解@RequestMapping,@Controller
- 二、路径转全限定名方法
- 三、扫描文件夹
- 四、通过反射来寻找有@RequestMapping以及@Controller的类
- 五、获取对象实例
- 六、通过invoke调用方法
- 文件结构以及测试结果
- 1、文件结构
- 2、TestController
- 3、测试结果
简介
通过反射读取带有@RequestMapping与@Controller注解的类并模拟请求路径调用方法
思路
首先拆分问题,我们在问题中有几个需求:
- 自定义注解@RequestMapping、@Controller
- 扫描文件夹
- 通过反射读取类信息找到有@Controller和@RequestMapping注解的类
- 通过new Instance()获取对象
- 通过invoke调用方法
进一步思考:
1、@RequestMapping可以用在哪些地方?是否支持运行时检测?
类、方法
支持运行时检测
2、反射读取类信息有三种方式:
- 类名.class
- 对象.getClass()
- Class.forName()
该选用哪一种呢?
实际上在编写框架的时候,我们并不知道使用者会定义哪些类,所以我们应该通过Class.forName()来获取类信息。
而Class.forName()传递的参数是类的全限定名即:包名+类名(com.ez4sterben.entity.Student)
随之第五个需求产生,编写一个根据路径转化为全限定名的方法。
实践
一、自定义注解@RequestMapping,@Controller
根据前一部分的分析,自定义注解
package com.spring.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 控制器
*
* @author ez4sterben
* @date 2023/07/22
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
package com.spring.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 请求映射
*
* @author ez4sterben
* @date 2023/07/22
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
String value() default "";
}
二、路径转全限定名方法
/**
* 获取java路径
*
* @param file 文件
* @return {@link String}
*/
private static String getJavaPath(File file) {
return file.toString().replace("\\",".").split("src.")[1].replace(".java","");
}
三、扫描文件夹
我们通过递归实现扫描文件夹,获取其中以.java结尾的文件,调用刚才编写的方法存入List中
public static String JAVA = ".java";
public static List<String> javaFiles = new ArrayList<>();
/**
* 获取所有文件
*
* @param file 文件
*/
public static void getAllFile(File file){
if (file.isFile()){
if (file.toString().endsWith(JAVA)) {
String javaPath = getJavaPath(file);
javaFiles.add(javaPath);
}
}
if (file.exists() && file.isDirectory()) {
File[] files = file.listFiles();
if (files != null) {
for (File file1 : files) {
getAllFile(file1);
}
}
}
}
文件扫描相关的工作到这里就完成了,完整代码如下:
package com.spring.utils;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* 文件扫描
*
* @author ez4sterben
* @date 2023/07/22
*/
public class FileScannerUtil {
public static List<String> javaFiles = new ArrayList<>();
public static String JAVA = ".java";
/**
* 得到所有java文件
*
* @param path 路径
* @return {@link List}<{@link String}>
*/
public static List<String> getAllJavaFile(String path){
getAllFile(new File(path));
return javaFiles;
}
/**
* 获取所有文件
*
* @param file 文件
*/
public static void getAllFile(File file){
if (file.isFile()){
if (file.toString().endsWith(JAVA)) {
String javaPath = getJavaPath(file);
javaFiles.add(javaPath);
}
}
if (file.exists() && file.isDirectory()) {
File[] files = file.listFiles();
if (files != null) {
for (File file1 : files) {
getAllFile(file1);
}
}
}
}
/**
* 获取java路径
*
* @param file 文件
* @return {@link String}
*/
private static String getJavaPath(File file) {
return file.toString().replace("\\",".").split("src.")[1].replace(".java","");
}
}
四、通过反射来寻找有@RequestMapping以及@Controller的类
从这里开始我们就需要一个测试类来调用方法了,这里我将测试类的代码全部展示出来。
package com.spring;
import com.spring.utils.BeanUtil;
import com.spring.utils.FileScannerUtil;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
/**
* 测试
*
* @author ez4sterben
* @date 2023/07/22
*/
public class Test {
public static String PATH = "F:\\projects\\JavaSEContainer\\sterben\\src\\com\\spring\\controller";
public static String REQUEST_PATH = "/test/index";
public static void main(String[] args) {
// 模拟启动tomcat
init();
// 模拟接收请求
request(REQUEST_PATH);
}
/**
* 初始化
*/
public static void init(){
List<String> files = FileScannerUtil.getAllJavaFile(PATH);
try {
for (String file : files) {
if (BeanUtil.isControllerAndRequestMapping(file)){
BeanUtil.createBean(file);
}
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* 请求
*
* @param url url
*/
public static void request(String url){
String[] urls = url.split("/");
String controllerName = urls[1];
String methodName = urls[2];
Object bean = BeanUtil.getBean("/" + controllerName);
try {
Map<String, Method> beanMethods = BeanUtil.getBeanMethods(bean.getClass());
Method method = beanMethods.get("/" + methodName);
Object invoke = method.invoke(bean);
System.out.println(invoke);
} catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
判断是否有Controller以及RequestMapping注解,这里通过测试类中的init方法调用,遍历我们扫描文件夹得到的List判断每个全限定名是否有这两个注解。
/**
* 控制器和请求映射
*
* @param name 全限定名
* @return {@link Boolean}
* @throws ClassNotFoundException 类没有发现异常
*/
public static Boolean isControllerAndRequestMapping(String name) throws ClassNotFoundException {
Class<?> clazz = Class.forName(name);
Controller controller = clazz.getAnnotation(Controller.class);
RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
if (controller != null && requestMapping != null){
return Boolean.TRUE;
}
return Boolean.FALSE;
}
五、获取对象实例
这部分代码在测试类中的init方法里面调用,创建一个bean并且存入Map中。key是annotation中的value即我们传入的请求路径,value是实例。
public static Map<String,Object> beanMap = new HashMap<>();
/**
* 创建bean
*
* @param name 名字
* @throws ClassNotFoundException 类没有发现异常
* @throws InstantiationException 实例化异常
* @throws IllegalAccessException 非法访问异常
*/
public static void createBean(String name) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class<?> clazz = Class.forName(name);
RequestMapping annotation = clazz.getAnnotation(RequestMapping.class);
Object instance = clazz.newInstance();
beanMap.put(annotation.value(), instance);
}
六、通过invoke调用方法
在日常开发中我们都是通过请求来调用方法的,那么理所当然,我们需要列出controller中的所有method并匹配执行。(注意:这里博主写的不够好,这个方法得到的结果也应该保存在一个Map中管理,可以自行优化)
/**
* get bean方法
*
* @param clazz clazz
* @return {@link Map}<{@link String},{@link Method}>
* @throws ClassNotFoundException 类没有发现异常
*/
public static Map<String,Method> getBeanMethods(Class<?> clazz) throws ClassNotFoundException {
Map<String,Method> methodMap = new HashMap<>();
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
declaredMethod.setAccessible(true);
RequestMapping annotation = declaredMethod.getAnnotation(RequestMapping.class);
if (annotation != null){
methodMap.put(annotation.value(), declaredMethod);
}
}
return methodMap;
}
调用详见测试类的request方法。
BeanUtil详细代码:
package com.spring.utils;
import com.spring.annotation.Controller;
import com.spring.annotation.RequestMapping;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* javaBean工具类
*
* @author ez4sterben
* @date 2023/07/22
*/
public class BeanUtil {
public static Map<String,Object> beanMap = new HashMap<>();
/**
* 创建bean
*
* @param name 名字
* @throws ClassNotFoundException 类没有发现异常
* @throws InstantiationException 实例化异常
* @throws IllegalAccessException 非法访问异常
*/
public static void createBean(String name) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class<?> clazz = Class.forName(name);
RequestMapping annotation = clazz.getAnnotation(RequestMapping.class);
Object instance = clazz.newInstance();
beanMap.put(annotation.value(), instance);
}
/**
* get bean方法
*
* @param clazz clazz
* @return {@link Map}<{@link String},{@link Method}>
* @throws ClassNotFoundException 类没有发现异常
*/
public static Map<String,Method> getBeanMethods(Class<?> clazz) throws ClassNotFoundException {
Map<String,Method> methodMap = new HashMap<>();
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
declaredMethod.setAccessible(true);
RequestMapping annotation = declaredMethod.getAnnotation(RequestMapping.class);
if (annotation != null){
methodMap.put(annotation.value(), declaredMethod);
}
}
return methodMap;
}
/**
* get bean
*
* @param name 名字
* @return {@link Object}
*/
public static Object getBean(String name){
return beanMap.get(name);
}
/**
* 控制器和请求映射
*
* @param name 名字
* @return {@link Boolean}
* @throws ClassNotFoundException 类没有发现异常
*/
public static Boolean isControllerAndRequestMapping(String name) throws ClassNotFoundException {
Class<?> clazz = Class.forName(name);
Controller controller = clazz.getAnnotation(Controller.class);
RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
if (controller != null && requestMapping != null){
return Boolean.TRUE;
}
return Boolean.FALSE;
}
}
文件结构以及测试结果
1、文件结构
2、TestController
package com.spring.controller;
import com.spring.annotation.Controller;
import com.spring.annotation.RequestMapping;
/**
* 测试控制器
*
* @author ez4sterben
* @date 2023/07/22
*/
@RequestMapping("/test")
@Controller
public class TestController {
@RequestMapping("/index")
public String index(){
System.out.println("/test/index");
return "执行完了";
}
}