Spring学习(三):MVC

2023-09-19 15:33:48

一、什么是MVC

MVC(Model-View-Controller)是一种软件设计模式,用于组织和管理应用程序的代码结构。它将应用程序分为三个主要部分,即模型(Model)、视图(View)和控制器(Controller),每个部分都有特定的职责和功能。

以下是 MVC 模式中各个组成部分的概述:

  • 模型(Model):模型代表应用程序的数据和业务逻辑。它负责处理数据的读取、存储、验证和处理,以及执行应用程序的核心业务逻辑。模型通常是独立于用户界面的,可以被多个视图和控制器共享。
  • 视图(View):视图是用户界面的表示,负责展示数据给用户,并接收用户的输入。它通常是模型的可视化表现形式,负责将模型的数据呈现给用户,并根据用户的操作更新界面。视图不处理业务逻辑,只负责显示和接收用户的操作。
  • 控制器(Controller):控制器是模型和视图之间的协调者,负责处理用户的输入、更新模型的数据以及更新视图的显示。它接收用户的操作请求,调用相应的模型方法进行数据处理和更新,并在必要时更新视图以反映最新的数据。

MVC 的核心思想是将应用程序的逻辑和数据分离,使其更易于理解、扩展和维护。通过将应用程序的不同部分分离,MVC 模式提供了更好的代码组织和可重用性。在 MVC 中,用户与视图进行交互,视图通过控制器将用户的操作转发给模型进行处理,模型根据业务逻辑进行数据处理,然后通知视图进行更新。这种分离和协作的方式使得应用程序的不同部分能够独立地开发和测试,同时也提高了代码的可维护性和重用性。

在这里插入图片描述
举例来说,当浏览器发送一个查询请求,要求查询用户信息时,Controller通过jdbc调用数据库方法获得对应的User对象,然后将user对象传递给user.jsp渲染,并发送回浏览器。


二、Servlet

Servlet 是 Java 编程语言中的一种特殊类,用于处理 Web 应用程序中的动态内容和 HTTP 请求。Servlet 提供了一种基于服务器的编程模型,允许开发者通过编写 Java 代码来处理客户端的请求并生成相应的响应。

实际上Servlet就是一个API接口,它需要底层的Web服务器实现HTTP协议的解析处理,但也以此将底层解析代码对开发者屏蔽。使用者只需要关注上层的api接口的调用即可。我们使用Servlet API编写自己的Servlet来处理HTTP请求,Web服务器实现Servlet API接口,实现底层功能。

用法关键在于继承HttpServlet,覆写doPost, doGet等方法,并调用业务方法,返回HttpResponse。

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

// WebServlet注解表示这是一个Servlet,并映射到地址/:
@WebServlet(urlPatterns = "/")
public class UserServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String action = request.getParameter("action");

        if (action != null) {
            switch (action) {
                case "register":
                    handleRegistration(request, response);
                    break;
                case "login":
                    handleLogin(request, response);
                    break;
                default:
                    response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid action");
            }
        } else {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Action parameter is missing");
        }
    }

    private void handleRegistration(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 处理用户注册逻辑
        // 从 request 中获取用户提交的注册信息
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        // 执行用户注册操作,例如将用户信息存储到数据库中

        // 返回注册成功的响应
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html><body>");
        out.println("<h2>Registration successful</h2>");
        out.println("<p>Welcome, " + username + "!</p>");
        out.println("</body></html>");
    }

    private void handleLogin(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 处理用户登录逻辑
        // 从 request 中获取用户提交的登录信息
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        // 执行用户登录验证操作,例如从数据库中检查用户名和密码是否匹配

        // 返回登录成功或失败的响应
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html><body>");
        if (username.equals("admin") && password.equals("password")) {
            out.println("<h2>Login successful</h2>");
            out.println("<p>Welcome back, " + username + "!</p>");
        } else {
            out.println("<h2>Login failed</h2>");
            out.println("<p>Invalid username or password.</p>");
        }
        out.println("</body></html>");
    }
}

UserServlet 类继承了 HttpServlet 并重写了 doPost 方法来处理客户端的 POST 请求。根据请求中的 action 参数的不同值,分别调用 handleRegistration 和 handleLogin 方法来处理用户注册和登录逻辑。

要将Servlet部署到支持Servlet api的web服务器(Servlet容器如 Apache Tomcat),你需要将编译后的类文件(例如 UserServlet.class)放置在正确的目录结构中,并在 web.xml 文件(位于 WEB-INF 目录下)中进行配置。

具体配置参考 lxf Servlet教程


三、MVC框架原理和实现

参考lxf mvc高级开发
在使用Servlet的案例中,我们可以注意到,一个Servlet只能处理一个url下的Get Post请求,例如如果一个 Servlet 映射到路径 /users,它将处理所有以 /users 开头的请求路径。

如果有一个MVC框架,能够通过一个底层的DispatchServlet,存储所有的url到方法的映射,那就不需要重复继承和编写Servlet相关代码,上层被映射的方法可以组织成一个纯粹的Java类,只需要关注control部分的业务逻辑即可

