Spring MVC(spring-webmvc)使用指南

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 工程执行过程

在这里插入图片描述

  1. 浏览器发出请求 http://localhost/hello
  2. Tomcat接收请求,经过请求解析封装出Request和Response对象,然后转交给应用程序
  3. 配置的DispatcherServlet会拦截到请求的路径
  4. DispatcherServlet经过一番分析处理,会将请求转发到自定义的Controller上(@RequestMapping)
  5. Controller经过处理,给出了一个返回路径
  6. DispatcherServlet拿到这个路径会找到对应的 JSP 进行视图渲染。

Spring MVC 执行流程

在这里插入图片描述

  1. 用户通过浏览器发送请求至DispatcherServlet
  2. DispatcherServlet收到请求调用HandlerMapping
  3. HandlerMapping找到具体的处理器链返回给DispatcherServlet
  4. DispatcherServlet会根据返回的处理器链调用HandlerAdapter
  5. HandlerAdapter经过适配调用具体的Handler(controller)
  6. Controller执行完成返回一个执行结果
  7. HandlerAdapter将Handler的结果ModelAndView对象返回给DispatcherServlet
  8. DispatcherServlet将ModelAndView对象传给ViewReslover
  9. ViewResolver解析后得到具体View,并返回给DispatcherServlet
  10. DispatcherServlet根据View进行视图渲染(即将模型数据填充至视图中)
  11. 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 原生 API

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 的方式注入


页面跳转之转发

请求转发:只发送一起请求,不会丢失数据(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 操作 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";
}


释放静态资源(不交给 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的前端控制器时,根据释放资源的配置

    1. 不再查找具体的controller处理
    2. 从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>
    

Spring MVC 的全局数据处理(@ControllerAdvice)

@ControllerAdvice:定义 Controller 层全局数据处理类。作用在注解了 @RequestMapping 的控制器方法上

包含注解 @Component,可以被扫描到

一般和以下注解搭配使用

  • @ExceptionHandler(异常处理)
  • @ModelAttribute(数据绑定)
  • @InitBinder(数据预处理)
  • **注意:**这三个注解都可以在普通的 Controller 类上使用,ControllerAdvice 只是作用范围可以自定义(默认全部)

@ControllerAdvice 支持属性:

  • value / basePackages 属性: 数组类型,指定一个或多个包,用来指定可以作用的基包

    即将对指定的包下面的 Controller 及其子包下面的 Controller 起作用

    若不指定,默认所有被扫描的包

  • basePackageClasses 属性: 是 basePackages 的一种变形, 数组类型,指定一个或多个 Controller 类,这些类所属的包及其子包下的所有 Controller 都被该 @ControllerAdvice 管理

  • assignableTypes 属性:数组类型,用来指定具体的 Controller 类型,它可以是一个共同的接口或父类等

  • annotations 属性: 指定一个或多个注解,被这些注解所标记的 Controller 会被该 @ControllerAdvice 管理


异常处理(@ExceptionHandler)

对于异常的处理一般有两种方式:

  • 一种是当前方法处理(try-catch),这种处理方式会造成业务代码和异常处理代码的耦合。

  • 一种是当前方法不处理,出现异常后直接抛给调用者处理。

    使用 Spring 框架,代码最终是由框架来调用的,即异常最终会抛到框架,然后由框架指定异常处理器来统一处理异常。


方式1:通过@ControllerAdvice 和 @ExceptionHandler 注解定义全局异常处理

使用方式:

  • @ControllerAdvice:定义全局处理类,异常监听。标准在 java 类上
  • 定义一个异常处理方法
    • 返回值String (视图)
    • 参数:Model
    • @ExceptionHandler 注解:标注在方法上
      • value 属性:指定当前异常处理器处理的异常类型
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
 * 定义全局异常处理
 */
@ControllerAdvice
public class MyHandlerException2 {
    
    // @Validated参数校验 ,解析BindingResult的错误信息并返回
    @ExceptionHandler(BindException.class)
    @ResponseBody
    public JsonResult exceptionHandler(BindException e, BindingResult result) {
        List<FieldError> fieldErrors = result.getFieldErrors();
        String collect = fieldErrors.stream()
            .map(f -> f.getField()+":"+f.getDefaultMessage())
            .collect(Collectors.joining(","));
        return new JsonResult(JsonResult.Validated_ERROR, collect);
    }

    @ExceptionHandler(value=Exception.class)
    public String handlerException(Model model) {
        model.addAttribute("errorMsg", "运行报错!");
        return "forward:error.jsp";
    }
}

方式2:自定义异常处理器

自定义一个类实现HandlerExceptionResolver接口,重写resolveException方法,将异常处理器交给spring容器管理即可

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 自定义异常处理器
 * 跳转到一个美化的页面,携带错误信息
 */
@Component
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("errorMsg","运行报错!");
        mv.setViewName("forward:error.jsp");
        return mv;
    }
}

