资讯专栏INFORMATION COLUMN

从零开始实现一个简易的Java MVC框架(九)--优化MVC代码

ruicbAndroid / 1642人阅读

摘要:前言在从零开始实现一个简易的框架七实现中实现了框架的的功能,不过最后指出代码的逻辑不是很好,在这一章节就将这一部分代码进行优化。

前言

在从零开始实现一个简易的Java MVC框架(七)--实现MVC中实现了doodle框架的MVC的功能,不过最后指出代码的逻辑不是很好,在这一章节就将这一部分代码进行优化。

优化的目标是1.去除DispatcherServlet请求分发器中的http逻辑代码;2.将ControllerHandlerResultRender中代码按功能细分出来,使其各司其职。

修改DispatcherServlet 创建接口

先在com.zbw.mvc包下创建两个包handler和render,分别用于放ControllerHandlerResultRender拆分出来的功能类。

再在这两个包下创建两个接口,以便后面的功能类都按这个接口规范。

package com.zbw.mvc.handler;

import com.zbw.mvc.RequestHandlerChain;

/**
 * 请求执行器 Handler
 */
public interface Handler {
    /**
     * 请求的执行器
     */
    boolean handle(final RequestHandlerChain handlerChain) throws Exception;
}
package com.zbw.mvc.render;

import com.zbw.mvc.RequestHandlerChain;

/**
 * 渲染请求结果 interface
 */
public interface Render {
    /**
     * 执行渲染
     */
    void render(RequestHandlerChain handlerChain) throws Exception;
}
实现RequestHandlerChain

上面两个接口都有个参数RequestHandlerChain,这个类是整个请求的执行链,用于存储整个请求需要保存的一些属性和串联整个请求。

在com.zbw.mvc下创建这个类

package com.zbw.mvc;
import ...

/**
 * http请求处理链
 */
@Data
@Slf4j
public class RequestHandlerChain {
    /**
     * Handler迭代器
     */
    private Iterator handlerIt;

    /**
     * 请求request
     */
    private HttpServletRequest request;

    /**
     * 请求response
     */
    private HttpServletResponse response;

    /**
     * 请求http方法
     */
    private String requestMethod;

    /**
     * 请求http路径
     */
    private String requestPath;

    /**
     * 请求状态码
     */
    private int responseStatus;

    /**
     * 请求结果处理器
     */
    private Render render;

    public RequestHandlerChain(Iterator handlerIt, HttpServletRequest request, HttpServletResponse response) {
        this.handlerIt = handlerIt;
        this.request = request;
        this.response = response;
        this.requestMethod = request.getMethod();
        this.requestPath = request.getPathInfo();
        this.responseStatus = HttpServletResponse.SC_OK;
    }

    /**
     * 执行请求链
     */
    public void doHandlerChain() {
        try {
            while (handlerIt.hasNext()) {
                if (!handlerIt.next().handle(this)) {
                    break;
                }
            }
        } catch (Exception e) {
            log.error("doHandlerChain error", e);
            render = new InternalErrorRender();
        }
    }

    /**
     * 执行处理器
     */
    public void doRender() {
        if (null == render) {
            render = new DefaultRender();
        }
        try {
            render.render(this);
        } catch (Exception e) {
            log.error("doRender", e);
            throw new RuntimeException(e);
        }
    }
}

在这个类中除了存储http请求信息以外,还有Handler迭代器handlerIt和请求结果处理器Render

doHandlerChain()方法就会迭代执行handlerIt中的Handler的handle()方法,并且会根据每个Handler返回的值来判断是否继续往下执行下一个Handler。

doRender()方法用于调用Render中的render()方法。

更改DispatcherServlet

接下来就可以修改DispatcherServlet请求转发器了。

package com.zbw.mvc;
import ...

/**
 * DispatcherServlet 所有http请求都由此Servlet转发
 */
