文章目录
- [1. MVC 架构](#1. MVC 架构)
- [2. 基于 Servlet 的 MVC 模式](#2. 基于 Servlet 的 MVC 模式)
- [3. Model 1模式 与 Model 2模式](#3. Model 1模式 与 Model 2模式)
-
-
- [Model 1 模式:](#Model 1 模式:)
- [Model 2 模式:](#Model 2 模式:)
-
- [4. MVVM 架构](#4. MVVM 架构)
- [5. Spring MVC](#5. Spring MVC)
- [6. SpringMVC 实现步骤](#6. SpringMVC 实现步骤)
- [8. Controller 的两种实现方式](#8. Controller 的两种实现方式)
- [9. RestFul风格](#9. RestFul风格)
- [10. @ResponseBody](#10. @ResponseBody)
- [11. 乱码解决](#11. 乱码解决)
- [12. 过滤器 VS 拦截器](#12. 过滤器 VS 拦截器)
- 参考
1. MVC 架构
MVC 是 Model、View 和 Controller 的缩写,分别代表 Web 应用程序中的 3 种职责。
-
(Model)模型:用于存储数据以及处理用户请求的业务逻辑。
-
(View)视图:向控制器提交数据,显示模型中的数据。
-
(Controller)控制器:根据视图提出的请求判断将请求和数据交给哪个模型处理,将处理后的有关结果交给哪个视图更新显示。
2. 基于 Servlet 的 MVC 模式
模型:一个或多个 JavaBean 对象,用于存储数据(实体模型,由 JavaBean 类创建)和处理业务逻辑(业务模型,由一般的 Java 类创建)。
视图:一个或多个 JSP 页面,向控制器提交数据和为模型提供数据显示,JSP 页面主要使用 HTML 标记和 JavaBean 标记来显示数据。
控制器:一个或多个 Servlet 对象,根据视图提交的请求进行控制,即将请求转发给处理业务逻辑的 JavaBean,并将处理结果存放到实体模型 JavaBean 中,输出给视图显示。
最典型的MVC就是JSP + servlet + javabean的模式。
3. Model 1模式 与 Model 2模式
Model 1 模式:
-
特点:
- 在 Model 1 模式中,整个应用程序的业务逻辑、数据处理和表示层都在同一个组件中。
- 通常使用 JSP(JavaServer Pages)作为视图层,Java Servlet 用于处理用户请求并在需要时调用 JavaBean(数据模型)来处理业务逻辑。
- 没有明确的分层结构,所有的逻辑都混合在一起,导致代码难以维护和扩展。
-
优点:
- 简单,适用于小型应用程序或快速原型开发。
-
缺点:
- 缺乏分离关注点,使得代码难以理解、测试和维护。
- 不利于团队合作和代码重用。
- 可扩展性较差,难以在应用程序规模增大时进行维护。
Model 2 模式:
-
特点:
- Model 2 模式引入了更严格的分层结构,将应用程序分为模型、视图和控制器三个独立的组件。
- 通常使用 Servlet 作为控制器层,负责接收用户请求并调用适当的模型进行处理,然后将结果传递给视图层进行呈现。
- JSP 通常被用作视图层,但与 Model 1 不同的是,JSP 只负责显示数据,不包含业务逻辑。
-
优点:
- 更好的分离关注点,使代码更易于理解、测试和维护。
- 支持团队合作和代码重用,因为不同的组件可以独立开发和测试。
- 更好的可扩展性,允许应用程序在不同的部分进行修改和扩展,而不影响其他部分。
-
缺点:
- 相对于 Model 1,Model 2 模式需要更多的代码和配置,因此可能增加开发时间和成本。
- 对于小型应用程序或快速原型开发来说,可能会感到过度复杂。
4. MVVM 架构
把View和Contrller都放在了View层(相当于把Controller一部分逻辑抽离了出来),Model层依然是服务端返回的数据模型。
而ViewModel充当了一个UI适配器的角色,也就是说View中每个UI元素都应该在ViewModel找到与之对应的属性。除此之外,从Controller抽离出来的与UI有关的逻辑都放在了ViewModel中,这样就减轻了Controller的负担。
-
View层:视图展示。包含UIView以及UIViewController,View层是可以持有ViewModel的。
-
ViewModel层:视图适配器。暴露属性与View元素显示内容或者元素状态一一对应。一般情况下ViewModel暴露的属性建议是readOnly的,至于为什么,我们在实战中会去解释。还有一点,ViewModel层是可以持有Model的。
-
Model层:数据模型与持久化抽象模型。数据模型很好理解,就是从服务器拉回来的JSON数据。而持久化抽象模型暂时放在Model层,是因为MVVM诞生之初就没有对这块进行很细致的描述。按照经验,我们通常把数据库、文件操作封装成Model,并对外提供操作接口。(有些公司把数据存取操作单拎出来一层,称之为DataAdapter层,所以在业内会有很多MVVM的变种,但其本质上都是MVVM)。
-
Binder:MVVM的灵魂。可惜在MVVM这几个英文单词中并没有它的一席之地,它的最主要作用是在View和ViewModel之间做了双向数据绑定。如果MVVM没有Binder,那么它与MVC的差异不是很大。
例子
- 创建模型(Model) :在这个示例中,我们创建了一个简单的
Person
类,用于表示人员的数据结构。它有两个属性:firstName
和lastName
。
java
public class Person {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
- 创建视图(View) :我们使用JavaFX创建了一个简单的用户界面。
MainView
类继承了JavaFX的Application
类,并实现了start()
方法。在start()
方法中,我们创建了两个文本框(TextField
)用于输入firstName
和lastName
,以及一个按钮(Button
)用于保存数据。这些UI组件被添加到一个垂直布局(VBox
)中,并且展示在舞台(Stage
)上。
java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class MainView extends Application {
private TextField firstNameField;
private TextField lastNameField;
private Button saveButton;
@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("MVVM Example");
firstNameField = new TextField();
lastNameField = new TextField();
saveButton = new Button("Save");
VBox root = new VBox();
root.getChildren().addAll(firstNameField, lastNameField, saveButton);
Scene scene = new Scene(root, 300, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
}
- 创建视图模型(ViewModel) :
PersonViewModel
类负责连接视图和模型。它包含了firstName
和lastName
的字符串属性,并且维护了一个保存按钮(saveButton
)。在构造函数中,我们将保存按钮传递进来,并且设置了一个监听器,以便在firstName
和lastName
字段不为空时激活保存按钮。
java
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.control.Button;
public class PersonViewModel {
private StringProperty firstName = new SimpleStringProperty();
private StringProperty lastName = new SimpleStringProperty();
private Button saveButton;
public PersonViewModel(Button saveButton) {
this.saveButton = saveButton;
// 添加监听器,当firstName和lastName不为空时,激活保存按钮
saveButton.disableProperty().bind(firstName.isEmpty().or(lastName.isEmpty()));
}
public StringProperty firstNameProperty() {
return firstName;
}
public StringProperty lastNameProperty() {
return lastName;
}
}
- 连接视图和视图模型 :在
MainView
类中,我们需要实例化视图模型,并将视图和视图模型连接起来。为了实现这一点,我们需要在start()
方法中创建一个PersonViewModel
对象,并且在按钮点击事件中设置监听器,以便在点击按钮时保存数据。
java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class MainView extends Application {
private TextField firstNameField;
private TextField lastNameField;
private Button saveButton;
@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("MVVM Example");
// 创建视图模型
PersonViewModel viewModel = new PersonViewModel(saveButton);
firstNameField = new TextField();
lastNameField = new TextField();
saveButton = new Button("Save");
// 绑定视图和视图模型的属性
firstNameField.textProperty().bindBidirectional(viewModel.firstNameProperty());
lastNameField.textProperty().bindBidirectional(viewModel.lastNameProperty());
VBox root = new VBox();
root.getChildren().addAll(firstNameField, lastNameField, saveButton);
// 设置保存按钮点击事件
saveButton.setOnAction(event -> {
// 在此处可以执行保存操作,例如将数据传递给模型进行持久化存储
System.out.println("Saving: " + viewModel.getFirstName() + " " + viewModel.getLastName());
});
Scene scene = new Scene(root, 300, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
5. Spring MVC
图为SpringMVC的一个较完整的流程图,实线表示SpringMVC框架提供的技术,不需要开发者实现,虚线表示需要开发者实现。
执行流程
- DispatcherServlet表示前置控制器,是整个SpringMVC的控制中心。用户发出请求,DispatcherServlet接收请求并拦截请求。
假设请求的url为 : http://localhost:8080/SpringMVC/hello
如上url拆分成三部分:
http://localhost:8080:服务器域名
SpringMVC:部署在服务器上的web站点
hello:控制器
通过分析,如上url表示为:请求位于服务器localhost:8080上的SpringMVC站点的hello控制器。
-
HandlerMapping为处理器映射。DispatcherServlet调用HandlerMapping,HandlerMapping根据请求url查找Handler。
-
HandlerExecution表示具体的Handler,其主要作用是根据url查找控制器,如上url被查找控制器为:hello。
-
HandlerExecution将解析后的信息传递给DispatcherServlet,如解析控制器映射等。
-
HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler。
-
Handler让具体的Controller执行。
-
Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView。
-
HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet。
-
DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名。
-
视图解析器将解析的逻辑视图名传给DispatcherServlet。
-
DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图。
-
最终视图呈现给用户。
6. SpringMVC 实现步骤
实现步骤:
-
新建一个web项目
-
导入相关jar包
-
编写web.xml , 注册DispatcherServlet
xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--1.注册servlet-->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--通过初始化参数指定SpringMVC配置文件的位置,进行关联-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<!-- 启动顺序,数字越小,启动越早 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!--所有请求都会被springmvc拦截 -->
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
/*
注意/ 和 /* 的区别:
< url-pattern > / </ url-pattern > 不会匹配到.jsp, 只针对我们编写的请求;
即:.jsp 不会进入spring的 DispatcherServlet类 。
< url-pattern > /* </ url-pattern > 会匹配 *.jsp,
会出现返回 jsp视图 时再次进入spring的DispatcherServlet 类,
导致找不到对应的controller所以报404错。
- 编写springmvc配置文件(在resource目录下添加springmvc-servlet.xml配置文件, 配置的形式与Spring容器配置基本类似,为了支持基于注解的IOC,设置了自动扫描包的功能,具体配置信息如下:)
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 自动扫描包,让指定包下的注解生效,由IOC容器统一管理 -->
<context:component-scan base-package="com.github.subei.controller"/>
<!-- 让Spring MVC不处理静态资源 -->
<mvc:default-servlet-handler />
<!--
支持mvc注解驱动
在spring中一般采用@RequestMapping注解来完成映射关系
要想使@RequestMapping注解生效
必须向上下文中注册DefaultAnnotationHandlerMapping
和一个AnnotationMethodHandlerAdapter实例
这两个实例分别在类级别和方法级别处理。
而annotation-driven配置帮助我们自动完成上述两个实例的注入。
-->
<mvc:annotation-driven />
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/jsp/" />
<!-- 后缀 -->
<property name="suffix" value=".jsp" />
</bean>
</beans>
- 接下来就是去创建对应的控制类 , controller
java
package com.github.subei.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/HelloController")
public class HelloController {
// 真实访问地址 : 项目名/HelloController/hello
@RequestMapping("/hello")
public String sayHello(Model model){
// 向模型中添加属性msg与值,可以在JSP页面中取出并渲染
model.addAttribute("msg","hello,SpringMVC");
// web-inf/jsp/hello.jsp
return "hello";
}
}
- 最后完善前端视图和controller之间的对应, jsp
js
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>SpringMVC</title>
</head>
<body>
${msg}
</body>
</html>
- 测试运行调试.
使用springMVC必须配置的三大件:
- 处理器映射器、处理器适配器、视图解析器
- 通常,只需要手动配置视图解析器 ,而处理器映射器 和处理器适配器 只需要开启注解驱动即可,而省去了大段的xml配置
8. Controller 的两种实现方式
-
实现 Controller 接口并重写handleRequest() 方法,并在springmvc-servlet.xml 添加 该控制器为bean,以及其访问路径;
-
使用注解,@Controller, @RequestMapping("/path")等
9. RestFul风格
在类上直接使用 @RestController ,这样子,里面所有的方法都只会返回 json 字符串了,不用再每一个都添加@ResponseBody !
Restful风格是一种基于标准HTTP方法来设计Web服务的架构风格。它强调使用HTTP协议的各种特性来实现资源的增删改查(CRUD操作),以及对资源的状态转换。下面是Restful风格的一些主要特点:
-
资源(Resources): 在Restful架构中,一切皆资源。资源可以是任何事物,例如用户、商品、订单等等。每个资源都有一个唯一的标识符(URI)来进行访问。
-
HTTP方法(HTTP Methods): ++Restful风格使用HTTP方法来定义操作资源的行为。常用的HTTP方法包括GET(获取资源)、POST(创建资源)、PUT(更新资源)、DELETE(删除资源)等。++
使用RESTful操作资源 :可以通过不同的请求方式来实现不同的效果!如下:请求地址一样,但是功能可以不同!
-
http://127.0.0.1/item/1 查询,GET
-
http://127.0.0.1/item 新增,POST
-
http://127.0.0.1/item 更新,PUT
-
http://127.0.0.1/item/1 删除,DELETE
-
表述(Representation): 资源的表述是指客户端与服务器之间交换的数据格式。通常使用JSON或XML格式来表示资源的状态。@ResponseBody
-
无状态(Stateless): Restful架构是无状态的,即服务器不会保存客户端的状态信息。每个请求都包含足够的信息,使服务器能够理解并处理请求。
-
统一接口(Uniform Interface): Restful架构通过使用统一的接口来简化系统架构,并提高系统的可见性和可伸缩性。这包括使用标准的HTTP方法、URI和媒体类型。
-
资源链接(Resource Linking): Restful架构允许在资源之间建立链接关系,以支持导航和发现。
-
无连接(Stateless): Restful架构是无连接的,即客户端的每个请求都包含足够的信息,使服务器能够理解并处理请求,而不需要依赖之前的请求。
10. @ResponseBody
由于@ResponseBody注解,(return str;)这里会将str转成json格式返回;十分方便
注意:使用json记得处理乱码问题
在类上直接使用 @RestController ,这样子,里面所有的方法都只会返回 json 字符串了,不用再每一个都添加@ResponseBody !
11. 乱码解决
- 在 web.xml 配置字符编码过滤器,以确保所有请求和响应都使用正确的字符编码
xml
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- 自定义过滤器类实现 Filter 接口,重写doFilter() 方法
java
import javax.servlet.*;
import java.io.IOException;
public class EncodingFilter implements Filter {
private String encoding;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
encoding = filterConfig.getInitParameter("encoding");
if (encoding == null) {
encoding = "UTF-8"; // 默认使用UTF-8编码
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
request.setCharacterEncoding(encoding);
response.setCharacterEncoding(encoding);
chain.doFilter(request, response);
}
@Override
public void destroy() {
// 过滤器销毁时的处理
}
}
12. 过滤器 VS 拦截器
区别:
过滤器(Filter)和拦截器(Interceptor)是在 Java Web 开发中用于实现对请求和响应进行处理的两种不同机制。
- 作用层次不同:
- ++过滤器是基于 Servlet 规范++的,工作在 Servlet 层面,可以在请求进入 Servlet 之前或响应离开 Servlet 之后进行处理。
- ++拦截器是 Spring 框架提供的++,工作在 Spring MVC 层面,用于对控制器的请求进行预处理和后处理。
- 所属技术框架不同:
- 过滤器是 Servlet 规范中的一部分,属于 Java EE 的一部分,而且不依赖于任何特定的框架。
- 拦截器是 Spring MVC 框架提供的,用于在 Spring MVC 控制器中进行请求处理的一个机制。
- 调用方式不同:
- 过滤器通过 Servlet 容器的
doFilter()
方法调用,直接在 Servlet 的请求处理链中执行。 - 拦截器则是由 Spring MVC 框架控制器所管理的,通过AOP(面向切面编程)的思想,与控制器方法进行交互。
- 灵活性不同:
-
过滤器的灵活性相对较低,++无法直接访问 Spring 托管的 Bean++,通常用于请求的预处理和后处理。
过滤器的设计初衷是为了处理 Servlet 请求和响应,它们并不属于 Spring 容器的管理范围,因此在过滤器中无法直接通过 Spring 上下文来访问 Spring 托管的 Bean。
-
拦截器则++可以方便地访问 Spring 托管的 Bean++,可以在请求处理前后执行更加灵活的逻辑,例如验证用户权限、记录日志等。
- 作用范围不同:
- ++过滤器作用于 Servlet 的请求处理链中++,可以对所有的请求进行拦截和处理。
- ++拦截器则主要用于对 Spring MVC 控制器的请求++进行处理,因此仅作用于经过 Spring MVC 控制器的请求。