方式3:使用web提供的异常机制

在 web.xml 中提供异常处理配置

<!--处理403异常-->
<error-page>
    <error-code>403</error-code>
    <location>/403.jsp</location>
</error-page>
<!--处理404异常-->
<error-page>
    <error-code>404</error-code>
    <location>/404.jsp</location>
</error-page>

数据绑定(@ModelAttribute)

全局数据绑定功能可以用来做一些初始化的数据操作,例如可以将一些公共的数据定义在添加了 @ControllerAdvice 注解的类中,这样,在每一个 Controller 的接口中,就都能够访问到这些数据。

@ModelAttribute 注解标注的方法会在执行目标 Controller 方法之前执行,常与 @ControllerAdvice 配合使用,可以让全局的@RequestMapping 都能获得在此处设置的键值对

@ModelAttribute 注解标注的方法的返回数据是一个全局数据,默认情况下,这个全局数据的 key 就是返回的数据类型,value 就是方法返回值,当然开发者可以通过 @ModelAttribute 注解的 name 属性去重新指定 key。

	// 无返回值方法,不指定@ModelAttribute的name属性
	// 键值对直接放入Model中,可以自定义key,value
    @ModelAttribute()
    public void presetParam(Model model) {
        model.addAttribute("globalAttr", "我是全局参数");
    }

    // 有返回值方法,不指定 @ModelAttribute 的 name 属性
	// 返回值是Map类型,绑定的全局数据键值对的 key 就是 "map",value 为方法的返回值
    @ModelAttribute()
    public Map<String, String> presetParam2() {
        Map<String, String> map1 = new HashMap<String, String>();
        map1.put("key1", "value1");
        return map1;
    }

	// 有返回值方法,指定 @ModelAttribute 的 name 属性
    // 绑定的全局数据键值对的 key 就是 name 属性的值,value 为方法的返回值
    @ModelAttribute(name = "map2")
    public Map<String, String> presetParam3() {
        Map<String, String> map = new HashMap<String, String>();
        map.put("key2", "value2");
        return map;
    }

	// 接受请求参数
    @ModelAttribute()
    public void presetParam4(@RequestParam("name") String name,Model model) {
        model.addAttribute("name", name);
    }

使用

	 //1.使用Model取出
    @GetMapping("model")
    public String methodOne(Model model) {
        Map<String, Object> modelMap = model.asMap();
        System.out.println(modelMap.get("name").toString()); // 传入name的值    
        return modelMap.get("globalAttr").toString();
    }

    //2.使用ModelMap取出
    @GetMapping("modelMap")
    public String methodThree(ModelMap modelMap) {
        return modelMap.get("map").toString();
    }

    //3.@ModelAttribute()指定key,直接取出
    @GetMapping("modelAttribute")
    public String methodTwo(@ModelAttribute("map2") Map map2) {
        return map2.toString();
    }

数据预处理(@InitBinder)

参考:https://blog.csdn.net/wang0907/article/details/108357696

@InitBinder 注解:请求数据预处理,用来设置 WebDataBinder,用于自动绑定前端请求参数到 Model 中

WebDataBinder 中的常用方法有

// 设置字段默认前缀,可用于绑定同属性多对象
public void setFieldDefaultPrefix(String fieldDefaultPrefix)

