手写Spring框架之MVC

程序员考拉

共 7951字,需浏览 16分钟

 · 2020-08-26


简介


上一篇博客实现了Bean容器和IOC功能, 本篇博客来实现简化版的 Spring MVC. 在看下面的内容之前, 我们首先来回顾下 Spring MVC的架构图:




Spring MVC 最核心部分的就是前端控制器DispatcherServlet, 而DispatcherServlet其实就是一个Servlet, 所以我们有必要先了解下Servlet的知识点, 如下:


映射处理器


(1) Request类


请求类中的方法和路径对应 @RequestMapping 注解里的方法和路径.


public class Request {
    /**
     * 请求方法
     */

    private String requestMethod;

    /**
     * 请求路径
     */

    private String requestPath;

    public Request(String requestMethod, String requestPath) {
        this.requestMethod = requestMethod;
        this.requestPath = requestPath;
    }

    public String getRequestMethod() {
        return requestMethod;
    }

    public String getRequestPath() {
        return requestPath;
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + requestMethod.hashCode();
        result = 31 * result + requestPath.hashCode();
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof Request)) return false;
        Request request = (Request) obj;
        return request.getRequestPath().equals(this.requestPath) && request.getRequestMethod().equals(this.requestMethod);
    }
}


(2) Handler类


Handler类为一个处理器, 封装了Controller的Class对象和Method方法.


public class Handler {

    /**
     * Controller 类
     */

    private Class controllerClass;

    /**
     * Controller 方法
     */

    private Method controllerMethod;

    public Handler(Class controllerClass, Method controllerMethod) {
        this.controllerClass = controllerClass;
        this.controllerMethod = controllerMethod;
    }

    public Class getControllerClass() {
        return controllerClass;
    }

    public Method getControllerMethod() {
        return controllerMethod;
    }
}


(3) 实现映射处理器


ControllerHelper 助手类定义了一个"请求-处理器" 的映射 REQUEST_MAP, REQUEST_MAP 就相当于Spring MVC里的映射处理器, 接收到请求后返回对应的处理器.


REQUEST_MAP 映射处理器的实现逻辑如下:


首先通过 ClassHelper 工具类获取到应用中所有Controller的Class对象, 然后遍历Controller及其所有方法, 将所有带 @RequestMapping 注解的方法封装为处理器, 将 @RequestMapping 注解里的请求路径和请求方法封装成请求对象, 然后存入 REQUEST_MAP 中.


public final class ControllerHelper {

    /**
     * REQUEST_MAP为 "请求-处理器" 的映射
     */

    private static final Map REQUEST_MAP = new HashMap();

    static {
        //遍历所有Controller类
        Set> controllerClassSet = ClassHelper.getControllerClassSet();
        if (CollectionUtils.isNotEmpty(controllerClassSet)) {
            for (Class controllerClass : controllerClassSet) {
                //暴力反射获取所有方法
                Method[] methods = controllerClass.getDeclaredMethods();
                //遍历方法
                if (ArrayUtils.isNotEmpty(methods)) {
                    for (Method method : methods) {
                        //判断是否带RequestMapping注解
                        if (method.isAnnotationPresent(RequestMapping.class)) {
                            RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                            //请求路径
                            String requestPath = requestMapping.value();
                            //请求方法
                            String requestMethod = requestMapping.method().name();

                            //封装请求和处理器
                            Request request = new Request(requestMethod, requestPath);
                            Handler handler = new Handler(controllerClass, method);
                            REQUEST_MAP.put(request, handler);
                        }
                    }
                }
            }
        }
    }

    /**
     * 获取 Handler
     */

    public static Handler getHandler(String requestMethod, String requestPath) {
        Request request = new Request(requestMethod, requestPath);
        return REQUEST_MAP.get(request);
    }
}


前端控制器


(1) Param类


Param类用于封装Controller方法的参数.


public class Param {

    private Map<String, Object> paramMap;

    public Param() {
    }

    public Param(Map<String, Object> paramMap) {
        this.paramMap = paramMap;
    }

    public Map<String, Object> getParamMap() {
        return paramMap;
    }