public class UserController {
    @GetMapping("/signin")
    public ModelAndView signin() {
        ...
    }

    @PostMapping("/signin")
    public ModelAndView doSignin(SignInBean bean) {
        ...
    }

    @GetMapping("/signout")
    public ModelAndView signout(HttpSession session) {
        ...
    }
}

以以上代码为例,如果Servlet可以直接将doGet中与业务逻辑无关的内容实现,把Controller业务逻辑需要的功能抽象为新的类,返回值再通过ModelAndView传送给Servlet,由Servlet交给底层渲染引擎得到View,就可以使代码更加简洁,扩展性更强。

MVC框架原理:

我们需要在MVC框架中创建一个接收所有请求的Servlet,通常我们把它命名为DispatcherServlet,它总是映射到/,然后,根据不同的Controller的方法定义的@Get或@Post的Path决定调用哪个方法,最后,获得方法返回的ModelAndView后,渲染模板,写入HttpServletResponse,即完成了整个MVC的处理。

结构如下
在这里插入图片描述
DispatchServlet编写

@WebServlet(urlPatterns = "/")
public class DispatcherServlet extends HttpServlet {
    private Map<String, GetDispatcher> getMappings = new HashMap<>(); //需要存储请求路径到某个具体方法的映射
    private Map<String, PostDispatcher> postMappings = new HashMap<>();
}

//处理一个GET请求是通过GetDispatcher对象完成的,它需要如下信息
class GetDispatcher {
    Object instance; // Controller实例
    Method method; // Controller方法
    String[] parameterNames; // 方法参数名称
    Class<?>[] parameterClasses; // 方法参数类型
}

使用invoke处理真正的请求

class GetDispatcher {
    ...
    public ModelAndView invoke(HttpServletRequest request, HttpServletResponse response) {
        Object[] arguments = new Object[parameterClasses.length];
        for (int i = 0; i < parameterClasses.length; i++) {
            String parameterName = parameterNames[i];
            Class<?> parameterClass = parameterClasses[i];
            if (parameterClass == HttpServletRequest.class) {
                arguments[i] = request;
            } else if (parameterClass == HttpServletResponse.class) {
                arguments[i] = response;
            } else if (parameterClass == HttpSession.class) {
                arguments[i] = request.getSession();
            } else if (parameterClass == int.class) {
                arguments[i] = Integer.valueOf(getOrDefault(request, parameterName, "0"));
            } else if (parameterClass == long.class) {
                arguments[i] = Long.valueOf(getOrDefault(request, parameterName, "0"));
            } else if (parameterClass == boolean.class) {
                arguments[i] = Boolean.valueOf(getOrDefault(request, parameterName, "false"));
            } else if (parameterClass == String.class) {
                arguments[i] = getOrDefault(request, parameterName, "");
            } else {
                throw new RuntimeException("Missing handler for type: " + parameterClass);
            }
        }
        return (ModelAndView) this.method.invoke(this.instance, arguments);
    }

    private String getOrDefault(HttpServletRequest request, String name, String defaultValue) {
        String s = request.getParameter(name);
        return s == null ? defaultValue : s;
    }
}

Dispatch核心流程

public class DispatcherServlet extends HttpServlet {
    ...
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        resp.setCharacterEncoding("UTF-8");
        String path = req.getRequestURI().substring(req.getContextPath().length());
        // 根据路径查找GetDispatcher:
        GetDispatcher dispatcher = this.getMappings.get(path);
        if (dispatcher == null) {
            // 未找到返回404:
            resp.sendError(404);
            return;
        }
        // 调用Controller方法获得返回值:
        ModelAndView mv = dispatcher.invoke(req, resp);
        // 允许返回null:
        if (mv == null) {
            return;
        }
        // 允许返回`redirect:`开头的view表示重定向:
        if (mv.view.startsWith("redirect:")) {
            resp.sendRedirect(mv.view.substring(9));
            return;
        }
        // 将模板引擎渲染的内容写入响应:
        PrintWriter pw = resp.getWriter();
        this.viewEngine.render(mv, pw);
        pw.flush();
    }
}

这样使得上层代码编写更灵活。例如,一个显示用户资料的请求可以这样写

@GetMapping("/user/profile")
public ModelAndView profile(HttpServletResponse response, HttpSession session) {
    User user = (User) session.getAttribute("user");
    if (user == null) {
        // 未登录,跳转到登录页:
        return new ModelAndView("redirect:/signin");
    }
    if (!user.isManager()) {
        // 权限不够,返回403:
        response.sendError(403);
        return null;
    }
    return new ModelAndView("/profile.html", Map.of("user", user));
}

最后一步是在DispatcherServlet的init()方法中初始化所有Get和Post的映射,以及用于渲染的模板引擎:

public class DispatcherServlet extends HttpServlet {
    private Map<String, GetDispatcher> getMappings = new HashMap<>();
    private Map<String, PostDispatcher> postMappings = new HashMap<>();
    private ViewEngine viewEngine;

    @Override
    public void init() throws ServletException {
        this.getMappings = scanGetInControllers();
        this.postMappings = scanPostInControllers();
        this.viewEngine = new ViewEngine(getServletContext());
    }
    ...
}

