【仿写spring之mvc篇】一、通过反射读取带有@RequestMapping与@Controller注解的类并模拟请求路径调用方法

简介

通过反射读取带有@RequestMapping与@Controller注解的类并模拟请求路径调用方法

思路

首先拆分问题,我们在问题中有几个需求:

  1. 自定义注解@RequestMapping、@Controller
  2. 扫描文件夹
  3. 通过反射读取类信息找到有@Controller和@RequestMapping注解的类
  4. 通过new Instance()获取对象
  5. 通过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 "执行完了";
    }
}

3、测试结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值