SpringMVC

SpringMVC

Spring Web MVC 是一种基于Java 的实现了Web MVC 设计模式的请求驱动类型的轻量级Web 框架,即使用了MVC 架 构模式的思想,将 web 层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开 发,Spring Web MVC 也是要简化我们日常Web 开发的。

Spring MVC的请求流程

Spring Web MVC 框架也是一个基于请求驱动的Web 框架,并且也使用了前端控制器模式来进行设计,再根据请求映射 规则分发给相应的页面控制器(动作/处理器)进行处理。

核心架构的具体流程步骤

springmvc-003

核心架构的具体流程步骤如下:

  1. 首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行 处理,作为统一访问点,进行全局的流程控制;
  2. DispatcherServlet——>HandlerMapping, HandlerMapping 将会把请求映射为 HandlerExecutionChain 对象(包含一 个Handler 处理器(页面控制器)对象、多个HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新 的映射策略;
  3. DispatcherServlet——>HandlerAdapter,HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器, 即适配器设计模式的应用,从而很容易支持很多类型的处理器;
  4. HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处 理方法,完成功能处理;并返回一个ModelAndView 对象(包含模型数据、逻辑视图名);
  5. ModelAndView 的逻辑视图名——> ViewResolver,ViewResolver 将把逻辑视图名解析为具体的View,通过这种策 略模式,很容易更换其他视图技术;
  6. View——>渲染,View 会根据传进来的Model 模型数据进行渲染,此处的Model 实际是一个Map 数据结构,因此 很容易支持其他视图技术;
  7. 返回控制权给DispatcherServlet,由DispatcherServlet 返回响应给用户,到此一个流程结束。

入门案例

web.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">
    
    <!--    配置spring的编码过滤器-->
    <filter>
        <filter-name>CharacterEncodingFilter</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>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>


<!--    处理请求方式的过滤器-->
    <filter>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>


    
	<!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 -->
    <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.xml</param-value>
        </init-param>
        
        <!--
			作为框架的核心组件,在启动过程中有大量的初始化操作要做
			而这些操作放在第一次请求时才执行会严重影响访问速度
			因此需要通过此标签将启动控制DispatcherServlet的初始化时间提前到服务器启动时
		-->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

springmvc.xml配置

thymeleaf配置

<?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"
       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">

    <context:component-scan base-package="cn.noybzy.controller"></context:component-scan>

    <!-- 配置Thymeleaf视图解析器 -->
    <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:default-servlet-handler/>
    <!-- 开启mvc注解驱动 -->
    <mvc:annotation-driven>
        <mvc:message-converters>
            <!-- 处理响应中文内容乱码 -->
            <bean
                    class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="defaultCharset" value="UTF-8" />
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html</value>
                        <value>application/json</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
</beans>




Controller

package cn.noybzy.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HelloController {

    @RequestMapping("/")
    public String protal(){
        return "index";
    }
}

ant风格路径写法

SpringMVC支持ant风格的路径

?:表示任意的单个字符 

*:表示任意的0个或多个字符

**:表示任意层数的任意目录 **

**注意:在使用**时,只能使用/**/xxx的方式

配置请求映射

@RequestMapping

package org.springframework.web.bind.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {

   String name() default "";

   @AliasFor("path")
   String[] value() default {};

   @AliasFor("value")
   String[] path() default {};
    
   RequestMethod[] method() default {};

   String[] params() default {};

   String[] headers() default {};

   String[] consumes() default {};

   String[] produces() default {};

}
  • method属性

@RequestMapping注解的method属性通过请求的请求方式(get或post)匹配请求映射

method属性是一个RequestMethod类型的**数组,**表示该请求映射能够匹配 多种请求方式的请求

@RequestMapping的派生注解

处理get请求的映射–>@GetMapping

处理post请求的映射–>@PostMapping

处理put请求的映射–>@PutMapping

处理delete请求的映射–>@DeleteMapping

  • params属性

@RequestMapping注解的params属性通过请求的请求参数匹配请求映射

params属性是一个字符串类型的数组,可以通过四种表达式设置请求参数 和请求映射的匹配关系

param”:要求请求映射所匹配的请求必须携带param请求参数

!param”:要求请求映射所匹配的请求必须不能携带param请求参数

param=value”:要求请求映射所匹配的请求必须携带param请求参数且param**=**value

param!=value”:要求请求映射所匹配的请求必须携带param请求参数但是param != value

  • headers属性

@RequestMapping注解的headers属性通过请求的请求头信息匹配请求映射

@RequestMapping注解的headers属性是一个字符串类型的数组,可以通过四种表达式设置请求头信 息和请求映射的匹配关系header”:要求请求映射所匹配的请求必须携带header请求头信息

!header”:要求请求映射所匹配的请求必须不能携带header请求头信息

header=value”:要求请求映射所匹配的请求必须携带header请求头信息且header = value

header!=value”:要求请求映射所匹配的请求必须携带header请求头信息且header != value

@RequestMapping(
	value = {"/testRequestMapping", "/test"}
	,method = {RequestMethod.GET, RequestMethod.POST}
	,params = {"username","password!=123456"}
    ,headers = {"..."}
)

获取请求参数

通过原生ServletAPI获取参数

将HttpServletRequest作为控制器方法的形参,此时HttpServletRequest类型的参数表示封装了当前请 求的请求报文的对象

@RequestMapping("/testParam")
	public String testParam(HttpServletRequest request){
	String username = request.getParameter("username");
	String password = request.getParameter("password");
	System.out.println("username:"+username+",password:"+password);
	return "success";
}

通过Controller方法形参获取

在控制器方法的形参位置,设置和请求参数同名的形参,当浏览器发送请求,匹配到请求映射时,在 DispatcherServlet中就会将请求参数赋值给相应的形参

@RequestMapping("/testParam")
	public String testParam(String username, String password){
	System.out.println("username:"+username+",password:"+password);
	return "success";
}

@RequestParam

请求参数和控制方法的形参绑定

3个参数

  • value:参数名
  • required:是否必须传入此参数(默认true)
  • defaultValue:默认值
java
@RequestMapping("/testParam")
	public String testParam(@RequestParam(value="name",required=true,defaultValue="null")String username, String password){
	System.out.println("username:"+username+",password:"+password);
	return "success";
}

@RequestHeader

获取请求头

将请求头信息和控制器方法的形参创建映射关系

  • value:参数名
  • required:是否必须传入此参数(默认true)
  • defaultValue:默认值

@CookieValue

获取Cookie

将cookie数据和控制器方法的形参创建映射关系

  • value:参数名
  • required:是否必须传入此参数(默认true)
  • defaultValue:默认值

通过pojo获取参数

可以在控制器方法的形参位置设置一个实体类类型的形参,

此时若浏览器传输的请求参数的参数名和实体类中的**属性名 ** 一致,那么请求参数就会为此属性赋值

@RequestMapping("/testpojo")
	public String testPOJO(User user){
	System.out.println(user);
	return "success";
}

解决获取请求参数的乱码问题

解决获取请求参数的乱码问题,

使用SpringMVC提供的编码过滤器CharacterEncodingFilter,在web.xml中进行注册

SpringMVC中处理编码的过滤器一定要配置到其他过滤器之前,否则无效

<!--配置springMVC的编码过滤器-->
<filter>
	<filter-name>CharacterEncodingFilter</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>CharacterEncodingFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

域对象共享数据

使用ServletAPI-Request域

@RequestMapping("/testServletAPI")
	public String testServletAPI(HttpServletRequest request){
	request.setAttribute("testScope", "hello,servletAPI");
	return "success";
}

使用ModelAndView-Request域

@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
	/**
	* ModelAndView有Model和View的功能
	* Model主要用于向请求域共享数据
	* View主要用于设置视图,实现页面跳转
	*/
	ModelAndView mav = new ModelAndView();
	//向请求域共享数据
	mav.addObject("testScope", "hello,ModelAndView");
	//设置视图,实现页面跳转
	mav.setViewName("success");
	return mav;
}