    public boolean isEmpty(){
        return MapUtils.isEmpty(paramMap);
    }
}


(2) Data类


Data类用于封装Controller方法的JSON返回结果.


public class Data {

    /**
     * 模型数据
     */

    private Object model;

    public Data(Object model) {
        this.model = model;
    }

    public Object getModel() {
        return model;
    }
}


(3) View类


Data类用于封装Controller方法的视图返回结果.


public class View {

    /**
     * 视图路径
     */

    private String path;

    /**
     * 模型数据
     */

    private Map<String, Object> model;

    public View(String path) {
        this.path = path;
        model = new HashMap<String, Object>();
    }

    public View addModel(String key, Object value) {
        model.put(key, value);
        return this;
    }

    public String getPath() {
        return path;
    }

    public Map<String, Object> getModel() {
        return model;
    }
}


(4) RequestHelper 助手类


前端控制器接收到HTTP请求后, 从HTTP中获取请求参数, 然后封装到Param对象中.


public final class RequestHelper {

    /**
     * 获取请求参数
     */

    public static Param createParam(HttpServletRequest request) throws IOException {
        Map<String, Object> paramMap = new HashMap<>();
        Enumeration<String> paramNames = request.getParameterNames();
        //没有参数
        if (!paramNames.hasMoreElements()) {
            return null;
        }

        //get和post参数都能获取到
        while (paramNames.hasMoreElements()) {
            String fieldName = paramNames.nextElement();
            String fieldValue = request.getParameter(fieldName);
            paramMap.put(fieldName, fieldValue);
        }

        return new Param(paramMap);
    }
}


(5) HelperLoader 类


到目前为止, 我们创建了ClassHelper, BeanHelper, IocHelper, ControllerHelper这四个Helper类, 我们需要一个入口程序来加载他们(实际上是加载静态代码块), 当然就算没有这个入口程序, 这些类也会被加载, 我们这里只是为了让加载更加集中.


public final class HelperLoader {

    public static void init() {
        Class[] classList = {
            ClassHelper.class,
            BeanHelper.class,
            IocHelper.class,
            ControllerHelper.class
        };
        for (Class cls : classList) {
            ClassUtil.loadClass(cls.getName());
        }
    }
}


(6) 实现前端控制器


前端控制器实际上是一个Servlet, 这里配置的是拦截所有请求, 在服务器启动时实例化.

当DispatcherServlet实例化时, 首先执行 init() 方法, 这时会调用 HelperLoader.init() 方法来加载相关的helper类, 并注册处理相应资源的Servlet.


对于每一次客户端请求都会执行 service() 方法, 这时会首先将请求方法和请求路径封装为Request对象, 然后从映射处理器 (REQUEST_MAP) 中获取到处理器. 然后从客户端请求中获取到Param参数对象, 执行处理器方法. 最后判断处理器方法的返回值, 若为view类型, 则跳转到jsp页面, 若为data类型, 则返回json数据.


@WebServlet(urlPatterns = "/*", loadOnStartup = 0)
public class DispatcherServlet extends HttpServlet {

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        //初始化相关的helper类
        HelperLoader.init();

        //获取ServletContext对象, 用于注册Servlet
        ServletContext servletContext = servletConfig.getServletContext();

