欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

荐 从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---11. MVC功能之http请求处理器的编写---简易框架最后一公里!

程序员文章站 2022-04-15 23:24:07
1. 本章需要完成的内容:完成ControllerRequestProcessor类的编写: Controller请求处理器完成JspRequestProcessor类的编写:jsp资源请求处理完成PreRequestProcessor类的编写: 请求预处理,包括编码以及路径处理完成StaticResourceRequestProcessor类的编写: 静态资源请求处理,包括但不限于图片,css,以及js文件等, 转发到DefaultServlet2. PreRequestProcess...

从零写一个具有IOC-AOP-MVC功能的框架—学习笔记 专栏往期文章链接:

  1. IOC功能相关章节:
    从零写一个具有IOC-AOP-MVC功能的框架—学习笔记—01.项目初始化
    从零写一个具有IOC-AOP-MVC功能的框架—学习笔记—02.IOC核心标签创建
    从零写一个具有IOC-AOP-MVC功能的框架—学习笔记—03.获取指定包下的类—ClassUtil工具类的编写
    从零写一个具有IOC-AOP-MVC功能的框架—学习笔记—04.容器的创建以及容器成员的增删查改
    从零写一个具有IOC-AOP-MVC功能的框架—学习笔记—05. 实现容器的依赖注入
  2. AOP功能相关章节:
    从零写一个具有IOC-AOP-MVC功能的框架—学习笔记—06. AOP前置工作准备以及实现逻辑分析
    从零写一个具有IOC-AOP-MVC功能的框架—学习笔记—07. AOP功能实现以及讲解
    从零写一个具有IOC-AOP-MVC功能的框架—学习笔记—08.框架的AOP功能和IOC功能测试
  3. MVC功能相关章节:
    从零写一个具有IOC-AOP-MVC功能的框架—学习笔记—09. MVC访问流程简介及部分类的编写
    从零写一个具有IOC-AOP-MVC功能的框架—学习笔记—10. MVC 结果渲染器的编写
    从零写一个具有IOC-AOP-MVC功能的框架—学习笔记—11. MVC功能之http请求处理器的编写—简易框架最后一公里!
  4. 学习笔记总结:
    从零写一个具有IOC-AOP-MVC功能的框架—学习笔记—12.Hello Framewok框架初步使用介绍(测试)+未来计划展望

1. 本章需要完成的内容:

  1. 完成ControllerRequestProcessor类的编写: Controller请求处理器
  2. 完成JspRequestProcessor类的编写:jsp资源请求处理
  3. 完成PreRequestProcessor类的编写: 请求预处理,包括编码以及路径处理
  4. 完成StaticResourceRequestProcessor类的编写: 静态资源请求处理,包括但不限于图片,css,以及js文件等, 转发到DefaultServlet

2. PreRequestProcessor类的编写

2.1 PreRequestProcessor类需要完成的代码


package org.myframework.mvc.processor.impl;

/**
 * @author *ccc
 * @date 2020/6/16 18:54
 * 岂曰无衣,与子同袍~
 */

import lombok.extern.slf4j.Slf4j;
import org.myframework.mvc.RequestProcessorChain;
import org.myframework.mvc.processor.RequestProcessor;

/**
 * 请求预处理,包括编码以及路径处理
 */
@Slf4j
public class PreRequestProcessor implements RequestProcessor {

    @Override
    public boolean process(RequestProcessorChain requestProcessorChain) throws Exception {

        // 1.设置请求编码,将其统一设置成UTF-8
        requestProcessorChain.getRequest().setCharacterEncoding("UTF-8");
        // 2.将请求路径末尾的/剔除,为后续匹配Controller请求路径做准备
        // (一般Controller的处理路径是/aaa/bbb,所以如果传入的路径结尾是/aaa/bbb/,
        // 就需要处理成/aaa/bbb)
        String requestPath = requestProcessorChain.getRequestPath();
        //http://localhost:8080/myframework requestPath="/"
        if (requestPath.length() > 1 && requestPath.endsWith("/")) {
            requestProcessorChain.setRequestPath(requestPath.substring(0, requestPath.length() - 1));
        }
        log.info("preprocess request {} {}", requestProcessorChain.getRequestMethod(), requestProcessorChain.getRequestPath());

        return true;
    }
}