// 设置无法被接受的字段
public void setDisallowedFields(String... disallowedFields)
    
// 注册自定义编辑器
public void registerCustomEditor(Class<?> var1, PropertyEditor var2)

// 注册校验器
public void addValidators(Validator... validators)

绑定同属性多对象

public class TestController{
    @RequestMapping("/test")
    public String test(User user, Person person){
        System.out.println(user, person);
        return "success"
    }
    
	@InitBinder("user")
    public void initBinderUser(WebDataBinder dataBinder){
        dataBinder.setFieldDefaultPrefix("u.");
    }

    @InitBinder("person")
    public void initBinderPerson(WebDataBinder dataBinder){
        dataBinder.setFieldDefaultPrefix("p.");
    }
}

测试url:http://localhost:8080/test?u.name=aaa&p.name=bbb


注册属性编辑器

在接收参数的时候,对于基础的数据类型,比如接收 String,Int 等类型,SpringMVC 是可以直接处理的,但是对于其他复杂的对象类型,有时候是无法处理的,这时候就需要属性编辑器来进行处理(源数据为String),过程一般就是 String -> 属性编辑器 -> 目标类型。

Spring 提供了一些默认的属性编辑器,例如 org.springframework.beans.propertyeditors.CustomDateEditor,也可以通过继承java.beans.PropertyEditorSuppotr 来根据具体的业务来自定义属性编辑器。

  • 使用默认的属性编辑器

    	@InitBinder
        public void initBinder(WebDataBinder dataBinder){
            /*
             * 创建一个字符串微调编辑器
             * 参数{boolean emptyAsNull}: 是否把空字符串("")视为 null
             */
            StringTrimmerEditor trimmerEditor = new StringTrimmerEditor(true);
            /*
             * 注册自定义编辑器
             * 接受两个参数{Class<?> requiredType, PropertyEditor propertyEditor}
             * requiredType:所需处理的类型
             * propertyEditor:属性编辑器,StringTrimmerEditor就是 propertyEditor的一个子类
             */
            dataBinder.registerCustomEditor(String.class, trimmerEditor);
            //日期格式的字符串转换成Date对象
            dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(
                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), false));
        }
    
  • 自定义属性编辑器

        /**
         * @description:  防止xss注入
         * @params:  String类型转换,将所有传递进来的String进行HTML编码,防止XSS攻击
         */
        @InitBinder
        protected void initBinder2(WebDataBinder binder) {
    
            System.out.println("22222222222222");
    
            //匿名类,自定义属性编辑器 PropertyEditorSupport
            binder.registerCustomEditor(String.class, new PropertyEditorSupport() {
                @Override
                public void setAsText(String text) {
                    setValue(text == null ? null : StringEscapeUtils.escapeHtml4(text.trim()));
                }
                @Override
                public String getAsText() {
                    Object value = getValue();
                    return value != null ? value.toString() : "";
                }
            });
        }
    

注册校验器

  • 自定义校验器

    直接实现 org.springframework.validation.Validator,该接口只有两个方法:

    • 一个是校验是否支持校验的 support(Class<?> clazz) 方法
    • 一个是进行具体校验的 validate(Object target, Errors errors) 方法
    /**
     * 定义一个校验器
     * 该校验器校验用户录入的userName长度是否大于8,并给出响应的错误信息,
     * 错误信息直接设置到errors中,最终会设置到org.springframework.validation.BindingReuslt,
     * 在接口中直接定义该对象则会自动注入对象值,从而可以获取到对应的错误信息。
     */
    @Component
    public class UserValidator implements Validator {
    
    	@Override
    	public boolean supports(Class<?> clazz) {
    		// 只支持User类型对象的校验
    		return User.class.equals(clazz);
    	}
    
    	@Override
    	public void validate(Object target, Errors errors) {
    		User user = (User) target;
    		String userName = user.getUserName();
    		if (StringUtils.isEmpty(userName) || userName.length() < 8) {
    			errors.rejectValue("userName", "valid.userNameLen",
    					new Object[] { "minLength", 8 }, "用户名不能少于{1}位");
    		}
    	}
    }
    
    	@Autowired
    	private UserValidator userValidator;
    
        // 注册校验器
    	@InitBinder
    	private void initBinder(WebDataBinder binder) {
    		binder.addValidators(userValidator);
    	}
    

