百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

手写Spring框架之MVC

ccwgpt 2024-11-22 11:34 21 浏览 0 评论

简介




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, Handler> REQUEST_MAP = new HashMap<Request, Handler>();

    static {
        //遍历所有Controller类
        Set<Class<?>> 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<User> getAllUser();
}

@Service
public class UserService implements IUserService {
    /**
     * 获取所有用户
     */
    public List<User> getAllUser() {
        List<User> 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<User> 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>用户id</th>
        <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


相关推荐

VUE3前端开发入门系列教程二:使用iView框架辅助开发

1、安装iView新框架,支持VUE3npminstallview-ui-plus2、编辑src/main.js,添加以下内容,导入js和css到项目importViewUIPlusfrom...

万能前端框架uni app初探03:底部导航开发

前言本节我们使用uniapp的底部导航功能,点击不同tab会显示不同页面,这个功能在实际项目开发中几乎是必备的。一、基础知识1.tabBar如果应用是一个多tab应用,可以通过tabBar配...

Rust Web 开发框架,前端你可以选择哪个?

Rust构建一切。在如今流行的语言中,Rust可谓是将构建和高效作为自己优美的身姿在大众视野中脱颖而出。它是一门赋予每个人构建可靠且高效软件能力的语言。它有什么特性呢?高性能。Rust速度惊人且内...

连载:前端开发中纠结的Javascript框架(上)

如今,前端开发有着许许多多的框架和库。其中一些好用,一些却不尽人意。通常我们会习惯性运用某一概念,模块或句法。事实上,并没有什么万能工具。这篇文章是关于未来框架的发展趋势——那就是没有框架!我从以下几...

前端开发框架的演进架构:提升用户体验和开发效率

前端开发框架是现代Web应用开发的重要工具,它不仅可以帮助开发者构建复杂的用户界面,还能够提升用户体验和开发效率。随着Web技术的不断发展,前端开发框架也在不断演进,为开发者提供了更丰富、更高效的工具...

Google应用Mesh-TensorFlow框架,让CNN也能处理超高分辨率图像

为了要处理超高分辨率医疗图像数据,Google开发了一种空间数据分区(SpatialPartition)技术,在不牺牲图像分辨率的条件下,分析超高分辨率图像。Google使用Mesh-TensorF...

大模型安全挑战加剧:框架层漏洞成新靶心

近日,360数字安全集团发布了一份关于大模型安全漏洞的报告,揭示了当前大模型及围绕其构建的框架和应用中存在的严重安全问题。报告显示,360近期研究发现了近40个大模型相关的安全漏洞,其中既包括二进制内...

Keras 3.0正式发布:可用于TensorFlow、JAX和PyTorch

机器之心报道编辑:陈萍经过5个月的更新迭代,Keras3.0终于来了。「大新闻:我们刚刚发布了Keras3.0版本!」Keras之父FrancoisChollet在X上激动的...

TensorFlow和Keras入门必读教程(tensorflow与keras版本对应)

导读:本文对TensorFlow的框架和基本示例进行简要介绍。作者:本杰明·普朗什(BenjaminPlanche)艾略特·安德烈斯(EliotAndres)来源:华章科技01TensorFlo...

谷歌官方回应“TensorFlow遭弃”:还在投资开发,将与JAX并肩作战

鱼羊发自凹非寺量子位|公众号QbitAI终于,谷歌出面回应“TensorFlow遭弃”传闻:我们将继续致力于将TensorFlow打造为一流机器学习平台,与JAX并肩推动机器学习研究。这段时...

2025 年的PHP :现代 Web 开发的强大引擎

程序员还在吐槽PHP过时?2025年的PHP8.4直接封神了。看看最近更新的属性钩子、强类型系统,加上Laravel这些框架,老语言早就脱胎换骨。十年前说PHP弱类型容易崩代码的,现在脸疼不?联合类...

前端内卷终结者?htmx如何让开发者告别200行JS只做一个按钮

当你用React写一个点赞按钮需要引入3个状态管理库、编写80行JSX和120行钩子函数时,htmx只需要一行HTML:<buttonhx-post="/like"hx-sw...

NativePHP桌面版V1.0正式发布(元气桌面电脑版下载)

导读:各位小伙伴,使用PHP构建桌面级系统的利器,NativePHP来了。概述NativePHP是一个用于使用PHP构建桌面应用的框架。它允许PHP开发人员使用熟悉的工具和技术创建跨平台的原生应用...

PHP Laravel框架底层机制(php基本框架)

当然可以,Laravel是最受欢迎的PHP框架之一,以优雅的语法和丰富的生态而闻名。尽管开发体验非常“高端”,它的底层其实是由一系列结构清晰、职责分明的组件构成的。下面我从整体架构、核心流程、...

PHP框架之Laravel框架教程:2. 控制器、路由、视图简单介绍

2.控制器、路由、视图简单介绍我们先建立控制器,目录是:app/Http/Controllers,新建控制器Ding.php,代码如下:Ding.php:<?phpnamespaceA...

取消回复欢迎 发表评论: