橙小张的Java后端学习之路SSHkjJava学习笔记

从零设计一个mvc框架

2017-07-02  本文已影响145人  橙小张
image.png

作为一个JavaWeb开发人员,没有经过EJB时代,但是使用过SSH和SSM这些优秀的框架,框架减轻了开发者的开发量,让开发者更有效率的应对业务,但是,作为一个爱折腾的程序员,不应该仅仅局限于对于框架的使用,更应该深入原理,去了解它底层的一些设计思想,这篇博客不是介绍像Spring,SpringMVC这些框架是怎么设计的,而是由理论到实战的讲解以下mvc框架的一些思想,以便于出现问题的时候更好的定位问题,也为学习这些优秀的框架打下一个基础。

目录

一、JavaWeb目录结构

image.png

这是用IDEA创建,Maven构建的JavaWeb项目,先分析下这里的一些目录结构:

二、Servlet概述

三、Servlet生命周期

四、Servlet工作原理

Tomcat作为Servlet容器的一种实现,他的工作步骤如下:

image.png

步骤:

  1. Web Client 向Servlet容器(Tomcat)发出Http请求
  2. Servlet容器接收Web Client的请求
  3. Servlet容器创建一个HttpRequest对象,将Web Client请求的信息封装到这个对象中。
  4. Servlet容器创建一个HttpResponse对象
  5. Servlet容器调用HttpServlet对象的service方法,把HttpRequest对象与HttpResponse对象作为参数传给 HttpServlet 对象。
  6. HttpServlet调用HttpRequest对象的有关方法,获取Http请求信息。
  7. HttpServlet调用HttpResponse对象的有关方法,生成响应数据。
  8. Servlet容器把HttpServlet的响应结果传给Web Client。
image.png

五、基于原生Servlet的Web项目

<servlet>
    <servlet-name>AServlet</servlet-name>
    <servlet-class>com.sailfish.user.AServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>AServlet</servlet-name>
    <url-pattern>/AServlet</url-pattern>
  </servlet-mapping>
public class AServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        System.out.println("hello doGet()...");
    }

      public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        System.out.println("hello doPost()...");
    }
}

六、基于Filter的MVC框架

web程序是基于 M(模型)V(视图)C(控制器)设计的。MVC是一种将应用程序的逻辑层和表现层进行分离的结构方式。在实践中,由于表现层从 Java 中分离了出来,所以它允许你的网页中只包含很少的脚本。

image.png

思路:

主体思路有了,就是思考怎么实现它,上面的三个关键词:route,path,controller,method,这里的mvc框架业主要做的就是这件事情,思路是这样的:

public class Route {

    /**
     * 路由path
     */
    private String path;

    /**
     * 执行路由的方法
     */
    private Method action;

    /**
     * 路由所在的控制器
     */
    private Object controller;
}
public class Routers {

    private static final Logger LOGGER = Logger.getLogger(Routers.class.getName());
    
    private List<Route> routes = new ArrayList<Route>();
    
    public Routers() {
    }
    
    public void addRoute(List<Route> routes){
        routes.addAll(routes);
    }
    
    public void addRoute(Route route){
        routes.add(route);
    }
    
    public void removeRoute(Route route){
        routes.remove(route);
    }
    
    public void addRoute(String path, Method action, Object controller){
        Route route = new Route();
        route.setPath(path);
        route.setAction(action);
        route.setController(controller);
        
        routes.add(route);
        LOGGER.info("Add Route:[" + path + "]");
    }

    public List<Route> getRoutes() {
        return routes;
    }

    public void setRoutes(List<Route> routes) {
        this.routes = routes;
    }
    
}
/**
     * 根据path查找路由
     * @param path  请求地址
     * @return      返回查询到的路由
     */
    public Route findRoute(String path) {
        String cleanPath = parsePath(path);
        List<Route> matchRoutes = new ArrayList<Route>();
        for (Route route : this.routes) {
            if (matchesPath(route.getPath(), cleanPath)) {
                matchRoutes.add(route);
            }
        }
        // 优先匹配原则
        giveMatch(path, matchRoutes);
        
        return matchRoutes.size() > 0 ? matchRoutes.get(0) : null;
    }
public interface Bootstrap {

    /**
     * 初始化方法
     * @param mario 全局对象
     */
    void init(Mario mario);
    
}
public class App implements Bootstrap {

    @Override
    public void init(Mario mario) {
        //实例化控制类(controller)
        Index index = new Index();
        UserController userController = new UserController();
        //添加请求路径和action的映射关系
        mario.addRoute("/", "index", index);
        mario.addRoute("/hello", "hello", index);
        mario.addRoute("/html", "html", index);
        
        mario.addRoute("/users", "users", userController);
        mario.addRoute("/user/add", "show_add", userController);
        mario.addRoute("/user/save", "save", userController);
        mario.addRoute("/user/edit", "edit", userController);
        mario.addRoute("/user/update", "update", userController);
        mario.addRoute("/user/del", "delete", userController);

    }
@Override
    public void init(FilterConfig filterConfig) throws ServletException {
        LOGGER.info(">>MarioFilter-init......");
        //创建mario实例
        Mario mario = Mario.me();
        if(!mario.isInit()){
            
            String className = filterConfig.getInitParameter("bootstrap"); //com.mario.demo.App
            Bootstrap bootstrap = this.getBootstrap(className);
            bootstrap.init(mario);

            /**
             *  应用已经加载完成,mario中包含了:
             *  1、route(路由)
             *  2、资源文件(配置加载器)
             *  3、是否已经加载的标识
             *  4、渲染器(render)
             */
            Routers routers = mario.getRouters();
            if(null != routers){
                routeMatcher.setRoutes(routers.getRoutes());
            }
            servletContext = filterConfig.getServletContext();
            
            mario.setInit(true);
        }
    }
@Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        request.setCharacterEncoding(Const.DEFAULT_CHAR_SET);
        response.setCharacterEncoding(Const.DEFAULT_CHAR_SET);
        
        // 请求的uri
        String uri = PathUtil.getRelativePath(request); // users,当前请求的路径
        
        LOGGER.info("Request URI:" + uri);

        //找到合适的路由
        Route route = routeMatcher.findRoute(uri);
        /**
         * Route:
         * 1、path:/users
         * 2、action:public void com.mario.demo.controller.UserController.users(com.junicorn.mario.servlet.wrapper.Request,com.junicorn.mario.servlet.wrapper.Response)
         * 3、controller:userController
         */

        // 如果找到
        if (route != null) {
            // 实际执行方法
            handle(request, response, route);
        } else{
            chain.doFilter(request, response);
        }
    }
private void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Route route){
        
        // 初始化上下文
        Request request = new Request(httpServletRequest);
        Response response = new Response(httpServletResponse);
        MarioContext.initContext(servletContext, request, response);
        
        Object controller = route.getController();
        // 要执行的路由方法
        Method actionMethod = route.getAction(); //actionMethod:public void com.mario.demo.controller.UserController.users(com.junicorn.mario.servlet.wrapper.Request,com.junicorn.mario.servlet.wrapper.Response)
        // 执行route方法
        executeMethod(controller, actionMethod, request, response);
    }

     /**
     * 执行路由方法
     */
    private Object executeMethod(Object object, Method method, Request request, Response response){
        int len = method.getParameterTypes().length;
        //允许对反射私有方法操作
        method.setAccessible(true);
        if(len > 0){
            Object[] args = getArgs(request, response, method.getParameterTypes());
            return ReflectUtil.invokeMehod(object, method, args);
        } else {
            return ReflectUtil.invokeMehod(object, method);
        }
    }

七、总结

备注:本片博客使用的案例来源于王爵的项目,他的项目已经足够简洁了,我觉得目前为止我也写不出比他好的😀

上一篇 下一篇

猜你喜欢

热点阅读