Spring MVC 拦截器

拦截器是 Spring MVC 提供的一种技术, 它的功能似于过滤器 Filter,它可以在请求进入 Controller 之前,离开 Controller 之后以及页面渲染完毕之后进行拦截

在这里插入图片描述

拦截器和过滤器的区别

  • 过滤器是 Servlet 规范中的一部分,任何 java web 工程都可以使用

    拦截器是 Spring MVC 框架的,只有使用了 Spring MVC 框架的工程才能用

  • 过滤器在 url-pattern 中配置了 /* 之后,可以对所有要访问的资源拦截

    拦截器只会拦截访问的控制器方法,如果访问的是 jsp,js,html,css,image 等资源,它是不拦截的


实现 HnadlerInterceptor 接口 方式

  • 实现 HnadlerInterceptor 接口

  • 实现其中的三个方法(三个拦截点)

    // 进入controller方法之前执行的内容(对请求拦截处理)
    	// 返回值 boolean。true :放行(继续向后执行,进入到controller),false :拦截过滤
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    
    // 执行了controller方法之后,执行的内容(对象响应进行拦截处理)
    public void postHandle(HttpServletRequest request, HttpServletResponse response, 
                           Object handler, ModelAndView modelAndView)
    
    // 页面渲染完成之后,执行(一般不用)
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                                Object handler, Exception ex)
    

基于URL实现的拦截器

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 自定义拦截器
 * 实现HnadlerInterceptor接口。实现其中的三个方法(三个拦截点)
 */
public class MyInterceptor01 implements HandlerInterceptor {

    /**
     * 进入controller方法之前执行的内容(对请求拦截处理)
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String path = request.getServletPath();
        if (path.matches(Const.NO_INTERCEPTOR_PATH)) {
        	//不需要的拦截直接过
            return true;
        } else {
        	// 这写你拦截需要干的事儿,比如取缓存,SESSION,权限判断等
            System.out.println("====================================");
            return true;
        }
    }

    /**
     * 执行了controller方法之后,执行的内容(对象响应进行拦截处理)
     */
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("执行MyInterceptor01的postHandle方法");
    }

    /**
     * 页面渲染完成之后,执行(不用)
     */
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("执行MyInterceptor01的afterCompletion方法");
    }
}

关键代码:path.matches(Const.NO_INTERCEPTOR_PATH 就是基于正则匹配的url

/**
 * 常量类
 */
public class Const {

    public static final String SUCCESS = "SUCCESS";
    public static final String ERROR = "ERROR";
    public static final String FIALL = "FIALL";
    /**********************对象和个体****************************/
    public static final String SESSION_USER = "loginedAgent"; // 用户对象
    public static final String SESSION_LOGINID = "sessionLoginID"; // 登录ID
    public static final String SESSION_USERID = "sessionUserID"; // 当前用户对象ID编号

    public static final String SESSION_USERNAME = "sessionUserName"; // 当前用户对象ID编号
    public static final Integer PAGE = 10; // 默认分页数
    public static final String SESSION_URL = "sessionUrl"; // 被记录的url
    public static final String SESSION_SECURITY_CODE = "sessionVerifyCode"; // 登录页验证码
    // 时间 缓存时间
    public static final int TIMEOUT = 1800;// 秒
	public static final String ON_LOGIN = "/logout.htm";
	public static final String LOGIN_OUT = "/toLogout";
    // 不验证URL anon:不验证/authc:受控制的
    public static final String NO_INTERCEPTOR_PATH =".*/((.css)|(.js)|(images)|(login)|(anon)).*";
}

继承 HandlerInterceptorAdapter 类方式

注意:继承 HandlerInterceptorAdapter 类方式已过时

1.创建注解:

/**
 * 在需要登录验证的Controller的方法上使用此注解
 */
@Target({ElementType.METHOD})	// 可用在方法名上
@Retention(RetentionPolicy.RUNTIME)	// 运行时有效
public @interface LoginRequired {
	
}

2.创建拦截器:

public class MyInterceptor02 extends HandlerInterceptorAdapter{
	
	 @Override
	 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
	 	// 如果不是映射到方法直接通过
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        // 方法注解级拦截器
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        // 判断接口是否需要登录
        LoginRequired methodAnnotation = method.getAnnotation(LoginRequired.class);
        // 有 @LoginRequired 注解,需要认证
        if (methodAnnotation != null) {
            // 拦截需要干的事儿,比如取缓存,SESSION,权限判断等
            System.out.println("====================================");
            return true;
        }
        return true;
	}
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        logger.info("进入到拦截器中:postHandle() 方法中");
        System.out.println(request.getRequestURI());
    }
 
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        logger.info("进入到拦截器中:afterCompletion() 方法中");
        System.out.println(request.getServletPath());
    }
}