使用Model-request域

Model、ModelMap、Map类型的参数其实本质上都是 BindingAwareModelMap 类型的

@RequestMapping("/testModel")
	public String testModel(Model model){
	model.addAttribute("testScope", "hello,Model");
	return "success";
}

### 使用ModelMap-request域

@RequestMapping("/testModelMap")
	public String testModelMap(ModelMap modelMap){
	modelMap.addAttribute("testScope", "hello,ModelMap");
	return "success";
}

使用Map-request域

@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){
	map.put("testScope", "hello,Map");
	return "success";
}

向session域共享数据

ServletAPI获取

@RequestMapping("/testSession")
	public String testSession(HttpSession session){
	session.setAttribute("testSessionScope", "hello,session");
	return "success";
}

向application域共享数据

@RequestMapping("/testApplication")
	public String testApplication(HttpSession session){
	ServletContext application = session.getServletContext();
	application.setAttribute("testApplicationScope", "hello,application");
	return "success";
}

SpringMVC视图

SpringMVC中的视图是View接口,视图的作用渲染数据

Thymeleaf视图解析器

若使用的视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymeleaf的视图解析器,由此视 图解析器解析之后所得到的是ThymeleafView

<!-- 配置Thymeleaf视图解析器 -->
    <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>

转发视图