@Slf4j
public class DispatcherServlet extends HttpServlet {

    /**
     * 请求执行链
     */
    private final List HANDLER = new ArrayList<>();

    /**
     * 执行请求
     */
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        RequestHandlerChain handlerChain = new RequestHandlerChain(HANDLER.iterator(), req, resp);
        handlerChain.doHandlerChain();
        handlerChain.doRender();
    }
}

可以看到现在DispatcherServlet已经很简洁了,把请求的逻辑代码交给RequestHandlerChain处理,自己没有多余的http逻辑代码。

实现几种Handler

上面只创建了Handler的接口没有实现类,现在就实现几个Handler的实现类。这些实现类只实现了简单的一些http请求的功能,大家可以自己根据情况开发更多的实现类。

PreRequestHandler

首先是PreRequestHandler,用于预处理http的一些信息,比如设置http编码,处理请求url,打印一些信息等。

package com.zbw.mvc.handler;
import ...

/**
 * 请求预处理
 */
@Slf4j
public class PreRequestHandler implements Handler {
    @Override
    public boolean handle(final RequestHandlerChain handlerChain) throws Exception {
        // 设置请求编码方式
        handlerChain.getRequest().setCharacterEncoding("UTF-8");
        String requestPath = handlerChain.getRequestPath();
        if (requestPath.length() > 1 && requestPath.endsWith("/")) {
            handlerChain.setRequestPath(requestPath.substring(0, requestPath.length() - 1));
        }
        log.info("[Doodle] {} {}", handlerChain.getRequestMethod(), handlerChain.getRequestPath());
        return true;
    }
}
SimpleUrlHandler

接下来是SimpleUrlHandler,用于处理静态资源,当碰到资源是静态资源时就直接转发请求到Tomcat默认的servlet去。

package com.zbw.mvc.handler;
import ...

/**
 * 普通url请求执行
 * 主要处理静态资源
 */
@Slf4j
public class SimpleUrlHandler implements Handler {
    /**
     * tomcat默认RequestDispatcher的名称
     * TODO: 其他服务器默认的RequestDispatcher.如WebLogic为FileServlet
     */
    private static final String TOMCAT_DEFAULT_SERVLET = "default";

    /**
     * 默认的RequestDispatcher,处理静态资源
     */
    private RequestDispatcher defaultServlet;

    public SimpleUrlHandler(ServletContext servletContext) {
        defaultServlet = servletContext.getNamedDispatcher(TOMCAT_DEFAULT_SERVLET);

        if (null == defaultServlet) {
            throw new RuntimeException("没有默认的Servlet");
        }

        log.info("The default servlet for serving static resource is [{}]", TOMCAT_DEFAULT_SERVLET);
    }


    @Override
    public boolean handle(final RequestHandlerChain handlerChain) throws Exception {
        if (isStaticResource(handlerChain.getRequestPath())) {
            defaultServlet.forward(handlerChain.getRequest(), handlerChain.getResponse());
            return false;
        }
        return true;
    }

    /**
     * 是否为静态资源
     */
    private boolean isStaticResource(String url) {
        return url.startsWith(Doodle.getConfiguration().getAssetPath());
    }
}
JspHandler

然后是处理jsp页面的实现类JspHandler,当碰到资源是jsp页面时就直接转发请求到Tomcat的jsp的servlet去。

package com.zbw.mvc.handler;
import ...

/**
 * jsp请求处理
 * 主要负责jsp资源请求
 */
public class JspHandler implements Handler {
    /**
     * jsp请求的RequestDispatcher的名称
     */
    private static final String JSP_SERVLET = "jsp";

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

    public JspHandler(ServletContext servletContext) {
        jspServlet = servletContext.getNamedDispatcher(JSP_SERVLET);
        if (null == jspServlet) {
            throw new RuntimeException("没有jsp Servlet");
        }
    }