注册拦截器

  • 配置类方式

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    import com.*.*.interceptor.AdminInterceptor;
    
    /**
     * 拦截器配置
     */
    @Configuration
    public class InterceptorConfig implements WebMvcConfigurer {
        
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //注册MyInterceptor01拦截器
            InterceptorRegistration registration = registry.addInterceptor(getMyInterceptor01());
            registration.addPathPatterns("/**");                      //所有路径都被拦截
            registration.excludePathPatterns(                         //添加不拦截路径
                                             "登陆路径",            	//登录
                                             "/**/*.html",            //html静态资源
                                             "/**/*.js",              //js静态资源
                                             "/**/*.css",             //css静态资源
                                             "/**/*.woff",
                                             "/**/*.ttf"
                                             );    
        
            // 注册MyInterceptor02拦截器。拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要拦截
            registry.addInterceptor(getMyInterceptor02()).addPathPatterns("/**");
        }
        
        @Bean
    	 public MyInterceptor01 getMyInterceptor01() {
    		 return new MyInterceptor01();
    	 }
    	 
    	 @Bean
    	 public MyInterceptor02 getMyInterceptor02() {
    		 return new MyInterceptor02();
    	 }
    }
    
  • xml方式

    在SpringMVC的配置文件中,添加拦截器配置(配置拦截器对应需要拦截的URL和方法规则)

        <!--配置SpringMVC的拦截器-->
        <mvc:interceptors>
            <!--配置具体的拦截器和拦截器的拦截规则-->
            <mvc:interceptor>
                <!-- mapping : 配置拦截规则。 /** 表示拦截所有  -->
                <mvc:mapping path="/**"/>
                <!-- exclude-mapping: 配置不拦截的规则 -->
                <mvc:exclude-mapping path="/hello/demo2"/>
                <!--创建对象:在当前拦截器中有效-->
                <bean class="cn.test.interceptors.MyInterceptor01"></bean>
            </mvc:interceptor>
        </mvc:interceptors>
    

自定义拦截器链

开发中拦截器可以单独使用,也可以同时使用多个拦截器形成一条拦截器链。

开发步骤和单个拦截器是一样的,只不过注册的时候注册多个,注意这里注册的顺序就代表拦截器执行的顺序。

配置拦截器:多个拦截器链的执行顺序和配置顺序有关

    <!--配置SpringMVC的拦截器-->
    <mvc:interceptors>
        <!--配置具体的拦截器和拦截器的拦截规则-->
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/hello/demo2"/>
            <bean class="cn.test.interceptors.MyInterceptor01"></bean>
        </mvc:interceptor>

        <!--配置具体的拦截器和拦截器的拦截规则-->
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/hello/demo2"/>
            <bean class="cn.test.interceptors.MyInterceptor02"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

拓展

自定义类型转换器

1.自定义一个类型转换器,实现类型转换的方法

import org.springframework.core.convert.converter.Converter;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 自定义的类型转换器
 * 实现Converter接口,<原始类型,目标类型>
 */
public class StringToDateConverter implements Converter<String, Date> {