2.2 PreRequestProcessor类相关代码讲解:

荐
                                                        从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---11. MVC功能之http请求处理器的编写---简易框架最后一公里!

3. StaticResourceRequestProcessor类的编写

3.1 StaticResourceRequestProcessor类需要完成的代码:

package com.*ccc.helloframework.mvc.processor.impl;

import com.*ccc.helloframework.mvc.RequestProcessorChain;
import com.*ccc.helloframework.mvc.processor.RequestProcessor;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;

/**
 * @author *ccc
 * @date 2020/7/14 22:40
 * 岂曰无衣,与子同袍~
 */
@Slf4j
public class StaticResourceRequestProcessor implements RequestProcessor {


    public static final String DEFAULT_TOMCAT_SERVLET = "default";
    public static final String STATIC_RESOURCE_PREFIX = "/static/";

    //tomcat默认请求派发器RequestDispatcher的名称
    RequestDispatcher defaultDispatcher;

    public StaticResourceRequestProcessor(ServletContext servletContext) {
        this.defaultDispatcher = servletContext.getNamedDispatcher(DEFAULT_TOMCAT_SERVLET);
        if (this.defaultDispatcher == null) {
            throw new RuntimeException("There is no default tomcat servlet");
        }
        log.info("The default servlet for static resource is {}", DEFAULT_TOMCAT_SERVLET);
    }

    @Override
    public boolean process(RequestProcessorChain requestProcessorChain) throws Exception {

        //1.通过请求路径判断是否是请求的静态资源 webapp/static
        if (isStaticResource(requestProcessorChain.getRequestPath())) {
            //2.如果是静态资源,则将请求转发给default servlet处理
            defaultDispatcher.forward(requestProcessorChain.getRequest(), requestProcessorChain.getResponse());
            return false;
        }
        return true;
    }

    //通过请求路径前缀(目录)是否为静态资源 /static/
    private boolean isStaticResource(String path) {
        return path.startsWith(STATIC_RESOURCE_PREFIX);
    }

}

3.2 StaticResourceRequestProcessor类相关代码讲解:

荐
                                                        从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---11. MVC功能之http请求处理器的编写---简易框架最后一公里!

4. JspRequestProcessor

4.1 JspRequestProcessor需要完成的代码:

package org.myframework.mvc.processor.impl;

import org.myframework.mvc.RequestProcessorChain;
import org.myframework.mvc.processor.RequestProcessor;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;

/**
 * @author *ccc
 * @date 2020/6/16 18:59
 * 岂曰无衣,与子同袍~
 */

/**
 * jsp资源请求处理
 */
public class JspRequestProcessor implements RequestProcessor {


    //jsp请求的RequestDispatcher的名称
    private static final String JSP_SERVLET = "jsp";
    //Jsp请求资源路径前缀
    private static final String JSP_RESOURCE_PREFIX = "/templates/";

    /**
     * jsp的RequestDispatcher,处理jsp资源
     */
    private RequestDispatcher jspServlet;

    public JspRequestProcessor(ServletContext servletContext) {
        jspServlet = servletContext.getNamedDispatcher(JSP_SERVLET);
        if (null == jspServlet) {
            throw new RuntimeException("there is no jsp servlet");
        }
    }

    @Override
    public boolean process(RequestProcessorChain requestProcessorChain) throws Exception {
        if (isJspResource(requestProcessorChain.getRequestPath())) {
            jspServlet.forward(requestProcessorChain.getRequest(), requestProcessorChain.getResponse());
            return false;
        }
        return true;
    }

    /**
     * 是否请求的是jsp资源
     */
    private boolean isJspResource(String url) {
        return url.startsWith(JSP_RESOURCE_PREFIX);
    }
}