    @Override
    public boolean handle(final RequestHandlerChain handlerChain) throws Exception {
        if (isPageView(handlerChain.getRequestPath())) {
            jspServlet.forward(handlerChain.getRequest(), handlerChain.getResponse());
            return false;
        }
        return true;
    }

    /**
     * 是否为jsp资源
     */
    private boolean isPageView(String url) {
        return url.startsWith(Doodle.getConfiguration().getViewPath());
    }
}
ControllerHandler

最后就是ControllerHandler,这个和从零开始实现一个简易的Java MVC框架(七)--实现MVC中的ControllerHandler功能一样,用于处理请求中数据和controller对应的关系。

package com.zbw.mvc.handler;
import ...

/**
 * Controller请求处理
 */
@Slf4j
public class ControllerHandler implements Handler {
    /**
     * 请求信息和controller信息关系map
     */
    private Map pathControllerMap = new ConcurrentHashMap<>();
    /**
     * bean容器
     */
    private BeanContainer beanContainer;

    public ControllerHandler() {
        beanContainer = BeanContainer.getInstance();

        Set> mappingSet = beanContainer.getClassesByAnnotation(RequestMapping.class);
        this.initPathControllerMap(mappingSet);
    }

    @Override
    public boolean handle(final RequestHandlerChain handlerChain) throws Exception {
        String method = handlerChain.getRequestMethod();
        String path = handlerChain.getRequestPath();
        ControllerInfo controllerInfo = pathControllerMap.get(new PathInfo(method, path));
        if (null == controllerInfo) {
            handlerChain.setRender(new NotFoundRender());
            return false;
        }
        Object result = invokeController(controllerInfo, handlerChain.getRequest());
        setRender(result, controllerInfo, handlerChain);
        return true;
    }

