用 300 行代码手写提炼 Spring 核心原理 [3]

系列文章

上文 中我们实现了 mini-spring 的 2.0 版本,基本功能已经实现,但依然还有很多工作要做,例如 HandlerMapping 还不能像 SpringMVC 一样支持正则,url 参数还不支持类型转换,在 3.0 版本中我们将继续优化。

初始化阶段

HandlerMapping

首先改造 HandlerMapping,在真实的 Spring 源码中,HandlerMapping 其实是一个 List 而非 Map,List 中的元素是自定义类型的。现在我们来仿真写一段代码,先定义一个内部类 Handler:

/**
 * 内部类,记录Controller中RequestMapping和Method的对应关系
 */
private class Handler {

    protected Object controller;	// 保存方法对应的实例
    protected Method method;		// 保存映射的方法
    protected Pattern pattern;		// url pattern
    protected Map<String, Integer> paramIndexMapping;	// 保存参数名-参数index的映射

    /**
	 * 构造方法
	 * @param pattern
	 * @param controller
	 * @param method
	 */
    protected Handler(Pattern pattern, Object controller, Method method) {
        this.pattern = pattern;
        this.controller = controller;
        this.method = method;
        paramIndexMapping = new HashMap<>();
        putParamIndexMapping(method);
    }

    private void putParamIndexMapping(Method method) {
        // 提取方法中加了注解的参数
        Annotation[][] pa = method.getParameterAnnotations();
        for (int i = 0; i < pa.length; i++) {
            for (Annotation a: pa[i]) {
                if (a instanceof MyRequestParam) {
                    String paramName = ((MyRequestParam) a).value().trim();
                    if (!"".equals(paramName)) {
                        paramIndexMapping.put(paramName, i);
                    }
                }
            }
        }
        // 提取方法中的request和response参数
        Class<?>[] paramsTypes = method.getParameterTypes();
        for (int i = 0; i < paramsTypes.length; i++) {
            Class<?> type = paramsTypes[i];
            if (type == HttpServletRequest.class || type == HttpServletResponse.class) {
                paramIndexMapping.put(type.getName(), i);
            }
        }
    }
}

然后优化 HandlerMapping 的结构:

// 保存url -> method的映射关系
private List<Handler> handlerMapping = new ArrayList<>();

修改 initHandlerMapping() 方法:

/**
 * 初始化HandlerMapping
 * 
 * 本例中handlerMapping中的内容是:
 * /demo/query = org.example.minispring.action.DemoAction.query(HttpServletRequest, HttpServletResponse, String)
 */
private void initHandlerMapping() {
    if (IoC.isEmpty())
        return;

    for (Entry<String, Object> entry : IoC.entrySet()) {
        Class<?> clazz = entry.getValue().getClass();
        if (!clazz.isAnnotationPresent(MyController.class))
            continue;

        String baseUrl = "";
        if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
            MyRequestMapping requestMapping = clazz.getAnnotation(MyRequestMapping.class);
            baseUrl = requestMapping.value();
        }

        // 解析@MyController中方法上的@MyRequestMapping注解
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            if (!method.isAnnotationPresent(MyRequestMapping.class)) {
                continue;
            }
            MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);				
            // 组合方法签名上的完整url,正则替换是为防止路径中出现多个连续多个"/"的不规范写法
            String regex = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
            Pattern pattern = Pattern.compile(regex);
            // 保存url -> method的对应关系
            handlerMapping.add(new Handler(pattern, entry.getValue(), method));
            System.out.println("Mapped " + regex + " -> " + method);
        }
    }
}

修改 doDispatch() 方法,为了支持 url 的正则匹配和 url 参数的类型转换,我们新增了两个方法 getHandler() 和 convert():

private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    Handler handler = getHandler(req);
    if (handler == null) {
        resp.getWriter().write("404 Not Found!");
        return;
    }

    // 获得方法的形参列表
    Class<?>[] paramTypes = handler.method.getParameterTypes();
    Object[] paramValues = new Object[paramTypes.length];

    // 从req中接收到的参数是Map类型,需要按照handler的method中的参数顺序传递
    // handler.paramIndexMapping就发挥作用了:按照参数名称映射到正确的index位置
    // 保证了paramValues中参数按顺序正确传递给method
    Map<String, String[]> params = req.getParameterMap();
    for (Map.Entry<String, String[]> entry : params.entrySet()) {
        String value = Arrays.toString(entry.getValue()).replaceAll("\\[|\\]", "").replaceAll("\\s", ",");
        if (!handler.paramIndexMapping.containsKey(entry.getKey()))
            continue;

        int index = handler.paramIndexMapping.get(entry.getKey());
        paramValues[index] = convert(paramTypes[index], value);
    }

    if (handler.paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
        int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
        paramValues[reqIndex] = req;
    }

    if (handler.paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
        int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
        paramValues[respIndex] = resp;
    }

    Object returnValue = handler.method.invoke(handler.controller, paramValues);
    if (returnValue == null || returnValue instanceof Void)
        return;
    resp.getWriter().write(returnValue.toString());
}

/**
 * 根据request的url找到匹配的handler
 */
private Handler getHandler(HttpServletRequest req) {
    if (handlerMapping.isEmpty())
        return null;

    String url = req.getRequestURI();
    String contextPath = req.getContextPath();
    url = url.replace(contextPath, "").replaceAll("/+", "/");
    for (Handler handler : handlerMapping) {
        Matcher matcher = handler.pattern.matcher(url);
        // 没有匹配上,继续匹配下一个
        if (!matcher.matches())
            continue;
        return handler;
    }
    return null;
}

/*
 * url传过来的参数都是String类型的,只需要把String转换为任意类型
 */
private Object convert(Class<?> type, String value) {
    if (Integer.class == type) {
        return Integer.valueOf(value);
    }
    // 如果还有double或者其他类型的参数,继续添加if
    return value;
}

运行阶段

至此,mini-spring 的 3.0 版本就已经完成了。运行效果演示:

参考

[1] 《Spring 5 核心原理与 30 个类手写实战》,谭勇德著。

posted @ 2024-07-22 07:35  watermark's  阅读(12)  评论(0编辑  收藏  举报