4.2 JspRequestProcessor相关代码讲解:

荐
                                                        从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---11. MVC功能之http请求处理器的编写---简易框架最后一公里!

5. ControllerRequestProcessor类的编写

5.1 ControllerRequestProcessor需要完成的代码:

package org.myframework.mvc.processor.impl;

import lombok.extern.slf4j.Slf4j;
import org.myframework.core.BeanContainer;
import org.myframework.mvc.RequestProcessorChain;
import org.myframework.mvc.annotation.RequestMapping;
import org.myframework.mvc.annotation.RequestParam;
import org.myframework.mvc.annotation.ResponseBody;
import org.myframework.mvc.processor.RequestProcessor;
import org.myframework.mvc.render.JsonResultRender;
import org.myframework.mvc.render.ResourceNotFoundResultRender;
import org.myframework.mvc.render.ResultRender;
import org.myframework.mvc.render.ViewResultRender;
import org.myframework.mvc.type.ControllerMethod;
import org.myframework.mvc.type.RequestPathInfo;
import org.myframework.util.ConverterUtil;
import org.myframework.util.ValidationUtil;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author *ccc
 * @date 2020/6/16 19:14
 * 岂曰无衣,与子同袍~
 */

/**
 * Controller请求处理器
 */
@Slf4j
public class ControllerRequestProcessor implements RequestProcessor {

    //IOC容器
    private BeanContainer beanContainer;

    //请求和controller方法的映射集合
    private Map<RequestPathInfo, ControllerMethod> pathControllerMethodMap = new ConcurrentHashMap<>();


    /**
     * 依靠容器的能力,建立起请求路径、请求方法与Controller方法实例的映射
     */
    public ControllerRequestProcessor() {
        this.beanContainer = BeanContainer.getInstance();
        Set<Class<?>> requestMappingSet = beanContainer.getClassesByAnnotation(RequestMapping.class);
        initPathControllerMethodMap(requestMappingSet);
    }

    private void initPathControllerMethodMap(Set<Class<?>> requestMappingSet) {

        if (ValidationUtil.isEmpty(requestMappingSet)) {
            return;
        }

        //1.遍历所有被@RequestMapping标记的类,获取类上面该注解的属性值作为一级路径
        for (Class<?> requestMappingClass : requestMappingSet) {

            RequestMapping requestMapping = requestMappingClass.getAnnotation(RequestMapping.class);
            String basePath = requestMapping.value();
            if (!basePath.startsWith("/")) {
                basePath = "/" + basePath;
            }

            //2.遍历类里所有被@RequestMapping标记的方法,获取方法上面该注解的属性值,作为二级路径
            Method[] methods = requestMappingClass.getDeclaredMethods();
            if (ValidationUtil.isEmpty(methods)) {
                continue;
            }

            for (Method method : methods) {

                if (method.isAnnotationPresent(RequestMapping.class)) {

                    RequestMapping methodRequest = method.getAnnotation(RequestMapping.class);
                    String methodPath = methodRequest.value();

                    if (!methodPath.startsWith("/")) {
                        methodPath = "/" + basePath;
                    }


                    String url = basePath + methodPath;

                    //3.解析方法里被@RequestParam标记的参数,
                    // 获取该注解的属性值,作为参数名,
                    // 获取被标记的参数的数据类型,建立参数名和参数类型的映射
                    Map<String, Class<?>> methodParams = new HashMap<>();

                    Parameter[] parameters = method.getParameters();

                    if (!ValidationUtil.isEmpty(parameters)) {

                        for (Parameter parameter : parameters) {

                            RequestParam param = parameter.getAnnotation(RequestParam.class);

                            //目前暂定为Controller方法里面所有的参数都需要@RequestParam注解
                            if (param == null) {
                                throw new RuntimeException("The parameter must have @RequestParam");
                            }

                            methodParams.put(param.value(), parameter.getType());
                        }
                    }

                    //4.将获取到的信息封装成RequestPathInfo实例和ControllerMethod实例,放置到映射表里
                    String httpMethod = String.valueOf(methodRequest.method());

                    RequestPathInfo requestPathInfo = new RequestPathInfo(httpMethod, url);

                    if (this.pathControllerMethodMap.containsKey(requestPathInfo)) {
                        log.warn("duplicate url:{} registration,current class {} method{} will override the former one",
                                requestPathInfo.getHttpPath(), requestMappingClass.getName(), method.getName());
                    }

                    ControllerMethod controllerMethod = new ControllerMethod(requestMappingClass, method, methodParams);

                    this.pathControllerMethodMap.put(requestPathInfo, controllerMethod);
                }
            }
        }

    }