    /**
     * 执行controller方法
     */
    private Object invokeController(ControllerInfo controllerInfo, HttpServletRequest request) {
        Map requestParams = getRequestParams(request);
        List methodParams = instantiateMethodArgs(controllerInfo.getMethodParameter(), requestParams);

        Object controller = beanContainer.getBean(controllerInfo.getControllerClass());
        Method invokeMethod = controllerInfo.getInvokeMethod();
        invokeMethod.setAccessible(true);
        Object result;
        try {
            if (methodParams.size() == 0) {
                result = invokeMethod.invoke(controller);
            } else {
                result = invokeMethod.invoke(controller, methodParams.toArray());
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * 设置请求结果执行器
     */
    private void setRender(Object result, ControllerInfo controllerInfo, RequestHandlerChain handlerChain) {
        if (null == result) {
            return;
        }
        Render render;
        boolean isJson = controllerInfo.getInvokeMethod().isAnnotationPresent(ResponseBody.class);
        if (isJson) {
            render = new JsonRender(result);
        } else {
            render = new ViewRender(result);
        }
        handlerChain.setRender(render);
    }

    /**
     * 初始化pathControllerMap
     */
    private void initPathControllerMap(Set> mappingSet) {
        mappingSet.forEach(this::addPathController);
    }

    /**
     * 添加controllerInfo到pathControllerMap中
     */
    private void addPathController(Class clz) {
        RequestMapping requestMapping = clz.getAnnotation(RequestMapping.class);
        String basePath = requestMapping.value();
        if (!basePath.startsWith("/")) {
            basePath = "/" + basePath;
        }
        for (Method method : clz.getDeclaredMethods()) {
            if (method.isAnnotationPresent(RequestMapping.class)) {
                RequestMapping methodRequest = method.getAnnotation(RequestMapping.class);
                String methodPath = methodRequest.value();
                if (!methodPath.startsWith("/")) {
                    methodPath = "/" + methodPath;
                }
                String url = basePath + methodPath;
                Map> methodParams = this.getMethodParams(method);
                String httpMethod = String.valueOf(methodRequest.method());
                PathInfo pathInfo = new PathInfo(httpMethod, url);
                if (pathControllerMap.containsKey(pathInfo)) {
                    log.warn("url:{} 重复注册", pathInfo.getHttpPath());
                }
                ControllerInfo controllerInfo = new ControllerInfo(clz, method, methodParams);
                this.pathControllerMap.put(pathInfo, controllerInfo);
                log.info("mapped:[{},method=[{}]] controller:[{}@{}]",
                        pathInfo.getHttpPath(), pathInfo.getHttpMethod(),
                        controllerInfo.getControllerClass().getName(), controllerInfo.getInvokeMethod().getName());
            }
        }
    }

    /**
     * 获取执行方法的参数
     */
    private Map> getMethodParams(Method method) {
        Map> map = new HashMap<>();
        for (Parameter parameter : method.getParameters()) {
            RequestParam param = parameter.getAnnotation(RequestParam.class);
            // TODO: 不使用注解匹配参数名字
            if (null == param) {
                throw new RuntimeException("必须有RequestParam指定的参数名");
            }
            map.put(param.value(), parameter.getType());
        }
        return map;
    }

    /**
     * 获取HttpServletRequest中的参数
     */
    private Map getRequestParams(HttpServletRequest request) {
        Map paramMap = new HashMap<>();
        //GET和POST方法是这样获取请求参数的
        request.getParameterMap().forEach((paramName, paramsValues) -> {
            if (ValidateUtil.isNotEmpty(paramsValues)) {
                paramMap.put(paramName, paramsValues[0]);
            }
        });
        // TODO: Body、Path、Header等方式的请求参数获取
        return paramMap;
    }

    /**
     * 实例化方法参数
     */
    private List instantiateMethodArgs(Map> methodParams, Map requestParams) {
        return methodParams.keySet().stream().map(paramName -> {
            Class type = methodParams.get(paramName);
            String requestValue = requestParams.get(paramName);
            Object value;
            if (null == requestValue) {
                value = CastUtil.primitiveNull(type);
            } else {
                value = CastUtil.convert(type, requestValue);
                // TODO: 实现非原生类的参数实例化
            }
            return value;
        }).collect(Collectors.toList());
    }

}
初始化HANDLER列表和去除TomcatServer的多余代码

刚才实现的几个HANDLER还需要初始化,就在DispatcherServletinit()方法中初始化。注意初始化的顺序会决定其在RequestHandlerChain执行链中执行的先后。

...
@Slf4j
public class DispatcherServlet extends HttpServlet {
    ...

    /**
     * 初始化Servlet
     */
    @Override
    public void init() throws ServletException {
        HANDLER.add(new PreRequestHandler());
        HANDLER.add(new SimpleUrlHandler(getServletContext()));
        HANDLER.add(new JspHandler(getServletContext()));
        HANDLER.add(new ControllerHandler());
    }
    ...
}

然后去除TomcatServerJspServletDefaultServlet两个servlet的初始化,因为已经在 JspHandlerSimpleUrlHandler中初始化了这两个servlet。

...
@Slf4j
public class TomcatServer implements Server {
    ...
    public TomcatServer(Configuration configuration) {
        try {
            this.tomcat = new Tomcat();
            tomcat.setBaseDir(configuration.getDocBase());
            tomcat.setPort(configuration.getServerPort());

            File root = getRootFolder();
            File webContentFolder = new File(root.getAbsolutePath(), configuration.getResourcePath());
            if (!webContentFolder.exists()) {
                webContentFolder = Files.createTempDirectory("default-doc-base").toFile();
            }

            log.info("Tomcat:configuring app with basedir: [{}]", webContentFolder.getAbsolutePath());
            StandardContext ctx = (StandardContext) tomcat.addWebapp(configuration.getContextPath(), webContentFolder.getAbsolutePath());
            ctx.setParentClassLoader(this.getClass().getClassLoader());

            WebResourceRoot resources = new StandardRoot(ctx);
            ctx.setResources(resources);
            
            // 去除了JspHandler和SimpleUrlHandler这两个servlet的注册
            
            tomcat.addServlet(configuration.getContextPath(), "dispatcherServlet", new DispatcherServlet()).setLoadOnStartup(0);
            ctx.addServletMappingDecoded("/*", "dispatcherServlet");
        } catch (Exception e) {
            log.error("初始化Tomcat失败", e);
            throw new RuntimeException(e);
        }
    }
}
实现几种Render

上面创建的Render接口也需要一些实现类。同样的,这些Render也只是实现基本的功能 ,大家可以自己根据情况开发更多。

DefaultRender

这个是默认的Render,设置HttpServletResponse中的status为RequestHandlerChain中StatusCode。

package com.zbw.mvc.render;
import ...
/**
 * 默认渲染 200
 */
public class DefaultRender implements Render {
    @Override
    public void render(RequestHandlerChain handlerChain) throws Exception {
        int status = handlerChain.getResponseStatus();
        handlerChain.getResponse().setStatus(status);
    }
}
InternalErrorRender

这个Render返回StatusCode为500

package com.zbw.mvc.render;
import ...
/**
 * 渲染500
 */
public class InternalErrorRender implements Render {
    @Override
    public void render(RequestHandlerChain handlerChain) throws Exception {
        handlerChain.getResponse().sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    }
}
NotFoundRender

这个Render返回StatusCode为404

package com.zbw.mvc.render;
import ...

/**
 * 渲染404
 */
public class NotFoundRender implements Render {
    @Override
    public void render(RequestHandlerChain handlerChain) throws Exception {
        handlerChain.getResponse().sendError(HttpServletResponse.SC_NOT_FOUND);
    }
}
JsonRender

这个Render返回json数据,当Handler请求发现返回数据为json格式时,就用这个Render

package com.zbw.mvc.render;
import ...

/**
 * 渲染json
 */
@Slf4j
public class JsonRender implements Render {
    private Object jsonData;
    public JsonRender(Object jsonData) {
        this.jsonData = jsonData;
    }
    @Override
    public void render(RequestHandlerChain handlerChain) throws Exception {
        // 设置响应头
        handlerChain.getResponse().setContentType("application/json");
        handlerChain.getResponse().setCharacterEncoding("UTF-8");
        // 向响应中写入数据
        try (PrintWriter writer = handlerChain.getResponse().getWriter()) {
            writer.write(JSON.toJSONString(jsonData));
            writer.flush();
        }
    }
}
ViewRender

这个Render跳转到页面,将ModelAndView中的信息存到HttpServletRequest中并跳转到对应页面

package com.zbw.mvc.render;
import ...

/**
 * 渲染页面
 */
@Slf4j
public class ViewRender implements Render {
    private ModelAndView mv;
    public ViewRender(Object mv) {
        if (mv instanceof ModelAndView) {
            this.mv = (ModelAndView) mv;
        } else if (mv instanceof String) {
            this.mv = new ModelAndView().setView((String) mv);
        } else {
            throw new RuntimeException("返回类型不合法");
        }
    }
    @Override
    public void render(RequestHandlerChain handlerChain) throws Exception {
        HttpServletRequest req = handlerChain.getRequest();
        HttpServletResponse resp = handlerChain.getResponse();
        String path = mv.getView();
        Map model = mv.getModel();
        model.forEach(req::setAttribute);
        req.getRequestDispatcher(Doodle.getConfiguration().getViewPath() + path).forward(req, resp);
    }
}
结语

至此,MVC的优化完成了,同时整个doodle框架的代码也算是完成了。

虽然doodle早已完成,但是讲解的文章托托延延到现在才完成。

在刚完成doodle时感觉整个框架已经成型了,但是在写这个系列文章的过程中才真正发现欠缺的还有非常非常多,甚至觉得把它称为框架都有些抬举它了呢。

只能说在实现它然后再写这个系列的文章之后对spring的崇拜之心更加深了,其被javaer广泛使用和拜读果然是有原因的。

另外也感谢大家阅读这个系列的文章,如果对大家有所帮助的话可以去给我的项目加个star,有什么问题和建议也可以提出来交流交流。

这个系列的所有文章我都放在我的博客上了:http://zzzzbw.cn/

从零开始实现一个简易的Java MVC框架(一)--前言

从零开始实现一个简易的Java MVC框架(二)--实现Bean容器

从零开始实现一个简易的Java MVC框架(三)--实现IOC

从零开始实现一个简易的Java MVC框架(四)--实现AOP

从零开始实现一个简易的Java MVC框架(五)--引入aspectj实现AOP切点

从零开始实现一个简易的Java MVC框架(六)--加强AOP功能

从零开始实现一个简易的Java MVC框架(七)--实现MVC

从零开始实现一个简易的Java MVC框架(八)--制作Starter

从零开始实现一个简易的Java MVC框架(九)--优化MVC代码

源码地址:doodle

原文地址:从零开始实现一个简易的Java MVC框架(九)--优化MVC代码

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76715.html

相关文章

  • 从零开始实现一个简易Java MVC框架

    摘要:不过仔细了解了一段时候发现,其实他的原理是很简单的,所以想要自己也动手实现一个功能类似的框架。原文地址从零开始实现一个简易的框架 前言 最近在看spring-boot框架的源码,看了源码之后更是让我感受到了spring-boot功能的强大。而且使用了很多的设计模式,让人在看的时候觉得有点难以下手。 不过仔细了解了一段时候发现,其实他的原理是很简单的,所以想要自己也动手实现一个功能类似的...

    neuSnail 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(五)--引入aspectj实现AOP切点

    摘要:接下来就可以把这个切点类加入到我们之前实现的功能中了。实现的切点功能首先改装注解,把之前改成来存储表达式。测试用例在上一篇文章从零开始实现一个简易的框架四实现中的测试用例的基础上修改测试用例。 前言 在上一节从零开始实现一个简易的Java MVC框架(四)--实现AOP中我们实现了AOP的功能,已经可以生成对应的代理类了,但是对于代理对象的选择只能通过指定的类,这样确实不方便也不合理。...

    wupengyu 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(八)--制作Starter

    摘要:服务器相关配置启动类资源目录目录静态文件目录端口号目录目录实现内嵌服务器在上一章文章从零开始实现一个简易的框架七实现已经在文件中引入了依赖,所以这里就不用引用了。 spring-boot的Starter 一个项目总是要有一个启动的地方,当项目部署在tomcat中的时候,经常就会用tomcat的startup.sh(startup.bat)的启动脚本来启动web项目 而在spring-b...

    AprilJ 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(六)--加强AOP功能

    摘要:在前面的文章中实现的功能时,目标类都只能被一个切面代理,如果想要生成第二个代理类,就会把之前的代理类覆盖。改装原有功能现在要改装原来的的实现代码,让的功能加入到框架中为了让切面能够排序,先添加一个注解,用于标记排序。 前言 在前面从零开始实现一个简易的Java MVC框架(四)--实现AOP和从零开始实现一个简易的Java MVC框架(五)--引入aspectj实现AOP切点这两节文章...

    Loong_T 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(二)--实现Bean容器

    摘要:容器实际上就是存放所有的地方,即以及相关信息对应其实体的容器,为什么称之为呢,因为在中,定义信息和实例的东西叫。了解到这个以后接下来就可以开始编写容器了,在包下创建一个类叫。获取容器实例至此,这个容器就完成了。 项目准备 首先确保你拥有以下环境或者工具 idea java 8 maven 3.3.X lombok插件 然后我们创建一个maven工程,编写pom.xml引入一些需要的...

    paulquei 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<