forward

@RequestMapping("/testForward")
public String testForward(){
	return "forward:/testHello";
}

重定向视图

redirect

@RequestMapping("/testRedirect")
public String testRedirect(){
	return "redirect:/testHello";
}

视图控制器

springmvc.xml

<mvc:view-controller path="/testView" view-name="success"></mvc:view-controller>

注: 当SpringMVC中设置任何一个view-controller时,其他控制器中的请求映射将全部失效,此时需 要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签:

<mvc:annotation-driven />

RESTful

REST:Representational State Transfer,表现层资源状态转移。

HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。

它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。

请求方式过滤器

SpringMVC 提供了 HiddenHttpMethodFilter 帮助我们将 POST 请求转换为 DELETEPUT 请求

a>当前请求的请求方式必须为post

b>当前请求必须传输请求参数**_method**

请求参数**_method**的值才是最终的请求方式

在web.xml中注册时,必须先注册CharacterEncodingFilter,再注册HiddenHttpMethodFilter

web.xml

<!--    处理请求方式的过滤器-->
    <filter>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

获取RESTful的请求参数

@RequestMapping(value = "/employee/{id}", method = RequestMethod.GET)
public String getEmployeeById(@PathVariable("id") Integer id, Model model){
	sout(id);
	return "";
}

SpringMVC处理ajax请求

@RequestBody

@RequestBody可以获取请求体信息,

使用@RequestBody注解标识控制器方法的形参,当前请求的请 求体就会为当前注解所标识的形参赋值

@RequestMapping("/test/RequestBody")
public String testRequestBody(@RequestBody String requestBody){
	System.out.println("requestBody:"+requestBody);
	return "success";
}

@RequestBody获取json格式的请求参数

使用jackson的依赖,

将json数据直接转换成map集合或者对象…

直接在控制器方法的形参接受

//将json格式的数据转换为map集合
@RequestMapping("/test/RequestBody/json")
public void testRequestBody(@RequestBody Map<String, Object> map,
	HttpServletResponse response) throws IOException {
	System.out.println(map);
	//{username=admin, password=123456}
	response.getWriter().print("hello,axios");
}
//将json格式的数据转换为实体类对象
@RequestMapping("/test/RequestBody/json")
public void testRequestBody(@RequestBody User user, HttpServletResponse
	response) throws IOException {
	System.out.println(user);
	//User{id=null, username='admin', password='123456', age=null,gender='null'}
	response.getWriter().print("hello,axios");
}

@ResponseBody

@ResponseBody用于标识一个控制器方法,

可以将该方法的返回值直接作为响应报文的响应体响应到 浏览器

不会被视图解析器解析

@RequestMapping("/testResponseBody")
@ResponseBody
public String testResponseBody(){
	//此时响应浏览器数据success
	return "success";
}

@RestController注解

@RestController注解是springMVC提供的一个复合注解,

标识在控制器的类上,就相当于为类添加了 @Controller注解,并且为其中的每个方法添加了@ResponseBody注解

返回json数据

加上**@ResponseBody注解后返回( return )对象或者集合直接响应到浏览器的是json格式**的数据

spring上传和下载

文件下载

使用ResponseEntity实现下载文件的功能