    @Override
    public boolean process(RequestProcessorChain requestProcessorChain) throws Exception {

        //1.解析HttpServletRequest的请求方法,请求路径,获取对应的ControllerMethod实例
        String method = requestProcessorChain.getRequestMethod();
        String path = requestProcessorChain.getRequestPath();

        ControllerMethod controllerMethod = this.pathControllerMethodMap.get(new RequestPathInfo(method, path));

        if (controllerMethod == null) {
            requestProcessorChain.setResultRender(new ResourceNotFoundResultRender(method, path));
            return false;
        }

        //2.解析请求参数,并传递给获取到的ControllerMethod实例去执行
        Object result = invokeControllerMethod(controllerMethod, requestProcessorChain.getRequest());

        //3.根据处理的结果,选择对应的render进行渲染
        setResultRender(result, controllerMethod, requestProcessorChain);

        return true;
    }


    /**
     * 根据不同情况设置不同的渲染器
     */
    private void setResultRender(Object result, ControllerMethod controllerMethod, RequestProcessorChain requestProcessorChain) {

        if (result == null) {
            return;
        }

        ResultRender resultRender;

        boolean isJson = controllerMethod.getInvokeMethod().isAnnotationPresent(ResponseBody.class);
        if (isJson) {
            resultRender = new JsonResultRender(result);
        } else {
            resultRender = new ViewResultRender(result);
        }

        requestProcessorChain.setResultRender(resultRender);
    }

    private Object invokeControllerMethod(ControllerMethod controllerMethod, HttpServletRequest request) {

        //1.从请求里获取GET或者POST的参数名及其对应的值
        Map<String, String> requestParamMap = new HashMap<>();

        //GET,POST方法的请求参数获取方式
        Map<String, String[]> parameterMap = request.getParameterMap();

        for (Map.Entry<String, String[]> parameter : parameterMap.entrySet()) {

            if (!ValidationUtil.isEmpty(parameter.getValue())) {
                //只支持一个参数对应一个值的形式
                requestParamMap.put(parameter.getKey(), parameter.getValue()[0]);
            }

        }

        //2.根据获取到的请求参数名及其对应的值,以及controllerMethod里面的参数和类型的映射关系,去实例化出方法对应的参数
        List<Object> methodParams = new ArrayList<>();

        Map<String, Class<?>> methodParamMap = controllerMethod.getMethodParameters();


        for (String paramName : methodParamMap.keySet()) {

            Class<?> type = methodParamMap.get(paramName);
            String requestValue = requestParamMap.get(paramName);
            Object value;

            //只支持String 以及基础类型char,int,short,byte,double,long,float,boolean,及它们的包装类型
            if (requestValue == null) {
                //将请求里的参数值转成适配于参数类型的空值
                value = ConverterUtil.primitiveNull(type);
            } else {
                value = ConverterUtil.convert(type, requestValue);
            }
            methodParams.add(value);

        }

        //3.执行Controller里面对应的方法并返回结果
        Object controller = beanContainer.getBean(controllerMethod.getControllerClass());
        Method invokeMethod = controllerMethod.getInvokeMethod();
        invokeMethod.setAccessible(true);
        Object result;

        try {

            if (methodParams.size() == 0) {
                result = invokeMethod.invoke(controller);
            } else {
                result = invokeMethod.invoke(controller, methodParams.toArray());
            }

        } catch (InvocationTargetException e) {
            //如果是调用异常的话,需要通过e.getTargetException()
            // 去获取执行方法抛出的异常
            throw new RuntimeException(e.getTargetException());
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }

        return result;
    }
}

