目录
大家好,今天给大家介绍一下mvc框架
官方对Spring MVC的描述是: Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从⼀开始就包含在 Spring 框架中。它的 正式名称“Spring Web MVC”来⾃其源模块的名称(Spring-webmvc),但它通常被称为Spring MVC.
在控制层框架历经Strust、WebWork、Strust2等诸多产品的历代更迭之后,目前业界普遍选择了SpringMVC作为Java EE项目表述层开发的首选方案。之所以能做到这一点,是因为SpringMVC具备如下显著优势:
MVC 是 Model View Controller 的缩写,它是软件⼯程中的⼀种软件架构设计模式,它把软件系统分 为模型、视图和控制器三个基本部分
•View(视图) 指在应⽤程序中专⻔⽤来与浏览器进⾏交互,展⽰数据的资源.
• Model(模型) 是应⽤程序的主体部分,⽤来处理程序中数据逻辑的部分.
• Controller(控制器)可以理解为⼀个分发器,用来决定对于视图发来的请求,需要⽤哪⼀个模型来处理,以及处理完后需要跳回到哪⼀个视图。即 ⽤来连接视图和模型
SpringMVC的作用主要覆盖的是表述层,例如:
简单来说就是 1. 简化前端参数接收( 形参列表 ) 2. 简化后端数据响应(返回值)
@RequestMapping注解的作用就是将请求的 URL 地址和处理请求的方式(handler方法)关联起来,建立映射关系。
SpringMVC 接收到指定的请求,就会来找到在映射关系中对应的方法来处理这个请求。
源码解析
@RequestMapping 注解可以用于类级别和方法级别,它们之间的区别如下:
1. 设置到类级别:@RequestMapping 注解可以设置在控制器类上,用于映射整个控制器的通用请求路径。这样,如果控制器中的多个方法都需要映射同一请求路径,就不需要在每个方法上都添加映射路径。
2. 设置到方法级别:@RequestMapping 注解也可以单独设置在控制器方法上,用于更细粒度地映射请求路径和处理方法。当多个方法处理同一个路径的不同操作时,可以使用方法级别的 @RequestMapping 注解进行更精细的映射。
引出进阶注解
注意:进阶注解只能添加到handler方法上,无法添加到类上!
接收单个参数, 在 Spring MVC 中直接⽤⽅法中的参数就可以,⽐如以下代码:
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
- @RestController
- @RequestMapping("/param")
- public class ParamController {
- @RequestMapping("/m1")
- public String method1(String name){
- return "接收到参数name:"+ name;
- }
- }
浏览器发送请求 http://127.0.0.1:8080/param/m1?name=spring
保证参数一致,即可自动接收
如何接收多个参数呢? 和接收单个参数⼀样, 直接使⽤⽅法的参数接收即可. 使⽤多个形参.
- @RequestMapping("/m2")
- public Object method2(String name, String password) {
- return "接收到参数name:" + name + ", password:" + password;
- }
使⽤浏览器发送请求并传参: http://127.0.0.1:8080/param/m2name=zhangsan&password=123456
如果参数⽐较多时, ⽅法声明就需要有很多形参. 并且后续每次新增⼀个参数, 也需要修改⽅法声明. 我们不妨把这些参数封装为⼀个对象. Spring MVC 也可以⾃动实现对象参数的赋值,⽐如 Person 对象:
- public class Person {
- private int id;
- private String name;
- private String password;
-
- public int getId() {
- return id;
- }
-
- public void setId(int id) {
- this.id = id;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public String getPassword() {
- return password;
- }
-
- public void setPassword(String password) {
- this.password = password;
- }
-
- @Override
- public String toString() {
- return "Person{" +
- "id=" + id +
- ", name='" + name + '\'' +
- ", password='" + password + '\'' +
- '}';
- }
- }
传递对象代码实现
- @RequestMapping("/m3")
- public Object method3(Person p){
- return p.toString();
- }
http://127.0.0.1:8080/param/m3?id=5&name=zhangsan&password=123456
某些特殊的情况下,前端传递的参数 key 和我们后端接收的 key 可以不⼀致,⽐如前端传递了⼀个 time 给后端,⽽后端是使⽤ createtime 字段来接收的,这样就会出现参数接收不到的情况,如果出现 这种情况,我们就可以使⽤ @RequestParam 来重命名前后端的参数值
- @RequestMapping("/m4")
- public Object method_4(@RequestParam("time") String createtime) {
- return "接收到参数createtime:" + createtime;
- }
http://127.0.0.1:8080/param/m4?time=2023-09-12
1. 使⽤ @RequestParam 进⾏参数重命名时, 请求参数只能和 @RequestParam 声明的名称⼀ 致, 才能进⾏参数绑定和赋值.
2. 使⽤ @RequestParam 进⾏参数重命名时, 参数就变成了必传参数.
⾮必传参数设置
如果我们的实际业务前端的参数是⼀个⾮必传的参数, 针对上述问题, 如何解决呢? 先来了解下参数必传的原因, 我们查看 @RequestParam 注解的实现细节就可以发现端倪,注解 实现如下:
- @Target({ElementType.PARAMETER})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface RequestParam {
- @AliasFor("name")
- String value() default "";
- @AliasFor("value")
- String name() default "";
- boolean required() default true;
- String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n"
- }
可以看到 required 的默认值为true, 表⽰含义就是: 该注解修饰的参数默认为必传 既然如此, 我们可以通过设置 @RequestParam 中的 required=false 来避免不传递时报错
Spring MVC 可以⾃动绑定数组参数的赋值
- @RequestMapping("/m5")
- public String method5(String[] arrayParam) {
- return Arrays.toString(arrayParam);
- }
集合参数:和数组类似, 同⼀个请求参数名有为多个, 且需要使⽤ @RequestParam 绑定参数关系 默认情况下,请求中参数名相同的多个值,是封装到数组. 如果要封装到集合,要使⽤ @RequestParam 绑定参数关系
- @RequestMapping("/m6")
- public String method6(@RequestParam List
listParam) { - return "size:"+listParam.size() + ",listParam:"+listParam;
- }
接收JSON对象, 需要使⽤ @RequestBody
注解 RequestBody: 请求正⽂,意思是这个注解作⽤在请求正⽂的数据绑定,请求参数必须在写在请求正 ⽂中
- @RequestMapping(value = "/m7")
- public Object method7(@RequestBody Person person) {
- return person.toString();
- }
path variable: 路径变量
默认传递参数写在URL上,SpringMVC就可以获取到
- @RequestMapping("/m8/{id}/{name}")
- public String method8(@PathVariable Integer id, @PathVariable("name") String use
- return "解析参数id:"+id+",name:"+userName;
- }
- @RequestMapping("/m9")
- public String getfile(@RequestPart("file") MultipartFile file) throws IOExceptio
- //获取⽂件名称
- String fileName = file.getOriginalFilename();
- //⽂件上传到指定路径
- file.transferTo(new File("D:/temp/" + file.getOriginalFilename()));
- return "接收到⽂件名称为: "+fileName;
- }
可以使用 @CookieValue 注释将 HTTP Cookie 的值绑定到控制器中的方法参数
- @GetMapping("/demo")
- public void handle(@CookieValue("JSESSIONID") String cookie) {
- //...
- }
- @GetMapping("/demo")
- public void handle(
- @RequestHeader("Accept-Encoding") String encoding,
- @RequestHeader("Keep-Alive") long keepAlive) {
- //...
- }
1.方法上使用@ResponseBody注解
可以在方法上使用 @ResponseBody注解,用于将方法返回的对象序列化为 JSON 或 XML 格式的数据,并发送给客户端。在前后端分离的项目中使用
2.类上使用@ResponseBody注解
如果类中的每一个方法标记了@ResponseBody注解就可以提取到类上
- @ResponseBody //responseBody可以添加到类上,代表默认类中的所有方法都生效!
- @Controller
- @RequestMapping("param")
- public class ParamController {
- }
这个注解是一个合并注解 @RestController = @ResponseBody + @Controller
SpringMVC涉及组件理解:
先来看一下DispatcherServlet的继承体系
关注三个类分别是 DispatcherServlet FrameworkServlet HttpServletBean
首先来看init()方法,在HttpServletBean中进行实现
initServletBean()方法
this.webApplicationContext = initWebApplicationContext(); 初始化web容器
对MVC九大组件进行初始化分别是:
1.初始化文件上传解析器MultipartResolver: 从应⽤上下⽂中获取名称为multipartResolver的 Bean,如果没有名为multipartResolver的Bean,则没有提供上传⽂件的解析器
2. 初始化区域解析器LocaleResolver:从应⽤上下⽂中获取名称为localeResolver的Bean,如果没 有这个Bean,则默认使⽤AcceptHeaderLocaleResolver作为区域解析器
3. 初始化主题解析器ThemeResolver:从应⽤上下⽂中获取名称为themeResolver的Bean,如果 没有这个Bean,则默认使⽤FixedThemeResolver作为主题解析器
4. 初始化处理器映射器HandlerMappings:处理器映射器作⽤,1)通过处理器映射器找到对应的 处理器适配器,将请求交给适配器处理;2)缓存每个请求地址URL对应的位置(Controller.xxx ⽅法);如果在ApplicationContext发现有HandlerMappings,则从ApplicationContext中获取 到所有的HandlerMappings,并进⾏排序;如果在ApplicationContext中没有发现有处理器映射
5. 初始化处理器适配器HandlerAdapter:作⽤是通过调⽤具体的⽅法来处理具体的请求;如果在 ApplicationContext发现有handlerAdapter,则从ApplicationContext中获取到所有的 HandlerAdapter,并进⾏排序;如果在ApplicationContext中没有发现处理器适配器,则默认 SimpleControllerHandlerAdapter作为处理器适配器
6. 初始化异常处理器解析器HandlerExceptionResolver:如果在ApplicationContext发现有 handlerExceptionResolver,则从ApplicationContext中获取到所有的 HandlerExceptionResolver,并进⾏排序;如果在ApplicationContext中没有发现异常处理器解 析器,则不设置异常处理器
7. 初始化RequestToViewNameTranslator:其作⽤是从Request中获取viewName,从 ApplicationContext发现有viewNameTranslator的Bean,如果没有,则默认使⽤ DefaultRequestToViewNameTranslator
8. 初始化视图解析器ViewResolvers:先从ApplicationContext中获取名为viewResolver的Bean, 如果没有,则默认InternalResourceViewResolver作为视图解析器
9. 初始化FlashMapManager:其作⽤是⽤于检索和保存FlashMap(保存从⼀个URL重定向到另⼀ 个URL时的参数信息),从ApplicationContext发现有flashMapManager的Bean,如果没有,则 默认使⽤DefaultFlashMapManager
以上就是init()方法的内容,我们来看Service()方法
service() 方法中真正的核心是doDispatch() 方法
-
-
- protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
- HttpServletRequest processedRequest = request;
- HandlerExecutionChain mappedHandler = null;
- boolean multipartRequestParsed = false;
-
- WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
-
- try {
- ModelAndView mv = null;
- Exception dispatchException = null;
-
- try {
- processedRequest = checkMultipart(request);
- multipartRequestParsed = (processedRequest != request);
-
- // Determine handler for the current request.
- mappedHandler = getHandler(processedRequest);
- if (mappedHandler == null) {
- noHandlerFound(processedRequest, response);
- return;
- }
-
- // Determine handler adapter for the current request.
- HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
-
- // Process last-modified header, if supported by the handler.
- String method = request.getMethod();
- boolean isGet = HttpMethod.GET.matches(method);
- if (isGet || HttpMethod.HEAD.matches(method)) {
- long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
- if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
- return;
- }
- }
-
- if (!mappedHandler.applyPreHandle(processedRequest, response)) {
- return;
- }
-
- // Actually invoke the handler.
- mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
-
- if (asyncManager.isConcurrentHandlingStarted()) {
- return;
- }
-
- applyDefaultViewName(processedRequest, mv);
- mappedHandler.applyPostHandle(processedRequest, response, mv);
- }
- catch (Exception ex) {
- dispatchException = ex;
- }
- catch (Throwable err) {
- // As of 4.3, we're processing Errors thrown from handler methods as well,
- // making them available for @ExceptionHandler methods and other scenarios.
- dispatchException = new ServletException("Handler dispatch failed: " + err, err);
- }
- processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
- }
- catch (Exception ex) {
- triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
- }
- catch (Throwable err) {
- triggerAfterCompletion(processedRequest, response, mappedHandler,
- new ServletException("Handler processing failed: " + err, err));
- }
- finally {
- if (asyncManager.isConcurrentHandlingStarted()) {
- // Instead of postHandle and afterCompletion
- if (mappedHandler != null) {
- mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
- }
- }
- else {
- // Clean up any resources used by a multipart request.
- if (multipartRequestParsed) {
- cleanupMultipart(processedRequest);
- }
- }
- }
- }
以上就是这篇博客的主要内容了,大家多多理解,下一篇博客见!