        //注册处理jsp和静态资源的servlet
        registerServlet(servletContext);
    }

    /**
     * DefaultServlet和JspServlet都是由Web容器创建
     * org.apache.catalina.servlets.DefaultServlet
     * org.apache.jasper.servlet.JspServlet
     */

    private void registerServlet(ServletContext servletContext) {
        //动态注册处理JSP的Servlet
        ServletRegistration jspServlet = servletContext.getServletRegistration("jsp");
        jspServlet.addMapping(ConfigHelper.getAppJspPath() + "*");

        //动态注册处理静态资源的默认Servlet
        ServletRegistration defaultServlet = servletContext.getServletRegistration("default");
        defaultServlet.addMapping("/favicon.ico"); //网站头像
        defaultServlet.addMapping(ConfigHelper.getAppAssetPath() + "*");
    }

    @Override
    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String requestMethod = request.getMethod().toUpperCase();
        String requestPath = request.getPathInfo();

        //这里根据Tomcat的配置路径有两种情况, 一种是 "/userList", 另一种是 "/context地址/userList".
        String[] splits = requestPath.split("/");
        if (splits.length > 2) {
            requestPath = "/" + splits[2];
        }

        //根据请求获取处理器(这里类似于SpringMVC中的映射处理器)
        Handler handler = ControllerHelper.getHandler(requestMethod, requestPath);
        if (handler != null) {
            Class controllerClass = handler.getControllerClass();
            Object controllerBean = BeanHelper.getBean(controllerClass);

            //初始化参数
            Param param = RequestHelper.createParam(request);

            //调用与请求对应的方法(这里类似于SpringMVC中的处理器适配器)
            Object result;
            Method actionMethod = handler.getControllerMethod();
            if (param == null || param.isEmpty()) {
                result = ReflectionUtil.invokeMethod(controllerBean, actionMethod);
            } else {
                result = ReflectionUtil.invokeMethod(controllerBean, actionMethod, param);
            }

            //跳转页面或返回json数据(这里类似于SpringMVC中的视图解析器)
            if (result instanceof View) {
                handleViewResult((View) result, request, response);
            } else if (result instanceof Data) {
                handleDataResult((Data) result, response);
            }
        }
    }

    /**
     * 跳转页面
     */

    private void handleViewResult(View view, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        String path = view.getPath();
        if (StringUtils.isNotEmpty(path)) {
            if (path.startsWith("/")) { //重定向
                response.sendRedirect(request.getContextPath() + path);
            } else { //请求转发
                Map<String, Object> model = view.getModel();
                for (Map.Entry<String, Object> entry : model.entrySet()) {
                    request.setAttribute(entry.getKey(), entry.getValue());
                }
                request.getRequestDispatcher(ConfigHelper.getAppJspPath() + path).forward(request, response);
            }
        }
    }

    /**
     * 返回JSON数据
     */

    private void handleDataResult(Data data, HttpServletResponse response) throws IOException {
        Object model = data.getModel();
        if (model != null) {
            response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");
            PrintWriter writer = response.getWriter();
            String json = JSON.toJSON(model).toString();
            writer.write(json);
            writer.flush();
            writer.close();
        }
    }
}


handwritten-mvc-framwork 实例


到这里为止, handwritten-mvc-framwork  框架已经实现了Bean容器, IOC功能, MVC功能, 所以现在我们完全可以用 handwritten-mvc-framwork  框架来写一个实例了.


(1) 业务类


public interface IUserService {
    List getAllUser();
}

@Service
public class UserService implements IUserService {
    /**
     * 获取所有用户
     */

    public List getAllUser() {
        List userList = new ArrayList<>();
        userList.add(new User(1, "Tom", 22));
        userList.add(new User(2, "Alic", 12));
        userList.add(new User(3, "Bob", 32));
        return userList;
    }
}


(2) 处理器


@Controller
public class UserController {
    @Autowired
    private IUserService userService;

    /**
     * 用户列表
     * @return
     */

    @RequestMapping(value = "/userList", method = RequestMethod.GET)
    public View getUserList() {
        List userList = userService.getAllUser();
        return new View("index.jsp").addModel("userList", userList);
    }
}


(3) JSP页面


<%@ page pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="BASE" value="${pageContext.request.contextPath}"/>
<html>
<head>
    <title>用户信息title>
head>
<body>
<h1>用户信息h1>
<table>
    <tr>
        <th>用户idth>
        <th>名称th>
        <th>年龄th>
    tr>
    <c:forEach var="userinfo" items="${userList}">
        <tr>
            <td>${userinfo.id}td>
            <td>${userinfo.name}td>
            <td>${userinfo.age}td>
            <td>
                <a href="#">详情a>
                <a href="#">编辑a>
            td>
        tr>
    c:forEach>
table>
body>
html>


(4) 结果


http://localhost:8081/handwritten/userList




原文链接:blog.csdn.net/litianxiang_kaola/article/details/86647040



浏览 28
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报