5.2 ControllerRequestProcessor相关代码讲解

荐
                                                        从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---11. MVC功能之http请求处理器的编写---简易框架最后一公里!

荐
                                                        从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---11. MVC功能之http请求处理器的编写---简易框架最后一公里!

荐
                                                        从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---11. MVC功能之http请求处理器的编写---简易框架最后一公里!
荐
                                                        从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---11. MVC功能之http请求处理器的编写---简易框架最后一公里!

荐
                                                        从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---11. MVC功能之http请求处理器的编写---简易框架最后一公里!

荐
                                                        从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---11. MVC功能之http请求处理器的编写---简易框架最后一公里!

荐
                                                        从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---11. MVC功能之http请求处理器的编写---简易框架最后一公里!

荐
                                                        从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---11. MVC功能之http请求处理器的编写---简易框架最后一公里!

6. ConverterUtil值转换工具类的补充

荐
                                                        从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---11. MVC功能之http请求处理器的编写---简易框架最后一公里!
相关代码如下:

package com.*ccc.helloframework.util;

/**
 * @author *ccc
 * @date 2020/7/15 8:02
 * 岂曰无衣,与子同袍~
 */
public class ConverterUtil {

    /**
     * 返回基本数据类型的空值
     * 需要特殊处理的基本类型即int\double\short\long\byte\float\boolean
     *
     * @param type 参数类型
     * @return 对应的空值
     */
    public static Object primitiveNull(Class<?> type) {

        if (type == int.class || type == double.class || type == short.class || type == long.class || type == byte.class || type == float.class) {
            return 0;
        } else if (type == boolean.class) {
            return false;
        }
        return null;
    }


    /**
     * String类型转换成对应的参数类型
     *
     * @param type         参数类型
     * @param requestValue 值
     * @return 转换后的Object
     */
    public static Object convert(Class<?> type, String requestValue) {

        if (isPrimitive(type)) {

            if (ValidationUtil.isEmpty(requestValue)) {
                return primitiveNull(type);
            }

            if (type.equals(int.class) || type.equals(Integer.class)) {

                return Integer.parseInt(requestValue);
            } else if (type.equals(String.class)) {

                return requestValue;
            } else if (type.equals(Double.class) || type.equals(double.class)) {

                return Double.parseDouble(requestValue);
            } else if (type.equals(Float.class) || type.equals(float.class)) {

                return Float.parseFloat(requestValue);
            } else if (type.equals(Long.class) || type.equals(long.class)) {

                return Long.parseLong(requestValue);
            } else if (type.equals(Boolean.class) || type.equals(boolean.class)) {

                return Boolean.parseBoolean(requestValue);
            } else if (type.equals(Short.class) || type.equals(short.class)) {

                return Short.parseShort(requestValue);
            } else if (type.equals(Byte.class) || type.equals(byte.class)) {

                return Byte.parseByte(requestValue);
            }

            return requestValue;
        } else {
            throw new RuntimeException("count not support non primitive type conversion yet");
        }

    }


    /**
     * 判定是否基本数据类型(包括包装类以及String)
     *
     * @param type 参数类型
     * @return 是否为基本数据类型
     */
    private static boolean isPrimitive(Class<?> type) {
        return type == boolean.class
                || type == Boolean.class
                || type == double.class
                || type == Double.class
                || type == float.class
                || type == Float.class
                || type == short.class
                || type == Short.class
                || type == int.class
                || type == Integer.class
                || type == long.class
                || type == Long.class
                || type == String.class
                || type == byte.class
                || type == Byte.class
                || type == char.class
                || type == Character.class;
    }
}

github地址:https://github.com/*ccc/helloframework/

本文地址:https://blog.csdn.net/qq_27148729/article/details/107368252