如何扫描所有Controller以获取所有标记有@GetMapping和@PostMapping的方法,使用反射.

更多推荐

【物联网】简要解释RTK(Real-Time Kinematic)>>实时动态差分定位

引言:RTK(Real-TimeKinematic)技术是一种基于差分GPS的高精度定位技术,它通过实时通信和数据处理,能够提供厘米级甚至亚米级的定位精度。RTK技术在许多领域都得到了广泛应用,如测绘、航空航天、农业等。本文将介绍如何使用C语言实现RTK技术的基本功能,包括获取GPS数据、差分修正数据以及计算修正后的位

卫星地图-航拍影像-叠加配准套合(ArcGIS版)

卫星地图-航拍影像-叠加配准套合(ArcGIS版)发布时间:2018-01-17版权:BIGEMAP第一步工具准备BIGEMAP地图下载器:Bigemap系列产品-GIS行业基础软件kml\shp相关教程:CAD文件直接导入BIGEMAP进行套合配准(推荐)本实例使用ArcMap10.2软件进行影像与矢量数据叠加配准。

【Git】Git cherry-pick

Gitcherry-pick1.指令效果与基本用法在Git的文档中,对于cherry-pick指令的描述如下:gitcherry-pick命令用来获得在单个提交中引入的变更,然后尝试将作为一个新的提交引入到你当前分支上。从一个分支单独一个或者两个提交而不是合并整个分支的所有变更是非常有用的。该命令的基本语法如下:git

探索Java生态系统的其他技术与工具

导言:Java生态系统拥有广泛的技术和工具,其中一些对于开发者来说至关重要。除了核心Java编程语言和开发框架,还有一些其他技术和工具可以帮助开发者更好地构建和管理Java项目。本文将深入探索这些技术和工具,包括Maven和Gradle的项目构建、SonarQube的代码质量管理、Spring框架的应用开发、Hiber

Java面向对象编程

下面关于IP地址的论述中哪个是不正确的()A.用户主机的IP地址可静态分配也可以动态分配B.IP地址有单播地址,也有多播地址C.一个用户主机只能有一个IP地址D.在以太局域网中使用ARP协议查找与一IP地址对应的MAC地址答案:Ctcp套接字中,不会阻塞的是哪一种操作()A.readB.writeC.acceptD.b

Openresty(二十一)ngx.balance和balance_by_lua灰度发布

一openresty实现灰度发布①灰度发布说明:'早期'博客对'灰度'发布的'概念'进行解读,并且对'原生nginx'灰度实现进行讲解后续:主要拿'节点引流'的灰度发布,并且关注'gray灰度策略'相关借鉴②回顾HTTP反向代理流程ngx_http_upstream可'操作'点:根据'负载均衡策略'选择上游的服务器wr

【AI】机器学习——支持向量机(线性模型)

支持向量机是一种二分类算法,通过在高维空间中构建超平面实现对样本的分类文章目录5.1SVM概述5.1.1分类5.2线性可分SVM5.2.1线性可分SVM基本思想5.2.2策略函数间隔几何间隔硬间隔最大化5.2.3原始算法支持向量5.2.4对偶形式算法1.构造并求解对偶问题2.计算参数3.求得分离超平面优点例题5.支持向

基于微信小程序的自习室系统设计与实现,可作为毕业设计

博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W+、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌文章目录1简介2技术栈3需求分析3.1用户需求分析3.1.1学生用户3.1.3管理员用户4数据库设计4.4.1ER图设计4.4.2数据库表设计**第五章

利用 Python PyPDF2库轻松提取PDF文本(及其他高级操作)

当需要从PDF文件中提取文本时,Python中的PyPDF2库是一个非常有用的工具。无论您是需要分析PDF文档中的内容还是需要在文档中搜索特定的信息,PyPDF2都可以帮助您轻松实现这些任务。在本文中,我们将探讨如何使用PyPDF2库提取PDF文件中的文本,并提供一些示例代码来帮助您入门。安装PyPDF2库首先,您需要

手摸手系列之前端Vue实现PDF预览及打印的终极解决方案

前言近期我正在开发一个前后端分离项目,使用了SpringBoot和Vue2,借助了国内优秀的框架jeecg,前端UI库则选择了ant-design-vue。在项目中,需要实现文件上传功能,同时还要能够在线预览和下载图片和PDF文件,甚至需要在页面上直接打印PDF文件。尽管框架自带了vue-print-nb-jeecg组

AI引擎助力,CamScanner智能高清滤镜开启扫描新纪元!

文章目录⭐写在前面⭐突破图像处理难点:扫描全能王的独特优势⭐耳听为虚,眼见为实⭐产品背后的主要核心:AI-Scan助力⭐深度学习助力智能文档处理的国际化进程⭐品味智能文档处理的轻松与精准⭐写在前面在数字化快速发展的今天,我们时常会遇到需要将纸质文件转变为电子文字的场景。无论是工作中的合同、报告,还是日常生活中的笔记、名

热文推荐