MVC 模式和 Spring MVC 介绍
MVC : 是一种用于设计创建web应用表现层的模式,主要作用是将视图展示和业务控制代码分离开来
MVC 使用了三种角色来分别处理不同的功能:
- Model(模型):数据模型(封装对象)
- View(视图):负责数据的展示(html,jsp)
- Controller(控制器):负责调度,用于程序业务逻辑处理
MVC架构跟三层架构的关系:
- MVC把三层架构中的表现层再度进行了分化,分成了控制器、视图、模型。
- 三层架构的目的是解耦,mvc的目的是实现web系统的职责划分。
MVC架构在三层架构中的位置图示:
Spring MVC 介绍
SpringMVC是Spring产品对MVC模式的一种具体实现,属于轻量级的WEB框架。它通过一套简单的注解,让一个普通的 Java 类成为控制器,而无须实现任何接口。同时还支持RestFul风格的编程风格。
SpringMVC的功能就是封装了原来Servlet中的共有功能,例如请求参数解析处理、请求结果封装返回等。
SpringMVC是 Spring 框架(Spring Framework)的子模块,也存在容器的概念(支持 Spring 的注解)。
Spring MVC 入门案例(xml)
SpringMVC依赖
<dependencies>
<!--springmvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<!--前端控制器 servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
开启SpringMVC注解支持
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 包扫描 -->
<context:component-scan base-package="cn.test"></context:component-scan>
<!-- 配置springmvc的注解驱动。内置了处理器映射器和处理器适配器 -->
<mvc:annotation-driven></mvc:annotation-driven>
<!-- 视图解析器 -->
<!--
配置返回页面的前缀和后缀。当需要返回一个视图的时候,只需写视图的名称,视图解析器会自动在该名称上拼接前后缀。
前缀 + 控制器返回值 + 后缀
/WEB-INF/jsps/ + 控制器返回值 + .jsp
-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsps/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
web.xml 中配置前端控制器
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<!-- 配置SpringMVC的前端控制器 DispatcherServlet -->
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--自动加载Springmvc的配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<!-- 处理所有请求,不处理.jsp为后缀的请求 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Controller:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 控制器:需要交给容器管理
* 方法:接受请求,调用service,完成响应和页面跳转
*/
@Controller
public class HelloController {
/**
* 控制器方法:
* 接受请求,调用service,完成响应和页面跳转
* 1、返回值String (跳转的页面路径)
* 2、需要在方法上通过注解@RequestMapping配置进入此方法的url
*
*/
@RequestMapping(value="/hello")
public String hello() {
System.out.println("hello heima23");
return "success"; // /WEB-INF/jsps/ + 控制器返回值 + .jsp
}
}
Spring MVC 原理
web 工程执行过程
- 浏览器发出请求
http://localhost/hello
- Tomcat接收请求,经过请求解析封装出Request和Response对象,然后转交给应用程序
- 配置的DispatcherServlet会拦截到请求的路径
- DispatcherServlet经过一番分析处理,会将请求转发到自定义的Controller上(@RequestMapping)
- Controller经过处理,给出了一个返回路径
- DispatcherServlet拿到这个路径会找到对应的 JSP 进行视图渲染。
Spring MVC 执行流程
- 用户通过浏览器发送请求至DispatcherServlet
- DispatcherServlet收到请求调用HandlerMapping
- HandlerMapping找到具体的处理器链返回给DispatcherServlet
- DispatcherServlet会根据返回的处理器链调用HandlerAdapter
- HandlerAdapter经过适配调用具体的Handler(controller)
- Controller执行完成返回一个执行结果
- HandlerAdapter将Handler的结果ModelAndView对象返回给DispatcherServlet
- DispatcherServlet将ModelAndView对象传给ViewReslover
- ViewResolver解析后得到具体View,并返回给DispatcherServlet
- DispatcherServlet根据View进行视图渲染(即将模型数据填充至视图中)
- DispatcherServlet会将渲染后的视图响应给浏览器
Spring MVC 的四大组件
- 前端控制器(DispatcherServlet):
SpringMVC的核心组件(DispathcherServlet),协调所有组件的运行
- 处理器映射器(HandlerMapping):
负责根据URL请求找到对应的的处理器(Controller中的方法)
- 处理器适配器(HandlerAdapter):
统一适配器接口,对处理器进行了一个封装,可以统一调用。真正的去调用处理方法(执行Controller中的方法)
- 视图解析器 (ViewReslover):
根据逻辑视图匹配到真正的物理视图
物理视图:jsp页面的完整路径
Spring MVC 请求和响应中各组件的执行顺序
Controller 层
映射请求
@RequestMapping 注解
@RequestMapping:用于建立请求URL和处理方法之间的映射关系,也可以通过它的属性对请求做出各种限制
可标注在类、方法上,若用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
属性:
- value / path 属性:指定映射的请求地址,指定的地址可以是URI Template 模式
- method 属性:指定请求的 method 类型, GET、POST、PUT、DELETE等,示例:RequestMethod.post
- params 属性:指定 request 中必须包含的参数,若请求没有携带指定参数,则抛出异常
- headers 属性:指定 request 中必须包含某些指定的header值,才能让该方法处理请求。
- consumes 属性:指定处理请求的提交内容类型(Content-Type),如application/json、text/html;
- produces 属性:指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回
@GetMapping、@PostMapping 等注解
@GetMapping、@PostMapping、@PutMapping、@DeleteMapping 等注解
- Spring MVC 注解 @RequestMapping(method = RequestMethod.GET/POST/PUT/DELETE...) 的缩写
- 用于将 HTTP 映射到特定的处理方法上,简化常用的 HTTP 方法的映射,并更好地表达被注解方法的语义
请求参数的接收与处理
接收请求参数
在SpringMVC中可以使用多种类型来接收前端传入的地址栏参数
简单类型(8种基本类型 \ 8种基本类型的包装类型 \ 字符串)
使用方式:只需要保证前端传递的参数名称跟方法的形参名称一致即可
//处理器中属性名必须和请求参数名一致 @RequestMapping("/simpleParam") public String simpleParam(Integer id, String name){ System.out.println(id); System.out.println(name); return "success"; }
对象(pojo)类型
使用方式:只需要保证前端传递的参数名称跟 pojo 的属性名称(set方法)一致即可,所有的请求参数自动地封装到 java 对象中
@RequestMapping("/dtoParam") public String voParam(UserDTO dto){ System.out.println(dto); return "success"; }
- 数组类型
使用方式:只需要保证前端传递的参数名称跟方法中的数组形参名称一致即可
同名参数传递,自动的将请求参数封装到数组种
- 集合类型
将集合包装到一个对象中即可,自动的将前端传入的数据封装到对象中的集合属性
- 日期类型
在 Spring MVC 中内置了一系列的类型转化器,可以自动的将请求参数的 String 类型转化为某种格式(例如:Integer)
对于一些常见的类型, Spring MVC 是内置了类型转换器的,但是对于一些格式比较灵活的参数(日期 时间),Spring MVC 无法完成类型转换,就需要自定义类型转换器
处理请求参数
@RequestParam 注解
标注在 Controller 方法参数之前,用于对请求中 url 传入的普通参数做一些限制,支持三个属性:
- value / name 属性:默认属性,用于绑定请求传入的参数名称。一般在请求传参和 Controller 方法入参不一致时使用
required 属性:用于指定此参数是否必传
- @RequestParam 修饰的参数默认必须传值,可以用 required = false 来指定非必传值
- defaultValue 属性:指定参数默认值(当参数为非必传参数且请求没有传入该参数时,使用默认值)
@RequestMapping("/list1")
public String test1(int userId) {
return "list";
}
@RequestMapping("/list2")
public String test2(@RequestParam int userId) {
return "list";
}
不加 @RequestParam 注解:
- 前端的参数名需要和后端控制器的变量名保持一致才能生效
- 参数为非必传
接受请求头信息
在控制器中获取当前请求的请求头的方式:
@RequestHeader 注解 配置到方法参数上 : 前端控制器自动获取头信息
- @RequestHeader Map map : 获取所有的请求头
- @RequestHeader("cookie") String cookie : 根据key从所有头信息中获取指定头信息
- @CookieValue("key") :从cookie中根据key获取指定value数据
/**
* 在控制器中获取,当前请求的请求头
*/
@RequestMapping(value="/demo12")
public String demo12(@RequestHeader Map map,
@RequestHeader("cookie") String cookie,
@CookieValue("JSESSIONID") String sessionId) {
System.out.println(map);
System.out.println(cookie);
System.out.println(sessionId);
return "success";
}
文件上传
客户端文件上传的三要素
- form表单的 method = post
- form表单的 enctype=“multipart/form-data”
- from表单中的input的 type = file
springmvc中服务端
文件上传依赖
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency>
需要在SpringMVC的配置文件中,配置文件上传解析器(自动的将上传的内容,转化为MultipartFile对象)
<!-- 文件解析器 id:固定值(multipartResolver) property:其中指定上传文件的大小规则 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!--文件的大小规则 5M = 1024 * 1024 * 5 --> <property name="maxUploadSize" value="5242880"></property> </bean>
在控制器方法上,直接使用MultipartFile对象参数封装上传的文件
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; @Controller public class FileUploadController { /** * 上传文件到服务端。服务端将文件转存到E:\file * MultipartFile :封装上传的文件 * 参数名 :和上传文件的input的name属性一致 */ @RequestMapping("/upload") public String upload(MultipartFile myFile, String name) throws IOException { System.out.println("name=" + name); System.out.println(myFile.getOriginalFilename()); //获取上传文件名 System.out.println(myFile.getSize()); //获取上传文件大小 File file = new File(new File("E:\\file"), myFile.getOriginalFilename()); myFile.transferTo(file); //写入文件内容 return "success"; } }
@RequestBody、@ResponseBody 注解
Ajax + json 实现异步交互
在 Spring MVC 中进行 ajax 的数据交互,可以通过两个注解简化开发
- @RequestBody : 自动的将请求的 json 字符串,转化为指定 java 对象(处理请求)
@ResponseBody :自动的将 java 对象,通过转换器转换为指定的格式(通常为 json 字符串))并响应(处理响应)
- @RestController 注解 = @ResponseBody + @Controller 组合
@RequestMapping("/testAjax") @ResponseBody public User testAjax(@RequestBody User user) { System.out.println("ajax请求的数据对象=" + user); //调用业务逻辑 User user2 = new User(); user2.setUsername("张三"); return user2; }
@RestController 注解: @ResponseBody + @Controller 组合
- @RestController 中包含 @RessponseBody 的注解效果,故该 Controller 中的方法,就无法返回 jsp、html 界面,配置的 InternalResourceViewResolver 不工作,只能返回 return 的内容
注意:
Spring MVC 默认使用 MappingJackson2HttpMessageConverter 对 json 数据进行转换,需要加入 jackson 的包
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency>
- Spring Boot 默认集成了 jackson ,不再需要添加 jackson 相关依赖
Spring MVC 操作 Servlet 对象
Servlet 原生 API 对象:
- HttpServletRequest
- HttpSevletResponse
- HttpSession
语法规则:
方式1(推荐):将对象以方法参数的形式配置到 Controller 方法中
/** * 获取Servlet原生API对象(request,response,session) */ @RequestMapping("/demo2") public String demo2(HttpServletRequest request, HttpServletResponse response, HttpSession session) { request.setAttribute("user", "1"); System.out.println(request); System.out.println(response); System.out.println(session); return "success"; }
- 方式2:将需要的 API 对象通过 @Autowired 的方式注入
Spring MVC 操作 session 域
使用传统方式操作session域
@RequestMapping("/hello13.action")
public String hello13(HttpSession session){
session.setAttribute("prod", "电视机");
return "hello";
}
@RequestMapping("/hello14.action")
public String hello14(HttpSession session){
String prod = (String) session.getAttribute("prod");
System.out.println(prod);
return "hello";
}
使用 @SessionAttribute("name") ,从session里面根据name来获取对应的参数的值
@GetMapping("/test-mvc-get-session")
public String testMvcGetSession(@SessionAttribute("girl") String girl) {
System.out.println("testMvcGetSession -----> girl in session is " + girl);
return "success";
}
使用ModelMap对象从session域中获取值
@RequestMapping("/getValue")
public String getValue(ModelMap modelMap){
System.out.println(modelMap.get("value"));
return "success";
}
清空session域
@RequestMapping("/delValue")
public String delValue(SessionStatus sessionStatus){
//清空session
sessionStatus.setComplete();
return "success";
}
页面跳转
页面跳转之转发
请求转发:只发送一起请求,不会丢失数据(SpringMVC的默认页面跳转形式)
方式1:直接返回逻辑视图
- 底层请求转发经过视图解析器,添加前后缀组成物理视图(页面的完成路径)渲染并跳转
方式2:使用 forward 转发
- 语法规则(返回值): forward: 物理视图
- forward:关键字后面的路径表示不再经过视图解析器
@RequestMapping("/demo3") public String demo3() { System.out.println("forward转发"); return "forward:/WEB-INF/jsps/success.jsp"; }
方式3:使用servlet原生api
- 注意:控制器方法返回值:void
@RequestMapping("/demo3") public void demo3(HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("servlet原生API对象"); request.getRequestDispatcher("/WEB-INF/jsps/success.jsp").forward(request, response); }
请求转发时携带数据
- 转发时携带数据:返回响应数据。
方式1(推荐):将数据绑定到request域
@RequestMapping("/demo4") public String demo4(HttpServletRequest request){ // 将数据绑定到request域 request.setAttribute("username", "张三") return "result" }
页面通过 el 表达式获取响应数据并展示
方式2:绑定到Model对象
Model:SpringMVC中的Model配置到参数上,底层通过Request实现。可以用于替换request完成数据响应
@RequestMapping("/demo4") public String demo4(Model model){ // 将数据绑定到Model对象 model.addAttribute("username", "张三") return "result" }
方式3(官方):通过ModelAndView返回
ModelAndView : 模型视图对象,通过此对象可以指定返回的视图地址和数据绑定
语法规则:
- 方法的返回值:ModelAndView
- 在方法中通过ModelAndView的setViewName方式指定跳转的页面
- 在方法中通过ModelAndView的addObject方式指定需要存入request域中的数据
@RequestMapping("/demo4") public ModelAndView demo4() { ModelAndView mv = new ModelAndView(); // 指定响应数据 mv.addObject("username", "传智播客"); // 配置返回页面视图地址 mv.setViewName("result"); // 支持逻辑视图,支持forward:物理视图 return mv; }
页面跳转之重定向
重定向:通过各种方法将各种网络请求重新定个方向转到其它位置(如:网页重定向、域名的重定向、路由选择的变化也是对数据报文经由路径的一种重定向)。
实现原理:
- 客户端浏览器发送http请求,web服务器接收请求后发送302状态码响应及对应新的location给客户端,客户端发现是302请求,则自动再发送一个新的http请求,请求url是新的location,服务器再根据新的http请求响应客户端。
- 在这里的location可以定义到任意的url,既然是浏览器重新发送了请求,则就没有什么request传递的概念了,在客户端浏览器的路径栏显示的是其重定向的路径,客户端可以观察到路径的变化。
特点:
- 是客户端的行为
- 是浏览器至少做了两次访问请求的
- 浏览器的地址发生改变
- 两次跳转之间的传输的数据丢失(request范围),即 无法从重定向后的页面获取到表单的数值
- 可以重定向到任意的url
重定向的方式:
方式1(推荐):使用 redirect 重定向
@RequestMapping("/demo5") public void demo5(HttpServletRequest request, HttpServletResponse response) throws IOException { System.out.println("第一次请求"); return "redirect:/demo6"; } @RequestMapping("/demo6") public String demo6() { System.out.println("重定向到demo6"); return "success"; }
方式2:使用servlet原生API
@RequestMapping("/demo5") public void demo5(HttpServletRequest request, HttpServletResponse response) throws IOException { System.out.println("第一次请求"); response.sendRedirect("/demo6"); } @RequestMapping("/demo6") public String demo6() { System.out.println("重定向到demo6"); return "success"; }
释放静态资源(不交给 Spring MVC 处理)
当有静态资源需要加载时,比如 jquery.js,通过谷歌开发者工具抓包发现,没有加载到 jquery.js 的原因:
- 现在 Spring MVC 的前端控制器 DispatcherServlet 的 url-pattern 配置的是 /(缺省),代表除了jsp请求不拦截, 其他的所有请求都会拦截,包括一些静态文件(js、html、css、jpg等等),而拦截住之后, 它又找不到对应的处理器方法来处理, 因此报错
释放静态资源方式
方式1(推荐):在Springmvc的配置文件中配置统一释放所有的静态资源文件
- 当SpringMVC处理静态资源时,委托给默认的Servlet处理
- 默认Servlet:tomcat中的默认Servlet
<mvc:default-servlet-handler/>
方式2:在SpringMVC的配置文件中配置释放静态资源
mvc:resources 标签
- mapping 标签:请求路径的URL的映射规则
- location 标签:静态资源的物理目录
当静态资源请求到SpringMVC的前端控制器时,根据释放资源的配置
- 不再查找具体的controller处理
- 从location路径中查找匹配的资源
<mvc:resources mapping="/js/*" location="/js/"></mvc:resources> <mvc:resources mapping="/image/*" location="/image/"></mvc:resources> <mvc:resources mapping="/css/*" location="/css/"></mvc:resources>
方式3:修改
web.xml
中前端控制器的URL映射规则,以特殊字符串结尾的请求交给前端控制器处理servlet-mapping 标签
url-pattern 标签:
- /* :对所有请求有效(包含 jsp 页面)
- / :对所有请求有效(不包含 jsp 页面)
- 需要进入SpringMVC的所有请求,都需要以 .do 结尾
- 控制器方法中 @RequestMapping 不需要再做额外的配置
<servlet-mapping> <servlet-name>mvc</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>