模拟SpringMVC的前端控制器分发请求

1. 客户端发送请求到这个DispatcherServlet前端控制器

@WebServlet(value = "/*",loadOnStartup = 1,
        initParams = @WebInitParam(name = "basePackages",value = "edu.uestc.monster.web.controller"))
public class DispatcherServlet extends HttpServlet {
}

        前端控制器接收所有的请求,由前端控制器分发到不同的servlet进行执行处理。loadOnStartup = 1表示servlet容器加载该应用时就初始化该servlet。初始化参数basePackages表示要扫描该包及其子包下所有的控制器

2. 利用servlet生命周期的初始化方法映射url和handler(处理器映射器),就是通过这个输入的url,找到对应的控制器方法,同时准备执行该方法的环境(处理器适配器)。

@Slf4j
@WebServlet(value = "/*",loadOnStartup = 1,
        initParams = @WebInitParam(name = "basePackages",value = "edu.uestc.monster.web.controller"))
public class DispatcherServlet extends HttpServlet {
    //处理器映射器(请求的url和方法的对应关系)
    private static final Map<String, Method> handlerMapping = new HashMap<>();
    //处理器适配器(为处理器-方法)准备执行环境---该方法所对应的对象实例
    private static final Map<Method,Object> instancePools = new HashMap<>();
    /**
     * 扫描控制层,得到控制层所有java文件的Class对象
     * 使用FileVisitor遍历文件和目录(非递归)
     *
     * @param basePackages 包信息,扫描该包及其子包下的java文件
     * @return 所有java文件的Class对象
     *
     */
    private List<Class<?>> packageScan(String basePackages) throws IOException, URISyntaxException {
        var classes = new ArrayList<Class<?>>();
        var root = basePackages.replace(".","\\");
        var url = Thread.currentThread().getContextClassLoader().getResource(root);
        //walkFileTree(path):遍历path路径下所有的文件和子目录
        Files.walkFileTree(Paths.get(url.toURI()),new SimpleFileVisitor<Path>(){
            @Override //访问到文件后触发该方法
            public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
                try {
                    var filePath = path.toString();
                    if (filePath.endsWith(".class")){//是一个class字节码文件,得到java文件的全限定名称
                        var className = filePath.substring(filePath.indexOf(root) ,filePath.length() - 6).replace("\\",".");
                        classes.add(Class.forName(className));
                    }
                } catch (Exception e) {
                    log.error(e.getMessage(),e);
                }
                return FileVisitResult.CONTINUE; //继续处理文件
            }
        });
        return classes;
    }

    /**
     * 处理器适配器
     *      1、处理请求uri和Method之间的映射关系
     *      2、映射Method和其对应的实例
     *
     *      路径:root  //"/"
     *          +
     *          @RequestMapping("/control/book/")
     *          +
     *          @RequestMapping("/save")
     */
    private void mappingProcess(String root,String basePackages) throws IOException, URISyntaxException {
        //1获取basePackages包下的所有的Servlet类
        var classes = packageScan(basePackages);
        for(var clazz : classes){
            //获取所有标注了@Controller注解的class
            var controller = clazz.getAnnotation(Controller.class);
            if(controller != null){//表明该class是一个控制器

                try {
                    var path = root;
                    //创建该控制器的实例
                    var instance = clazz.getDeclaredConstructor().newInstance();
                    var mapping = clazz.getAnnotation(RequestMapping.class);
                    if(mapping != null){
                        var prefix = mapping.value().startsWith("/") ? mapping.value() : "/" + mapping.value();
                        path += prefix.endsWith("/") ? prefix : prefix + "/";
                    }
                    //获取该类下所有的方法
                    var methods = clazz.getMethods();
                    for(var method : methods){
                        var annotation = method.getAnnotation(RequestMapping.class);
                        if(annotation != null){
                            //计算出url
                            var suffix = annotation.value().startsWith("/") ? annotation.value().substring(1) : annotation.value();
                            var url = path + suffix;
                            if(handlerMapping.containsKey(url)){ //重复的url
                                log.error("url冲突:" + url);
                                throw new InitException("url冲突:" + url);
                            }
                            handlerMapping.put(url,method);
                            instancePools.put(method,instance);
                        }
                    }
                } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                    log.error(e.getMessage(),e);
                }
            }
        }
    }

    /**
     * 生命周期方法,只执行一次
     */
    @Override
    public void init() throws ServletException {
        try {
            var basePackages = getInitParameter("basePackages");
            var root = getServletContext().getContextPath();
            mappingProcess(root,basePackages);
        } catch (IOException | URISyntaxException e) {
            log.error(e.getMessage(),e);
        }
    }

}

3. 通过servlet的http请求方法处理请求,会去找具体的handler方法

@Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            //获取请求的uri
            var uri = request.getRequestURI();
            var method = handlerMapping.get(uri);
            if(method != null){//执行该方法
                var instance = instancePools.get(method);
                var result = method.invoke(instance,request,response);
                if(result != null){//对返回值做出处理
                    //如果有@ResponseBody注解,直接将内容进行输出json
                    if (method.getAnnotation(ResponseBody.class) != null)
                        responseJson(response,result);
                     else
                        resolverView(request,response,result.toString()); //处理逻辑视图
                }
            } else {
                var dispatcher = uri.endsWith(".jsp") || uri.equals(".jspx") ?
                     //获取tomcat处理jsp的servlet
                    getServletContext().getNamedDispatcher("jsp") :
                    //交给tomcat的默认Servlet进行处理
                    getServletContext().getNamedDispatcher("default");//获取tomcat的默认的servlet,处理静态资源

                dispatcher.forward(request,response);
            }
        } catch (IllegalAccessException | InvocationTargetException e) {
            log.error(e.getMessage(),e);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }

4. 找到具体的controller之后,根据返回值处理不同的视图解析

/**
     * 向客户端输出json
     * @param response 响应对象
     * @param value 将该value以json格式输出
     * @throws IOException
     */
    private void responseJson(HttpServletResponse response, Object value) throws IOException {
        //response.setCharacterEncoding("UTF-8");
        var writer = response.getWriter();
        response.setContentType("application/json; charset=utf-8");
        writer.write(JSONObject.toJSONString(value));

    }

    /**
     * 解析视图
     *  规则约定:1、视图默认存放于/pages/下
     *          2、视图默认为jsp
     *          3、控制层默认以分发的形式寻找渲染视图
     *          4、重定向约定:redirect:/url
     */
    private void resolverView(HttpServletRequest request,HttpServletResponse response,String viewName) throws IOException, ServletException {
        if(viewName.startsWith("redirect:")){//需要重定向
            var view = viewName.substring("redirect:".length());
            response.sendRedirect(getServletContext().getContextPath() + view);
        } else if(viewName.startsWith("forward:")){//分发到指定的视图
            var view = viewName.substring("forward:".length());
            request.getRequestDispatcher(view).forward(request,response);
        } else {// 视图分发: 默认规则
            var prefix = "/pages/";
            var suffix = ".jsp";
            // /pages/message.jsp
            var view = prefix + viewName + suffix;
            request.getRequestDispatcher(view).forward(request,response);
        }
    }

附:InitException代码

package edu.uestc.monster.exception;

/**
 * 自定义异常
 *      前端控制器初始化异常
 */
public class InitException extends RuntimeException{
    public InitException(String message){
        super(message);
    }
}

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值