MVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分。
M:Model(模型层)是指工程中的JavaBean,作用是处理数据。JavaBean分为两类
V:View(视图层):指工程中的html或jsp等页面,作用是与用户进行交互,展示数据
C:Controller(控制层):指工程中的servlet,作用是接收请求和响应浏览器
MVC的工作流程
用户通过视图层发送请求到服务器,在服务器中请求被Controller接收,Controller调用相应的Model层处理请求,处理完毕将结果返回到Controller,Controller再根据请求处理的结果找到相应的View视图,渲染数据后最终响应给浏览器。
Spring MVC属于Spring 的后续产品,是Spring的一个子项目。
Spring MVC 是Spring 为表述层开发提供的一整套完整的解决方案。在经历了Struts1(现在一般不用),Struts 2(一般老项目使用)等诸多产品的更迭之后,目前Spring MVC 作为JavaEE项目表述层开发的首选方案
java三层架构分为:表述层、业务逻辑层、数据访问层,其中表述层包含了前台页面和后台servlet
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.ityfcgroupId>
<artifactId>springmvcartifactId>
<version>1.0.0-SNAPSHOTversion>
<packaging>warpackaging>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.3.1version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.3version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.thymeleafgroupId>
<artifactId>thymeleaf-spring5artifactId>
<version>3.0.12.RELEASEversion>
dependency>
dependencies>
project>
2.3 配置web.xml
1)默认配置方式
此配置作用下,SpringMVC的配置文件默认位于 WEB-INF 下,默认名称为 xxx-servlet.xml。例如,以下配置所对应 SpringMVC 的配置文件位于 WEB-INF下,文件名为 SpringMVC-servlet.xml
<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_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>SpringMVCservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>SpringMVCservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
2)扩展配置方式
可通过 init-param 标签设置 SpringMVC 配置文件的位置和名称,通过 load-on-startup 标签设置 SpringMVC 前端控制器 DispatcherServlet 的初始化时间。
<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_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>SpringMVCservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:springMVC.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>SpringMVCservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
2.4 创建请求控制器
@Controller
public class HelloController {
@RequestMapping("/index")
public String index(){
return "index";
}
@RequestMapping("/target")
public String toTarget(){
return "target";
}
}
2.5 创建springMVC配置文件
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.ityang"/>
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<property name="prefix" value="/WEB-INF/templates/"/>
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8" />
bean>
property>
bean>
property>
bean>
beans>
2.6 配置tomcat
配置上下文路径
2.7 创建页面
index.html
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>首页h1>
<a th:href="@{/target}">跳转到目标页面target.htmla>
body>
html>
target.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>helloworldh1>
body>
html>
2.8 启动测试
2.9 总结
浏览器发送请求,若请求地址符合前端控制器的url-pattern,该请求就会被前端控制器DispatcherServlet处理。前端控制器会读取SpringMVC的核心配置文件,通过扫描组件找到控制器,将请求地址和控制器中@RequestMapping注解的value属性值进行匹配,若匹配成功,该注解所标识的控制器方法就是处理请求的方法。处理请求的方法需要返回一个字符串类型的视图名称,该视图名称会被视图解析器解析,加上前缀和后缀组成视图的路径,通过Themeleaf对视图进行渲染,最终转发到视图所在页面。
1、@RequestMapping注解的作用
将请求和处理请求的控制器方法关联起来,建立映射关系。一个请求进来之后会根据此映射关系找到具体的处理方法。
2、@RequestMapping注解的作用位置
标识在类上:设置映射请求的请求路径的初始信息
标识在方法上:设置映射请求的请求路径的具体信息
@Controller
@RequestMapping("/hello")
public class HelloController {
@RequestMapping("/index")
public String index(){
return "index";
}
}
3、@RequestMapping注解的属性
1)value 属性
通过请求的请求地址匹配请求映射,它是一个字符串类型的数组,表示可以设置多个路径。value属性必须设置,至少设置一个路径
@RequestMapping(
value = {"/index","/index1"}
)
public String index(){
return "index";
}
2)method 属性
通过请求的请求方式匹配请求映射,它是一个RequestMethod类型的数组,表示可以设置多个请求方式。如果一个请求满足请求映射的value属性,但是不满足method属性,则浏览器报405
@RequestMapping(
value = {"/index","/index1"},
method = {RequestMethod.GET,RequestMethod.POST}
)
public String index(){
return "index";
}
1、SpringMVC中提供了@RequestMapping 的派生注解:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
2、常用的请求方式有get、post、put、delete
但是目前浏览器只支持get和post请求,即使我们在form表单的method属性设置了put或者delete,也不会生效,会默认按照get请求方式处理。若要发送put和delete请求,则需要通过spring提供的过滤器HiddenHttpMethodFilter(后面会写到)
3)params 属性
通过请求的请求参数匹配请求映射,它是一个字符串类型的数组,可以通过四种表达式设置请求参数和请求映射的匹配关系,比如以下四种情况:
“id”:要求请求的参数必须要有id属性
“!id”:要求请求的参数不能有id属性
“id=1”:要求请求的参数的id属性必须等于1
“id!=1”:要求请求的参数的id属性不能等于1
@RequestMapping(
value = {"/index","/index1"},
method = {RequestMethod.GET,RequestMethod.POST},
params = {"id=1","username"}
)
public String index(){
return "index";
}
4)headers 属性
通过请求的请求头信息匹配请求映射,它是一个字符串类型的数组,可以通过四种表达式设置请求参数和请求映射的匹配关系,比如以下四种情况:
“Host”:要求请求的请求头中必须要有Host
“!Host”:要求请求的请求头中不能有Host
“Host=localhost:8080”:要求请求的请求头中的Host必须等于localhost:8080
“Host!=localhost:8080”:要求请求的请求头中的Host不能等于localhost:8080
及时满足了method和value属性映射,但是不满足headers属性映射,此时页面会报404。
@RequestMapping(
value = {"/index","/index1"},
method = {RequestMethod.GET,RequestMethod.POST},
params = {"id=1","username"},
headers = {"Host=localhost:8080"}
)
public String index(){
return "index";
}
4、SpringMVC支持ant风格的路径
?:表示任意的单个字符
@RequestMapping("/t?st/index")
public String index(){
return "index";
}
*:表示任意的0个或多个字符
@RequestMapping("/t*st/index")
public String index(){
return "index";
}
**:表示任意的一层或多层目录,注意 在使用**时,只能使用/**/xxx的方式
@RequestMapping("/**/index")
public String index(){
return "index";
}
5、SpringMVC支持路径中的占位符
SpringMVC路径中的占位符常用语restful风格,当请求路径中将某些数据通过路径的方式传输到服务器中,就可以在相应的@RequestMapping注解的value属性中通过占位符{xxx}表示传输的数据,再通过@PathVariable注解,将占位符所表示的数据赋值给控制器方法的形参
@RequestMapping("/user/{id}")
public String index(@PathVariable("id")Long id){
return "index";
}
将HttpServletRequest作为控制器方法的形参,此时HttpServletRequest类型的参数表示封装了当前请求的请求报文对象。代码示例如下:
@RequestMapping("/index")
public String index(HttpServletRequest request){
String username = request.getParameter("username");
String password = request.getParameter("password");
return "index";
}
在控制器方法的形参位置,设置和请求参数同名的形参,当浏览器发送请求,匹配到请求映射时,在DispatcherServlet中就会将请求参数赋值给相应的形参
@RequestMapping("/index")
public String index(String username,String password, String[] hobby){
return "index";
}
注意:
String[] hobby 使用数组可以接收多个同名的请求参数,也可以用字符串String hobby接收,springmvc会自动将多个值用逗号进行拼接
作用:将请求参数和控制器方法的形参创建映射关系。
该注解一共有三个属性:
value:指定为形参赋值的请求参数的参数名
required:设置是否必须传输此请求参数,默认值为true
defaultValue:不管required属性值是true还是false,当value所指定的请求参数没有传输时,则使用默认值为形参赋值
@RequestMapping("/index")
public String index(@RequestParam(value = "user_name", required = false, defaultValue = "jack") String username){
return "index";
}
作用:将请求头信息和控制器方法的形参创建映射关系。
该注解一共有三个属性:
value:指定为形参赋值的请求参数的参数名
required:设置是否必须传输此请求参数,默认值为true
defaultValue:不管required属性值是true还是false,当value所指定的请求参数没有传输时,则使用默认值为形参赋值
@RequestMapping("/index")
public String index(@RequestHeader(value = "Host", required = false, defaultValue = "127.0.0.1:8080") String host){
return "index";
}
作用:将cookie数据和控制器方法的形参创建映射关系
该注解一共有三个属性:
value:指定为形参赋值的请求参数的参数名
required:设置是否必须传输此请求参数,默认值为true
defaultValue:不管required属性值是true还是false,当value所指定的请求参数没有传输时,则使用默认值为形参赋值
@RequestMapping("/index")
public String index(@CookieValue(value = "JSESSIONID", required = false, defaultValue = "127.0.0.1:8080") String jsessionid){
return "index";
}
我们可以在控制器方法的形参位置设置一个实体类类型的参数,此时若浏览器传输的请求参数的参数名和实体类中的属性名一致,那么请求参数就会为此属性赋值。
@RequestMapping("/index")
public String index(User user){
return "index";
}
1)GET请求参数乱码问题解决
Tomcat 安装包的conf/server.xml 配置文件中设置编码URIEncoding="UTF-8"
2)POST请求参数乱码问题解决
web.xml 中配置CharacterEncodingFilter
<filter>
<filter-name>CharacterEncodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
init-param>
<init-param>
<param-name>forceResponseEncodingparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>CharacterEncodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
1、使用ServletAPI向request域对象共享数据
@RequestMapping("/target")
public String toTarget(HttpServletRequest request){
request.setAttribute("key","value");
request.getAttribute("key");
request.removeAttribute("key");
return "target";
}
2、使用ModelAndView向request域对象共享数据
@RequestMapping("/target")
public ModelAndView toTarget(){
/**
* ModelAndView有Model和View的功能
* Model主要用于向request域共享数据
* View主要用于设置视图,实现页面跳转
*/
ModelAndView mav = new ModelAndView();
mav.addObject("key","value");
mav.setViewName("target");
return mav;
}
3、使用Model向request域对象共享数据
@RequestMapping("/target")
public String toTarget(Model model){
model.addAttribute("key","value");
return "target";
}
4、使用Map向request域对象共享数据
@RequestMapping("/target")
public String toTarget(Map<String, Object> map){
map.put("key","value");
return "target";
}
5、使用ModelMap向request域对象共享数据
@RequestMapping("/target")
public String toTarget(ModelMap modelMap){
modelMap.addAttribute("key","value");
return "target";
}
6、Model、Map、ModelMap之间的关系
Model、Map、ModelMap类型的参数其实本质上都是 BindingAwareModelMap 类型的
@RequestMapping("/target")
public String toTarget(HttpSession session){
session.setAttribute("key","value");
return "target";
}
@RequestMapping("/target")
public String toTarget(HttpSession session){
ServletContext application = session.getServletContext();
application.setAttribute("key", "value");
return "target";
}
SpringMVC中的视图是View接口,视图的作用是渲染数据,将模型Model中的数据展示给用户。
SpringMVC中的视图种类有很多,默认有转发视图InternalResourceView和重定向视图RedirectView。
当工程引入jstl的依赖时,转发视图会自动转换为jstlView。
若使用的视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymeleaf的视图解析器,由此视图解析器解析所得到的是ThymeleafView
当控制器方法中所设置的视图名称没有任何前缀时,此时的视图名称会被SpringMVC配置文件中配置的视图解析器解析,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过转发的方式实现跳转。
@RequestMapping("/hello")
public String hello(){
return "hello";
}
SpringMVC中默认的转发视图是 InternalResourceView
SpringMVC中创建转发视图的情况:
当控制器方法中所设置的视图名称以forward:
为前缀时,创建 InternalResourceView 视图,此时的视图名称不会被SpringMVC配置文件中配置的视图解析器解析,而是会将前缀forward:
去掉,剩余部分作为最终路径通过转发的方式实现跳转。
@RequestMapping("/hello")
public String hello(){
return "forward:/test";
}
手动配置 InternalResourceView 视图解析器
SpringMVC中默认的重定向视图是 RedirectView
当控制器方法中所设置的视图名称以redirect:
为前缀时,创建 RedirectView视图,此时的视图名称不会被SpringMVC配置文件中配置的视图解析器解析,而是会将前缀redirect:
去掉,剩余部分作为最终路径通过重定向的方式实现跳转。
@RequestMapping("/hello")
public String hello(){
return "redirect:/test";
}
当控制器方法中,仅仅用来实现页面跳转,即只需要设置视图名称时,可以将处理器方法使用view-controller标签进行表示
<mvc:view-controller path="/" view-name="success">mvc:view-controller>
<mvc:annotation-driven />
注:当SpringMVC配置文件中配置了任何一个view-controller,其他控制器中的请求映射将全部失效,此时需要配置一个注解驱动才能解决失效问题
REST:Representational State Transfer,表现层资源状态转移。
1、资源
资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成。每个资源是服务器上一个可命名的抽象概念。因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、数据库中的一张表等等具体的东西,可以将资源设计的要多抽象有多抽象,只要想象力允许而且客户端应用开发者能够理解。与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是名词。一个资源可以由一个或多个URL来标识。URL既是资源的名称,也是资源在Web上的地址。对某个资源感兴趣的客户端应用,可以通过资源的URL与其进行交互。
2、资源的表述
资源的表述是一段对于资源在某个特定时刻的状态的描述。可以在客户端-服务器端之间转移(交换)。资源的表述可以有多种格式,例如HTML/XML/JSON/纯文本/图片/视频/音频等等。资源的表述格式可以通过协商机制来确定。请求-响应方向的表述通常使用不同的格式。
3、状态转移
在客户端和服务器端之间转移(transfer)代表资源状态的表述。通过转移和操作资源的表述,来间接实现操作资源的目的。
简单来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。
它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。
REST风格提倡URL地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为URL地址的一部分,以保证整体风格的一致性。
@PostMapping("/user")
public String addUser(String username,String password) {
System.out.println("username:"+username+",password:"+password);
System.out.println("添加用户");
return "success";
}
@GetMapping("/user")
public String queryUserList() {
System.out.println("查询所有用户");
return "success";
}
@GetMapping("/user/{id}")
public String queryUserById(@PathVariable("id") String id) {
System.out.println("查询id为"+id+"的用户");
return "success";
}
@PutMapping("/user")
public String alterUser(String username,String password) {
System.out.println("username:"+username+",password:"+password);
System.out.println("修改用户");
return "success";
}
@DeleteMapping("/user/{id}")
public String deleteUser(@PathVariable("id") String id) {
System.out.println("删除id为"+id+"的用户");
return "success";
}
由于目前浏览器只支持get和post请求,即使我们在form表单的method属性设置了put或者delete,也不会生效,会默认按照get请求方式处理。若要发送put和delete请求,则需要通过spring提供的过滤器HiddenHttpMethodFilter来解决。
需要在web.xml中添加一个过滤器HiddenHttpMethodFilter
<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">
<filter>
<filter-name>characterEncodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
init-param>
<init-param>
<param-name>forceResponseEncodingparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>characterEncodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
<filter>
<filter-name>HiddenHttpMethodFilterfilter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilterfilter-class>
filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
<servlet>
<servlet-name>dispatcherServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:springMVC.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>dispatcherServletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
对于delete请求超链接处理form表单之类的问题这里不过多阐述了,实际项目中可能就是用的框架处理。
最后贴一个springmvc的主配置
<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 http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com">context:component-scan>
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<property name="prefix" value="/WEB-INF/templates/"/>
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8"/>
bean>
property>
bean>
property>
bean>
<mvc:view-controller path="/" view-name="index"/>
<mvc:annotation-driven/>
<mvc:default-servlet-handler/>
beans>
报文信息转换器(HttpMessageConverter),有两个作用:1、将请求报文转化为java对象;2、将java对象转化为响应报文。
HttpMessageConverter提供了两个注解和两个类供我们使用:@RequestBody、@ResponseBody;RequestEntity、ResponseEntity。
@RequestBody可以获取请求体
,需要在控制器方法
设置一个形参,使用@RequestBody进行标识,当前请求的请求体就会为当前注解所标识的形参赋值。示例如下:将请求体
赋值给当前注解所标识的形参
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class HttpController {
@PostMapping("/testRequestBody")
public ModelAndView testRequestBody(ModelAndView modelAndView, @RequestBody String requestBody){
System.out.println("requestBody:"+requestBody);
modelAndView.setViewName("success");
return modelAndView;
}
}
RequestEntity类是封装请求报文的一种类型,需要在控制器方法的形参中设置该类型的形参,当前请求的请求报文就会赋值给该形参,可以通过getHeaders()方法获取请求头信息,用getBody()获取请求体信息。示例如下:RequestEntity类型参数封装了整个请求报文
@PostMapping("/testRequestEntity")
public ModelAndView testRequestEntity(ModelAndView modelAndView, RequestEntity<String> requestEntity){
System.out.println("request-head:"+requestEntity.getHeaders());
System.out.println("request-body:"+requestEntity.getBody());
modelAndView.setViewName("success");
return modelAndView;
}
@ResponseBody注解用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应浏览器。
1、@ResponseBody注解返回字符串
/**
* 如果没加@ResponseBody注解,字符串会被当做视图名解析,如果加了则直接作为响应体响应给浏览器
**/
@RequestMapping("/testResponseBody")
@ResponseBody
public String testResponseBody(){
return "success";
}
// 相当于原始写法
public void testResponseBody(HttpServletResponse response) throws IOException {
response.getWriter().write("success");
}
2、@ResponseBody注解返回JSON字符串对象
我们直接返回java对象给浏览器是不行的,它不认识整个对象,所以我们需要转换成JSON字符串对象给浏览器,需要做的工作如下:
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.12.3version>
dependency>
代码示例
@RequestMapping("/testResponseUser")
@ResponseBody
public User testResponseUser(){
return new User(1001,"Keeling","10086",18,"男");
}
3、@RestController 组合注解(实际开发中常用
)
这个注解是SpringMVC提供的一个复合注解,标识在控制器的类上,相当于为这个类添加@Controller注解,并且为这个类中所有的方法添加@ResponseBody注解。
ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到服务器的响应报文。(实际开发中我们一般自己封装返回类型)
ResponseEntity实现文件下载(了解)
package com.example.controller;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
@Controller
public class FileUpAndDownController {
@RequestMapping("/testDownload")
public ResponseEntity<byte[]> testDownload(HttpSession session) throws IOException {
/*获取ServletContext对象*/
ServletContext context = session.getServletContext();
/*获取服务器中文件的真实路径*/
String path = context.getRealPath("/static/img/cat.jpg");
/*创建输入流*/
InputStream inputStream = new FileInputStream(path);
/*创建字节数组*/
byte[] buffer = new byte[inputStream.available()];
/*将流读取到字节数组中*/
inputStream.read(buffer);
/*创建HttpHeaders对象设置响应头信息*/
MultiValueMap<String, String> headers = new HttpHeaders();
/*设置下载的方式和文件名*/
headers.add("Content-Disposition", "attachment;filename=hello.jpg");
/*设置响应状态码*/
HttpStatus status = HttpStatus.OK;
/*创建ResponseEntity对象*/
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(buffer,headers,status);
/*关闭输入流*/
inputStream.close();
return responseEntity;
}
}
第一步:添加文件上传需要的依赖
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.4version>
dependency>
第二步:SpringMVC配置文件,添加下面这个Bean
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">bean>
第三步:前端页面
<h2>测试文件上传h2>
<form th:action="@{/testUpload}" method="post" enctype="multipart/form-data">
图片:<input type="file" name="photo"><br>
<input type="submit" value="提交">
form>
第四步:控制器方法
/**
* SpringMVC将我们当前上传的文件封装到MultipartFile中
* 然后使用该对象的transferTo方法实现上传即可
*/
@RequestMapping("/testUpload")
public ModelAndView testUpload(MultipartFile photo, HttpSession session, ModelAndView modelAndView) throws IOException {
/*获取上传的文件名*/
String name = photo.getOriginalFilename();
/*获取上传文件的后缀名*/
String suffix = name.substring(name.lastIndexOf("."));
/*将UUID作为文件名*/
String uuid = UUID.randomUUID().toString();
//将uuid和后缀名拼接后成为最终的文件名
name = uuid + suffix;
/*获取服务器中upload目录的路径*/
ServletContext context = session.getServletContext();
String photoPath = context.getRealPath("upload");
/*判断photoPath所对应路径是否存在*/
File file = new File(photoPath);
//不存在创建目录
if (!file.exists()){
file.mkdir();
}
/*
设置上传后的文件路径(包括文件名)
File.separator表示的是文件的分隔符
*/
String filePath = photoPath + File.separator + name;
/*将该文件上传到服务器*/
photo.transferTo(new File(filePath));
modelAndView.setViewName("success");
return modelAndView;
}
springmvc中的拦截器必须在SpringMVC的配置文件中配置
1、配置全局的拦截器
<mvc:interceptors>
<bean class="ssm.interceptor.HandlerInterceptor1"/>
mvc:interceptors>
2、通过映射路径配置拦截器
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/a"/>
<bean class="ssm.interceptor.HandlerInterceptor1"/>
mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="ssm.interceptor.HandlerInterceptor2"/>
mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="ssm.interceptor.HandlerInterceptor3"/>
mvc:interceptor>
mvc:interceptors>
3、针对具体的HandlerMapping进行配置
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="handlerInterceptor1"/>
<ref bean="handlerInterceptor2"/>
list>
property>
bean>
<bean id="handlerInterceptor1" class="ssm.intercapter.HandlerInterceptor1"/>
<bean id="handlerInterceptor2" class="ssm.intercapter.HandlerInterceptor2"/>
@Component
public class HelloInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("【HelloInterceptor】preHandler...");
// false表示拦截,不向下执行;true表示放行
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("【HelloInterceptor】postHandle....");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("【HelloInterceptor】afterCompletion..");
}
}
拦截器的三个抽象方法
- preHandle():执行控制器方法之前执行。可以用于身份认证、身份授权。比如如果认证没有通过表示用户没有登陆,需要此方法拦截不再往下执行(return false),否则就放行(return true)。
- postHandle():执行控制器方法之后,返回ModelAndView之前执行。可以看到该方法中有个modelAndView的形参。应用场景:从modelAndView出发:将公用的模型数据(比如菜单导航之类的)在这里传到视图,也可以在这里同一指定视图。
- afterCompletion():处理好ModelAndView数据,渲染完视图之后执行。应用场景:统一异常处理,统一日志处理等。
1、情况一:所有拦截器都放行
控制台输出:
HandlerInterceptor1….preHandle
HandlerInterceptor2….preHandle
HandlerInterceptor3….preHandle
HandlerInterceptor3….postHandle
HandlerInterceptor2….postHandle
HandlerInterceptor1….postHandle
HandlerInterceptor3….afterCompletion
HandlerInterceptor2….afterCompletion
HandlerInterceptor1….afterCompletion
总结:
preHandle() 会按照配置文件中的配置顺序从上到下执行,postHandle()和afterCompletion() 则是反序执行
2、情况二:有一个拦截器不放行
控制台输出:
HandlerInterceptor1….preHandle
HandlerInterceptor2….preHandle
HandlerInterceptor3….preHandle
HandlerInterceptor2….afterCompletion
HandlerInterceptor1….afterCompletion
总结:
- 由于拦截器1和2放行,所以拦截器3的preHandle才能执行。也就是说前面的拦截器放行,后面的拦截器才能执行preHandle。
- 拦截器3不放行,所以其另外两个方法没有被执行。即如果某个拦截器不放行,那么它的另外两个方法就不会背执行。
- 只要有一个拦截器不放行,所有拦截器的postHandle方法都不会执行,但是只要执行过preHandle并且放行的,就会执行afterCompletion方法。
1、什么是过滤器(Filter)
依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例,只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,比如:在过滤器中修改字符编码; 在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等。
2、什么是拦截器(interceptor)
拦截器,在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。
在 Webwork的中文文档的解释为——拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行。同时也是提供了一种可以提取action中可重用的部分的方式。
3、拦截器链(多拦截器)
拦截器链的概念:如果多个拦截器能够对相同的请求进行拦截,则多个拦截器会形成一个拦截器链,主要理解拦截器链中各个拦截器的执行顺序。拦截器链中多个拦截器的执行顺序,根拦截器的配置顺序有关,先配置的先执行。
4、区别
过滤器(filter):
1) filter属于Servlet技术,只要是web工程都可以使用
2) filter主要对所有请求过滤
3) filter的执行时机早于Interceptor
拦截器(interceptor)
1) interceptor属于SpringMVC技术,必须要有SpringMVC环境才可以使用
2) interceptor通常对处理器Controller进行拦截
3) interceptor只能拦截dispatcherServlet处理的请求
SpringMVC中拥有一套非常强大的异常处理机制,SpringMVC通过 HandlerExceptionResolver处理程序的异常,包括请求映射,数据绑定以及目标方法的执行时发生的异常。
SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:
HandlerExceptionResolver,该接口的实现类有:DefaultHandlerExceptionResolver和
SimpleMappingExceptionResolver
我们基于 Spring MVC 提供的简单异常处理器 SimpleMappingExceptionResolver 来进行配置指定异常处理。
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArithmeticException">errorprop>
props>
property>
<property name="exceptionAttribute" value="e"/>
bean>
// 将当前类标识为异常处理的组件,是Spring提供的新注解,它是对Controller的增强,可对 controller中被 @RequestMapping注解的方法加一些逻辑处理;
@ControllerAdvice
public class ExceptionController {
// 用于设置所表示的方法处理的异常,@ExceptionHandler加在ControllerAdvice中,处理全局异常
@ExceptionHandler(value = {ArithmeticException.class,NullPointerException.class})
public String exception(Exception e, Model model){
// 向请求域中放异常信息
model.addAttribute("e",e);
// 遇到异常跳转的视图名称
return "error";
}
}
/**
* web工程的初始化类,用来替换web.xml
*/
@Configuration
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 指定Spring的配置类
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{
SpringConfig.class
};
}
/**
* 指定springMVC的配置类
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{
SpringMvcConfig.class
};
}
/**
* 指定DispatchServlet的映射规则,即url-pattern
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* 配置过滤器
*/
@Override
protected Filter[] getServletFilters() {
// 解决post乱码问题
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceRequestEncoding(true);
// 解决无法使用put和delete请求的问题,处理编码的过滤器一定要在最前面,否则会失效
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
}
}
/**
* 代替SpringMVC的配置文件
*
*/
@Configuration
@ComponentScan // 开启扫描组建
@EnableWebMvc // mvc注解驱动
public class SpringMvcConfig implements WebMvcConfigurer {
// ----------- 配置视图解析器 --------------
/**
* 配置生成模板解析器
*/
@Bean
public ITemplateResolver templateResolver() {
WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
ServletContextTemplateResolver servletContextTemplateResolver = new ServletContextTemplateResolver(webApplicationContext.getServletContext());
servletContextTemplateResolver.setPrefix("/WEB-INF/templates/");
servletContextTemplateResolver.setSuffix(".html");
servletContextTemplateResolver.setTemplateMode(TemplateMode.HTML);
servletContextTemplateResolver.setCharacterEncoding("UTF-8");
return servletContextTemplateResolver;
}
/**
* 生成模板引擎并为模板引擎注入模板解析器
*/
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
/**
* 生成视图解析器并未解析器注入模板引擎
*/
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
// ----------- 开启静态资源访问 --------------
/**
* 设置默认servlet对静态资源处理
* @param configurer
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
// ----------- 添加拦截器 --------------
/**
* 设置拦截器和拦截路径
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
MyInterceptor interceptor = new MyInterceptor();
registry.addInterceptor(interceptor).addPathPatterns("/**");
}
// ----------- 配置视图控制器 --------------
/**
* 配置视图的请求路径和视图名
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
// ----------- 配置文件上传解析器 --------------
@Bean
public MultipartResolver multipartResolver(){
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
return commonsMultipartResolver;
}
// ----------- 指定异常处理 --------------
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
properties.setProperty("java.lang.ArithmeticException", "error");
resolver.setExceptionMappings(properties);
resolver.setExceptionAttribute("exception");
resolvers.add(resolver);
}
}
1、DispatcherServlet 前端控制器:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求(框架提供,无需开发)
2、HandlerMapping 处理器映射器:根据请求的url、method等信息查找Handler,即控制器方法(框架提供,无需开发)
3、HandlerAdapter 处理器适配器:通过HandlerAdapter对处理器(控制器方法)进行执行(框架提供,无需开发)
4、Handler 处理器:在DispatcherServlet的控制下Handler对具体的用户请求进行处理(需要开发)
5、ViewResolver 视图解析器:进行视图解析,得到相应的视图(框架提供,无需开发)
6、View 视图:将模型数据通过页面展示给用户(框架提供,无需开发)
1、DispatcherServlet的继承关系图
2、具体的初始化步骤
DispatcherServlet 本质上还是一个Servlet,所以初始化的时候还是遵循着Servlet的初始化过程,Servlet在初始化的过程中,容器将调用servlet的init(ServletConfig config) 方法初始化这个对象,DispatcherServlet 的初始化实际上就是调用Servlet 的 init(ServletConfig config) 方法
在GenericServlet 中 对 init (ServletConfig config) 方法进行了实现,并在 init (ServletConfig config)方法中调用了 init() 方法(方法重载),而在GenericServlet 中并没有对 init() 方法有具体的代码实现,而在HttpServletBean类中对 init() 方法进行了重写
而在HttpServletBean中 调用 init() 方法,其中最主要的是,在 init() 方法中,又调用了initServletBean() 方法,该方法是由 HttpServletBean定义,但是HttpServletBean 并没有对 initServletBean() 方法进行代码实现,而是由它的子类 FrameworkServlet进行重写实现
FrameworkServlet 重写并调用 initServletBean(),在initServletBean()方法中进行了创建并初始化 WebApplicationContext(上下文),并刷新(onRefresh(WebApplication wac) 方法),在FrameworkServlet 中没有对 onRefresh(WebApplication wac) 方法进行具体的代码实现,而是由DispatcherServlet 进行重写并调用 onRefresh(WebApplication wac) 方法,在方法中调用了具体的初始化方法 调用了initStrategies(context)方法,初始化策略,即初始化DispatcherServlet的各个组件
通过 DispatcherServlet 调用 init(ServletConfig config)方法,到最后调用到 initStrategies(context)方法(初始化策略,是 DispatcherServlet 具体的初始化代码,里面对 DispatcherServlet 各种组件进行了初始化)
具体调用过程代码如下
/**
* servlet 接口中的初始化方法
**/
public void init(ServletConfig config) throws ServletException;
/**
* GenericServlet 中对 init (ServletConfig config) 方法进行了实现
**/
public void init(ServletConfig config) throws ServletException {
this.config = config;
// 调用本类中 init() 方法(方法重载),但是没有具体实现
this.init();
}
public void init() throws ServletException {}
/**
* HttpServletBean类中对 GenericServlet 中 init() 方法进行了重写
**/
public final void init() throws ServletException {
PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
this.initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
} catch (BeansException var4) {
if (this.logger.isErrorEnabled()) {
this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
}
throw var4;
}
}
// 核心方法,这里初始化,
this.initServletBean();
}
// HttpServletBean类中重载的方法,但是没具体实现
protected void initServletBean() throws ServletException {}
/**
* FrameworkServlet 重写了 initServletBean()
**/
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
// 核心代码 这里通过调用 initWebApplicationContext() 来初始化并获取 WebApplicationContext对象
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
// 上面方法中调用了本方法初始化 WebApplicationContext
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
// 创建一个 WebApplicationContext 对象,并将它赋给 wac
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
//调用 刷新的方法,该方法具体是在 DispatcherServlet中实现
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
// 该方法具体是在 DispatcherServlet中实现
protected void onRefresh(ApplicationContext context) {
// For subclasses: do nothing by default.
}
/**
* DispatcherServlet 重写了 onRefresh()
**/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
// 进行了一系列初始化
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
1、用户发送请求到前端控制器(DispatcherServlet)。
2、前端控制器 ( DispatcherServlet ) 收到请求调用处理器映射器 (HandlerMapping),去查找处理器(Handler)。
3、处理器映射器(HandlerMapping)找到具体的处理器(可以根据 xml 配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet。
4、前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)。
5、处理器适配器(HandlerAdapter)去调用自定义的处理器类(Controller)。
6、自定义的处理器类(Controller)将得到的参数进行处理并返回结果给处理器适配器(HandlerAdapter)。
7、处理器适配器 ( HandlerAdapter )将得到的结果返回给前端控制器 (DispatcherServlet)。
8、前端控制器(DispatcherServlet )将 ModelAndView 传给视图解析器 (ViewReslover)。
9、视图解析器(ViewReslover)将得到的参数从逻辑视图转换为物理视图并返回给前端控制器(DispatcherServlet)。
10、前端控制器(DispatcherServlet)调用物理视图进行渲染并返回。
11、前端控制器(DispatcherServlet)将渲染后的结果最终返回给用户。