    /**
     * 日期转化
     *   参数:请求参数中的原始数据
     *   返回值:转换好的数据(目标类型)
     */
    public Date convert(String source) {
        Date date = null;
        try {
            date = new SimpleDateFormat("yyyy-MM-dd").parse(source);
        }catch (Exception e) {
            e.printStackTrace();
        }
        return date;
    }
}

2.将自定义的类型转换注册到SpringMvc的转换服务中,然后再将服务注册到SpringMVC的注解驱动

  • xml方式
    <!--将自定义的类型转化器加入Springmvc的转化器ConversionService的集合中-->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <!--使用集合传入自定义的converter-->
        <property name="converters">
            <set>
                <!--一个bean对应一个类型转换器-->
                <bean class="cn.test.converter.StringToDateConverter"></bean>
            </set>
        </property>
    </bean>

    <!--将Springmvc的转化器注册到springmvc驱动中-->
    <mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
  • 配置类方式
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.GenericConversionService;

@Configuration
public class ConvertConfig {
    @Bean
    public ConversionService genericConversionService(GenericConversionService genericConversionService){
        genericConversionService.addConverter(new StringToDateConverter());
        return genericConversionService;
    }
}

Restful 风格及@PathVariable注解

restful简称REST,全称是Representational State Transfer。

REST是一种软件架构风格, 其强调HTTP应当以资源为中心 ( URL中尽量不要出现动词 )。

它制定了HTTP请求四个动作,分别表示对资源的CRUD操作: GET(获取)、POST(新建)、PUT(更新)、DELETE(删除)

restful 使用方式:

  • 同一个URL,根据不同的请求方式,做不一样的业务处理
  • 自定义地址参数 /PATH/{自定义地址参数名称}
  • 接收REST风格请求地址中占位符的值:@PathVariable(value=“地址参数名称”) 注解
  • 请求方式:GET(获取),POST(新建),PUT(更新),DELETE(删除)
原来Restful
保存/saveUserPOST /user
修改/udpateUser?uid=1PUT /user/1
删除/deleteUser?uid=1DELETE /user/1
查询一个/findUserByUid?uid=1GET /user/1
查询所有/findUserGET /user
@Controller
@RequestMapping("/user")
public class UserController {

    /**
     * 根据id查询
     *  请求地址:/user/5
     *  请求方式:GET
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String findById(@PathVariable(value="id") String id) {
        System.out.println("根据id查询,id=" + id);
        return "success";
    }

    /**
     * 根据id删除
     *  请求地址:/user/5
     *  请求方式:DELETE
     */
    @RequestMapping(value="/{id}", method = RequestMethod.DELETE)
    public String delete(@PathVariable(value="id") String id) {
        System.out.println("根据id删除,id=" + id);
        return "success";
    }

    /**
     * 根据id更新
     *  请求地址:/user/5
     *  请求方式:PUT
     */
    @RequestMapping(value="/{id}", method = RequestMethod.PUT)
    public String update(@PathVariable(value="id") String id) {
        System.out.println("根据id更新,id=" + id);
        return "success";
    }

    /**
     * 保存
     *  请求地址:/user
     *  请求方式:POST
     */
    @RequestMapping(value="/", method = RequestMethod.POST)
    public String save() {
        System.out.println("保存");
        return "success";
    }
}

@RestController注解说明

restful风格多用于前后端绝对分离的项目开发中,这时同步请求将无法使用,所有的处理器都将成为返回数据的异步请求。

这种情况,可以将所有处理器方法上的 @ResponseBody 注解提取到类上去。

然后,进一步可以使用 @RestController 注解来替代 @Controller 和 @ResponseBody 两个注解。

写法如下:

//@Controller
//@ResponseBody
@RestController
@RequestMapping("/day02")
public class Day02Controller {
    ...
}

处理中文乱码

SpringMVC在使用post提交请求时,对于中文参数是有乱码问题的, 针对这种情况它提供了一个中文乱码过滤器,只需要进行配置一下即可。

web.xml配置SpringMVC中文乱码过滤器

	<!--在web.xml中配置过滤器,设置编码即可 CharacterEncodingFilter-->
	<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>
  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨鸦_Cormorant

大家喜欢的话可以点个关注投币哟

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值