@RequestMapping("/testDown")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
	//获取ServletContext对象
	ServletContext servletContext = session.getServletContext();
	//获取服务器中文件的真实路径
	String realPath = servletContext.getRealPath("/static/img/1.jpg");
	//创建输入流
	InputStream is = new FileInputStream(realPath);
	//创建字节数组
	byte[] bytes = new byte[is.available()];
	//将流读到字节数组中
	is.read(bytes);
	//创建HttpHeaders对象设置响应头信息
	MultiValueMap<String, String> headers = new HttpHeaders();
	//设置要下载方式以及下载文件的名字
	headers.add("Content-Disposition", "attachment;filename=1.jpg");
	//设置响应状态码
	HttpStatus statusCode = HttpStatus.OK;
	//创建ResponseEntity对象
	ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers,
	statusCode);
	//关闭输入流
	is.close();
	return responseEntity;
}

文件上传

文件上传要求form表单的请求方式必须为post,并且添加属性enctype=“multipart/form-data” SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息

  • 添加依赖

    <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
    <dependency>
    	<groupId>commons-fileupload</groupId>
    	<artifactId>commons-fileupload</artifactId>
    	<version>1.3.1</version>
    </dependency>
    
  • springmvc配置文件添加配置

    <!--必须通过文件解析器的解析才能将文件转换为MultipartFile对象-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    </bean>
    
  • 控制器

    @RequestMapping("/testUp")
    public String testUp(MultipartFile photo, HttpSession session) throws IOException {
    	//获取上传的文件的文件名
    	String fileName = photo.getOriginalFilename();
    	//处理文件重名问题
    	String hzName = fileName.substring(fileName.lastIndexOf("."));
    	fileName = UUID.randomUUID().toString() + hzName;
    	//获取服务器中photo目录的路径
    	ServletContext servletContext = session.getServletContext();
    	String photoPath = servletContext.getRealPath("photo");
        File file = new File(photoPath);
    	if(!file.exists()){
    		file.mkdir();
    	}
    	String finalPath = photoPath + File.separator + fileName;
    	//实现上传功能
    	photo.transferTo(new File(finalPath));
    	return "success";
    }
    

    拦截器

SpringMVC中的拦截器用于拦截控制器方法的执行

SpringMVC中的拦截器需要实现HandlerInterceptor

SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置:

<bean class="com.atguigu.interceptor.FirstInterceptor"> </bean>
<ref bean="firstInterceptor"></ref>
<!-- 以上两种配置方式都是对DispatcherServlet所处理的所有的请求进行拦截 -->
<mvc:interceptor>
	<mvc:mapping path="/**"/> 
	<mvc:exclude-mapping path="/testRequestEntity"/>
	<ref bean="firstInterceptor"></ref>
</mvc:interceptor>
<!--
以上配置方式可以通过ref或bean标签设置拦截器,通过mvc:mapping设置需要拦截的请求,通过
mvc:exclude-mapping设置需要排除的请求,即不需要拦截的请求
-->

拦截器的三个抽象方法

preHandle:控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返 回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法

postHandle:控制器方法执行之后执行postHandle()

afterCompletion:处理完视图和模型数据,渲染视图完毕之后执行afterCompletion()

异常处理

SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:

HandlerExceptionResolver

接口的实现类有:DefaultHandlerExceptionResolverSimpleMappingExceptionResolver

SpringMVC提供了自定义的异常处理器SimpleMappingExceptionResolver,使用方式:

  • 使用xml配置异常解析器
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
	<property name="exceptionMappings">
		<props>
		<!--
		properties的键表示处理器方法执行过程中出现的异常
		properties的值表示若出现指定异常时,设置一个新的视图名称,跳转到指定页面
		-->
		<prop key="java.lang.ArithmeticException">error</prop>
		</props>
	</property>
<!--
exceptionAttribute属性设置一个属性名,将出现的异常信息在请求域中进行共享
-->
	<property name="exceptionAttribute" value="ex"></property>
</bean>

  • 使用注解的异常处理
//@ControllerAdvice将当前类标识为异常处理的组件
@ControllerAdvice
public class ExceptionController {
	//@ExceptionHandler用于设置所标识方法处理的异常
	@ExceptionHandler(ArithmeticException.class)
	//ex表示当前请求处理中出现的异常对象
	public String handleArithmeticException(Exception ex, Model model){
		model.addAttribute("ex", ex);
		return "error";
	}
}