Spring MVC

 

1、什么是MVC

  mvc是Web层的一种设计模式,Model、View 和Controller 的缩写,分别代表 Web 应用程序中的 3 种职责。

  • 模型:用于存储数据以及处理用户请求的业务逻辑。
  • 视图:向控制器提交数据,显示模型中的数据。
  • 控制器:根据视图提出的请求判断将请求和数据交给哪个模型处理,将处理后的有关结果交给哪个视图更新显示。

  基于 Servlet 的 MVC 模式的具体实现如下:

  • 模型:一个或多个 JavaBean 对象,用于存储数据(实体模型,由 JavaBean 类创建)和处理业务逻辑(业务模型,由一般的 Java 类创建)。

  • 视图:一个或多个 JSP 页面,向控制器提交数据和为模型提供数据显示,JSP 页面主要使用 HTML 标记和 JavaBean 标记来显示数据。

  • 控制器:一个或多个 Servlet对象,根据视图提交的请求进行控制,即将请求转发给处理业务逻辑的 JavaBean,并将处理结果存放到实体模型 JavaBean 中,输出给视图显示。

  基于 Servlet 的 MVC 模式的流程如图所示:

 

 

  JSP 中的 MVC 模式

  spring对web层的支持主要是提供了Spring MVC 

2、Spring MVC概述

  Spring Web MVC是基于Servlet API构建的原始Web框架,并从一开始就包含在Spring Framework中。正式名称“ Spring Web MVC”来自其源模块spring-webmvc的名称, 但它通常被称为“ Spring MVC”。

  简而言之,springMVC对servlet进行封装,避免繁琐的获取表单参数,多余的serlvet服务类等代码

3、Spring MVC的执行流程

4、Spring MVC的内部执行流程

 

 Spring MVC 执行流程

Tomcat启动阶段

1、Tomcat启动加载web.xml配置文件,web.xml中配置了一个servlet,如果servlet中的<load-on-startup>1</load-on-startup>指定了1,说明servlet的实例化和初始化的方法将会直接执行

2、通过加载web.xml配置文件,实例化初始化DispatcherServlet

3、DispatcherServlet只要初始化完成会去加载SpringMVC配置文件(配置文件一加载Spring容器就启动了,在SpringMVC中配置的处理器映射器、处理器适配器、视图解析器以及Controller都将实例化完成存放到容器中)

浏览器访问阶段

例如浏览器访问http://localhost:8080/login

1、浏览器通过域名端口找到本机的tomcat,然后通过Tomcat发送访问/login这个路径的请求

2、这个请求会进入DispatcherServlet,DispatcherServlet会找到处理器映射器

3、处理器映射器找到要执行的类与方法(执行链),并把执行链返回给DispatcherServlet

4、DispatcherServlet拿到结果,找到处理器适配器

5、处理器适配器去执行对应的方法

6、对应的方法返回ModelAndView给处理器适配器

7、处理器适配器把拿到的结果(ModelAndView)返回给DispatcherServlet

8、DispatcherServlet拿到结果(ModelAndView)之后,找到视图解析器,请求进行视图解析

9、视图解析器根据ModelAndView,拼接前后缀,找到视图真正的地址(View)并把这个地址(View)返回给了DispatcherServlet

10、此时DispatcherServlet有数据(ModelAndView)和视图的真正地址(View),就会找到对应的模板引擎(JSP),通过模板引擎(使用JSP中的EL表达式等)进行视图渲染,将模型数据填充到request域

11、最后前端展示

5、Spring MVC的基本使用

5.1、创建web工程并导入相关jar包

spring-jcl-5.1.9.RELEASE
spring-webmvc-5.1.9.RELEASE
spring-web-5.1.9.RELEASE
spring-core-5.1.9.RELEASE
spring-context-5.1.9.RELEASE
spring-aop-5.1.9.RELEASE
spring-expression-5.1.9.RELEASE
spring-beans-5.1.9.RELEASE

5.2、创建Controller类

public class MyController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        ModelAndView mv  = new ModelAndView();
        mv.addObject("msg","hello Springmvc");
        mv.setViewName("success");
        return mv;
    }
}

5.3、创建success页面

  在WEB-INF下创建pages文件夹,在文件夹下创建success页面

5.4、编写配置文件stringmvc.xml

    <!--myController 交给Spring管理 添加到容器-->
    <bean name="/myController" class="top.ftime.wk.controller.MyController"/>
    <!--        配置处理器映射器-->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
    <!--        配置处理器适配器-->
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    <!--    配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--    配置前缀-->
        <property name="prefix" value="/WEB-INF/pages/"/>
        <!--    配置后缀-->
        <property name="suffix" value=".jsp"/>
    </bean>

5.5、配置web.xml

    <servlet>
        <!--        顺便起名-->
        <servlet-name>flowerTime</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--
        初始化参数加载配置文件
        注意:key是固定写法  必须写contextConfigLocation
        -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>flowerTime</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

6、Spring MVC视图解析器

  Spring 视图解析器是 Spring MVC 中的重要组成部分,用户可以在配置文件中定义 Spring MVC 的一个视图解析器(ViewResolver),示例代码如下:

    <!--    配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--    配置前缀-->
        <property name="prefix" value="/WEB-INF/pages/"/>
        <!--    配置后缀-->
        <property name="suffix" value=".jsp"/>
    </bean>

 

  上述视图解析器配置了前缀和后缀两个属性,因此在Spring MVC基本使用教程中的 MyController 控制器类的视图路径仅需提供ViewName("success"),视图解析器将会自动添加前缀和后缀。

  InternalResourceViewResolver 是 URLBasedViewResolver 的子类,所以 URLBasedViewResolver 支持的特性它都支持。

  在实际应用中 InternalResourceViewResolver 也是使用的最广泛的一个视图解析器。那么InternalResourceViewResolver有什么自己独有的特性呢?

  单从字面意思来看,我们可以把 InternalResourceViewResolver 解释为内部资源视图解析器,这就是 InternalResourceViewResolver 的一个特性。

  InternalResourceViewResolver 会把返回的视图名称都解析为 InternalResourceView 对象,InternalResourceView 会把 Controller 处理器方法返回的模型属性都存放到对应的 request 属性中,然后通过 RequestDispatcher 在服务器端把请求 forword 重定向到目标 URL。

  比如在 InternalResourceViewResolver 中定义了 prefix=/WEB-INF/,suffix=.jsp,然后请求的 Controller 处理器方法返回的视图名称为 login,那么这个时候 InternalResourceViewResolver 就会把 login 解析为一个 InternalResourceView 对象,先把返回的模型属性都存放到对应的 HttpServletRequest 属性中,然后利用 RequestDispatcher 在服务器端把请求 forword 到 /WEB-INF/test.jsp。

  这就是 InternalResourceViewResolver 一个非常重要的特性,我们都知道存放在 /WEB-INF/ 下面的内容是不能直接通过 request 请求的方式请求到的,为了安全性考虑,我们通常会把 jsp 文件放在 WEB-INF 目录下,而 InternalResourceView 在服务器端跳转的方式可以很好的解决这个问题。

7、@Controller注解、@RequestMapping注解和配置文件的优化

7.1、@Controller注解

  在Spring MVC基本使用教程中,我们定义的MyController只能做一件事,因为就一个实现方法,并且每次都要单独配置一个对应的Bean,这种方式不是很优雅,在前面Spring的学习中我们学习了使用@Component注解的方式把当前类添加到容器中,此注解在Spring MVC中同样适用,但是我们不仅要让当前类添加到容器中,还要让Spring MVC指导这是一个Controller类,是一个控制器类,因此在@Component注解的延伸下有了@Controller注解

@Controller注解的使用:

  首先在Spring MVC配置文件中开启包扫描

<!-- 使用扫描机制扫描控制器类,控制器类都在controller包及其子包下 -->
<context:component-scan base-package="top.ftime.wk.controller"/>

 

  然后在相应的控制器类上添加@Controller注解

7.2、@RequestMapping注解

  在基于注解的控制器类中可以为每个请求编写对应的处理方法。那么如何将请求与处理方法一一对应呢?

  需要使用 org.springframework.web.bind.annotation.RequestMapping 注解类型将请求与处理方法一一对应。

请求相关的注解

@GetMapping        Get请求
@DeleteMapping      Delete请求
@PutMapping         Put请求
@PostMapping          Post请求
@RequestMapping       Request请求

  @RequestMapping注解能匹配所有请求
@Controller
public class FirstController {


    /**
     * GetMapping注解表示此方法只能接收get请求  参数:表示请求的路径
     * @return
     */
    @GetMapping("login")
    public ModelAndView login(){
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("flowerTime","hello login");
        modelAndView.setViewName("first");
        return modelAndView;
    }
    /**
     * PostMapping注解表示此方法只能接收post请求  参数:表示请求的路径
     * @return
     */
    @PostMapping("res")
    public ModelAndView res() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("flowerTime", "hello res");
        modelAndView.setViewName("first");
        return modelAndView;
    }

}

   @RequestMapping注解可以标注在类上,在类级别注解的情况下,控制器类中的所有方法都将映射为类级别的请求,用户可以使用如下URL访问login方法。

http://localhost:8080/springMVCDemo02/index/login

  类级别注解的示例代码如下:
package controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/index")
public class IndexController {
    @RequestMapping("/login")
    public String login() {
        return "login";
    }

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

   为了方便维护程序,建议开发者采用类级别注解,将相关处理放在同一个控制器类中。例如,对商品的增、删、改、查处理方法都可以放在 GoodsOperate 控制类中。

7.3、配置文件的优化

<?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"
       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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">


    <!--myController 交给Spring管理 添加到容器-->
<!--    <bean name="/myController" class="top.ftime.wk.controller.MyController"/>-->
    <!--        配置处理器映射器-->
<!--    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>-->
    <!--        配置处理器适配器-->
<!--    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>-->


    <!--
    配置包扫描
    并在类上添加@component的延伸注解@controller
    在方法上添加请求方法相关的注解,用什么方式就写什么方式的注解,参数表示的是请求的路径
    常用请求相关的注解
    - @GetMapping           Get请求
    - @DeleteMapping        Delete请求
    - @PutMapping           Put请求
    - @PostMapping          Post请求
    - @RequestMapping       Request请求
    -->
    <context:component-scan base-package="top.ftime.wk.controller"/>

    <!--开启MVC的注解驱动,这行的配置等效于配置“处理器映射器”和“处理器适配器-->
    <mvc:annotation-driven>

    <!--    配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--    配置前缀-->
        <property name="prefix" value="/WEB-INF/pages/"/>
        <!--    配置后缀-->
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>

 

8、Spring MVC获取参数

8.1、基本数据类型和字符串类型

  基本数据类型和字符串类型参数的获取分为两种情况:

表单中input标签name属性的值,也就是请求参数的key是否与方法形参的名字一致

  1:一致时,会自动把数据封装上

  2:不一致时,通过@RequestParam()注解进行绑定,注解的参数要和表单中input标签name的属性值一致

 /**
     * 基本数据类型和字符串类型
     * 方法的形参与表单的name属性,也就是和请求参数的key一致时,会自动把数据封装
     * 不一致时,通过@RequestParam("表单的name")注解进行绑定
     * @param age
     * @param username
     * @return
     */
    @GetMapping("basicParam")
    public ModelAndView basicParams(int age ,@RequestParam("name") String username){

        System.out.println(age);
        System.out.println(username);
        ModelAndView modelAndView =new ModelAndView();
        modelAndView.setViewName("first");
        return modelAndView;

    }

 

8.2、数组类型

/**
     * 数组类型
     * 方法的形参要与表单的name属性一致
     * @param hobby
     * @return
     */
    @GetMapping("arrayParam")
    public ModelAndView arrayParams(String [] hobby){

        System.out.println(Arrays.toString(hobby));

        ModelAndView modelAndView =new ModelAndView();
        modelAndView.setViewName("first");
        return modelAndView;
    }

 

8.3、引用类型(对象)

    /**
     * 对象
     * 表单name属性要和实体类中的属性名一致,和controller方法中的接收对象名无关
     * 用实体类接收
     * @param user
     * @return
     */
    @PostMapping("userParam")
    public ModelAndView userParams(User user){

        System.out.println(user);

        ModelAndView modelAndView =new ModelAndView();
        modelAndView.setViewName("first");
        return modelAndView;
    }

8.4、包装类型(对象作为成员变量)

 /**
     * 包装类类型
     * 前端表单中name的属性值要写 "包装类属性名.实体类的属性"
     * 用包装类接收
     * @param admin
     * @return
     */
    @GetMapping("adminParam")
    public ModelAndView adminParams(Admin admin){
        System.out.println(admin);
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("first");
        return modelAndView;
    }

 

8.5、List集合类型

8.6、Map集合类型

8.7、josn格式字符串

  json格式字符串参数的接收需要第三方的支持(例如Jackson),我们需要导入相关的第三方jar包。

注意:

  json格式字符串转对象有很多库可以使用,比如:json-lib、fastjson、Gson、Jackson等,由于Spring默认使用Jackson,只需要导入Jackson的jar包就行了,不需要进行任何配置,如果使用其他的第三方支持,需要配置比较麻烦,不如直接使用Jackson省事。

    /**
     * 接收json格式字符串
     * 导入Jackson的jar包  @RequestBody 注解表示把请求体中的json格式的字符串转换成对象,依赖jackson
     * @param user
     * @return
     */
    @PostMapping("jsonParam")
    public ModelAndView jsonParams(@RequestBody User user){
        System.out.println(user);
        return null;
    }

   Jackson相关jar包

  jackson-annotations-2.9.8
  jackson-databind-2.9.8
  jackson-core-2.9.8

 

8.8、其他

 

  通过 @PathVariable 获取 URL 中的参数,控制器类示例代码如下:

@Controllerpublic class UserController {
    @RequestMapping("/user")
    /**
     * 通过@PathVariable获取URL的参数
     */
    public String register(@PathVariable String uname,@PathVariable String upass,Model model) {
        if ("zhangsan".equals(uname)
                && "123456".equals(upass)) {
            logger.info("成功");
            return "login"; // 注册成功,跳转到 login.jsp
        } else {
            // 在register.jsp页面上可以使用EL表达式取出model的uname值
            model.addAttribute("uname", uname);
            return "register"; // 返回 register.jsp
        }
    }
}

 

  
  通过 @ModelAttribute 注解接收请求参数适用于 get 和 post 提交请求方式, @ModelAttribute 注解放在处理方法的形参上时,用于将多个请求参数封装到一个实体对象,从而简化数据绑定流程,而且自动暴露为模型数据,在视图页面展示时使用。
@RequestMapping("/register")
public String register(@ModelAttribute("user") UserForm user) {
    if ("zhangsan".equals(uname) && "123456".equals(upass)) {
        logger.info("成功");
        return "login"; // 注册成功,跳转到 login.jsp
    } else {
        logger.info("失败");
        // 使用@ModelAttribute("user")与model.addAttribute("user",user)的功能相同
        //register.jsp页面上可以使用EL表达式${user.uname}取出ModelAttribute的uname值
        return "register"; // 返回 register.jsp
    }
}
  
  “通过实体 Bean 接收请求参数”中只是将多个请求参数封装到一个实体对象,并不能暴露为模型数据(需要使用 model.addAttribute 语句才能暴露为模型数据)。

9、乱码问题

9.1、get请求接收参数乱码

  如果你的get请求传递中文参数时有乱码可以有如下解决方式
  • 第一种方式

 

 String s = new String(username.getBytes("ISO-8859-1"), "utf-8");

 

 

 

  • 第二种方式

 

/**
 修改 comcat中的config目录下 server.xml
 添加  URIEncoding="utf-8"

 注意点:如果有 useBodyEncodingForURI="true"  请干掉

 例如:
     <Connector port="8080" protocol="HTTP/1.1"
        connectionTimeout="20000"
        redirectPort="8443"
        disableUploadTimeout="true"
        executor="tomcatThreadPool"
        URIEncoding="utf-8"/>

 */ 
  • 第三种方式(高版本支持)

同post解决乱码方式

9.2、post请求接收参数乱码

  post请求也不一定会出现乱码问题,要看post请求参数的位置
  如果参数的位置在请求体中,会产生乱码;如果参数的位置在请求头中,则不会产生乱码。
  在web.xml中配置拦截器
 <!--解决Post乱码问题的拦截器CharacterEncodingFilter  
在高版本的Spring中是可以解决 get请求 和Post请求 、 低版本 比如说4.X中 只能解决Post请求 不能解决get请求
--> <filter> <filter-name>Charac</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> </filter> <filter-mapping> <filter-name>Charac</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

10、获取ServletAPI

想要在代码中得到最原始的HttpServletRequest和HttpServletREsponse可以直接在方法里写个形参就行了,然后就可以直接使用了

11、Controller的返回值类型

    /**
     * Controller的返回值类型
     * 1、ModelAndView
     * 2、String
     *      JSP页面
     *      普通字符串
     *      转发
     *      重定向
     * 3、对象
     * 4、void
     * 5、ResponseEntity
     */

11.1、返回ModelAndView

    /**
     * 返回ModelAndView
     * ModelAndView携带了数据,返回JSP页面,在JSP页面可以使用EL表达式取出
     * @return
     */
    @PostMapping("modelAndView")
    public ModelAndView fun1() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("flowerTime", "落花无意,流水无情");
        modelAndView.setViewName("first");
        return modelAndView;
    }

11.2、返回字符串

11.2.1、返回字符串之返回JSP页面
    /**
     * JSP页面
     *不携带数据
     * @return
     */
    @PostMapping("String_1")
    public String fun2() {
//返回first.jsp页面
return "first"; } /** * model方式携带数据,实际上是把Model中的数据放到了请求域中 * @return */ @PostMapping("String_1") public String fun2(Model model) { model.addAttribute("flowerTime","花火");
     //返回first.jsp页面
return "first"; } /** * map方式携带数据,实际上也是把Map中的数据放到了请求域中 * * @return */ @PostMapping("String_1") public String fun2(Map map) { map.put("flowerTime", "打上花火");
     //返回first.jsp页面
return "first"; }
11.2.2、返回字符串之返回普通字符串
    /**
     * 返回真正的字符串
     * 注解@ResponseBody表示返回的是字符串而不是页面
     * 中文乱码可以使用produces属性解决
     *
     * @return
     */
    @ResponseBody
    @GetMapping(value = "String_2", produces = "text/html;charset=utf-8")
    public String fun3() {
     //返回到浏览器上的是字符串
return "神秘花园"; }

若要返回json格式的字符串,produces属性值为“application/json;charset=utf-8”

  produces属性只能解决局部的中文乱码,如果想统一设置,需在spring.xml中配置统一的字符编码

    <!--开启MVC的注解驱动,这行的配置等效于配置“处理器映射器”和“处理器适配器”-->
    <mvc:annotation-driven>
        <!--    统一设置全局输出的字符编码-->
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=utf-8</value>
                        <value>application/json;charset=utf-8</value>
                        <value>*/*;charset=utf-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

 

11.3、转发和重定向

  重定向是将用户从当前处理请求定向到另一个视图(例如 JSP)或处理请求,以前的请求(request)中存放的信息全部失效,并进入一个新的 request 作用域。
  
  转发是将用户对当前处理的请求转发给另一个视图或处理请求,以前的 request 中存放的信息不会失效。
  
  转发是服务器行为,重定向是客户端行为。

1)转发过程
  客户浏览器发送 http 请求,Web 服务器接受此请求,调用内部的一个方法在容器内部完成请求处理和转发动作,将目标资源发送给客户;在这里转发的路径必须是同一个 Web 容器下的 URL,其不能转向到其他的 Web 路径上,中间传递的是自己的容器内的 request。

  在客户浏览器的地址栏中显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。
2)重定向过程
  客户浏览器发送 http 请求,Web 服务器接受后发送 302 状态码响应及对应新的 location 给客户浏览器,客户浏览器发现是 302 响应,则自动再发送一个新的 http 请求,请求 URL 是新的 location 地址,服务器根据此请求寻找资源并发送给客户。

  在这里 location 可以重定向到任意 URL,既然是浏览器重新发出了请求,那么就没有什么 request 传递的概念了。在客户浏览器的地址栏中显示的是其重定向的路径,客户可以观察到地址的变化。重定向行为是浏览器做了至少两次的访问请求。

在SpringMVC中,控制器类中处理方法的return语句默认就是转发实现,只不过是实现的是转发到视图。

  在SpringMVC中,转发与重定向的示例代码如下:
    /**
     * 转发
     *
     * @return
     */
    @GetMapping("forward")
    public String fun4() {
        System.out.println("使用了转发");
        return "forward:String_2";
    }

    /**
     * 重定向
     *
     * @return
     */
    @GetMapping("redirect")
    public String fun5() {
        System.out.println("使用了重定向");
        return "redirect:String_2";
    }

11.4、返回对象

返回对象 需要使用jackson的支持 是把对象转换成json了 实际返回的是json格式字符串

    /**
     * 返回对象
     * 返回对象需要使用Jackson的支持,是把对象装换成json,实际上返回的是json格式字符串
     * @return
     */
    @ResponseBody
    @GetMapping("object")
    public User fun6(){
        User user = new User();
        user.setName("tom");
        user.setAge(12);
        user.setFlag(false);
        return user;
    }

11.5、返回void

无返回值并不是代表就不返回,无返回值默认是找你配置的路径下的void.JSP页面,所以一般可以在无返回值的方法里转发或者重定向

    /**
     * 无返回值
     * 无返回值默认是找你配置的路径下的void,JSP页面
     * 所以一般可以在无返回值的方法里转发或者重定向‘’
     */
    @GetMapping("void")
    public void fun7(){

    }

11.6、返回ResponseEntity

一般返回ResponseEntity更符合规范,因为ResponseEntity内部提供状态码,其实返回的也是一个json格式的字符串,因此也依赖Jackson
    @GetMapping("requestEntity")
    public ResponseEntity<User> fun8(){
        User user = new User();
        user.setName("tom");
        user.setAge(12);
        user.setFlag(false);
        return ResponseEntity.status(HttpStatus.OK).body(user);
    }

12、Rest风格

Rest风格的规范 我们要求知道3个

  • 对于请求方式规范 不同的操作 使用不同的请求方式

    • get: 请求一般是获取数据

    • post: 登录和添加数据

    • put: 一般是修改

    • delete : 删除操作

  • 对于不同的状态码的规范

    比如 200表示成功 404 表示未找到 201 表示创建成功

  • Rest风格的请求路径(传递参数)

 

13、文件上传

  目前市场上流行的文件上传分为2中
  • 提交表单(包含异步)的方式
  • Base64格式的方式(一定要掌握)

13.1、提交表单的方式

13.1.1、提交表单上传的三要素
  • post请求

  • 有个input type=file的标签

  • enctype="multipart/form-data"

  对于基于表单的文件上传,不要忘记使用 enctype 属性,并将它的值设置为 multipart/form-data,同时将表单的提交方式设置为 post。为什么要这样呢?下面从 enctype 属性说起。

  表单的 enctype 属性指定的是表单数据的编码方式,该属性有以下 3 个值。
  • application/x-www-form-urlencoded:这是默认的编码方式,它只处理表单域里的 value 属性值。
  • multipart/form-data:该编码方式以二进制流的方式来处理表单数据,并将文件域指定文件的内容封装到请求参数里。
  • text/plain:该编码方式只有当表单的 action 属性为“mailto:”URL 的形式时才使用,主要适用于直接通过表单发送邮件的方式。

  由上面 3 个属性的解释可知,在基于表单上传文件时 enctype 的属性值应为 multipart/form-data。
13.1.2、Java代码的编写方式
  • 如果是提交表单文件上传

    • Servlet3.0之前原始的写法

    • Servlet3.0之后part的写法

    • fileupload.jar+commons.io.jar的写法

    • fileupload.jar+commons.io.jar继承SpringMVC的写法

  • 如果是Base64上传的方式

    • 写法很单一

13.2、提交表单之part的方式

13.2.1、单文件上传之part的方式

  part方式的文件上传不需要导入任何包,也不需要进行任何视图解析器的配置
  编写Controller类
@Controller
public class FileController {

    @PostMapping("upload")
    @ResponseBody
    public String uploadFile(HttpServletRequest request) throws IOException, ServletException {
        //从前端读取文件,参数是input的name属性值
        Part fileName = request.getPart("fileName");

        //打印文件名称,能打印就说明上传成功
        System.out.println(fileName.getSubmittedFileName());


        //为防止文件重名,此时可以选择重命名,重命名的方式有以下几种
        //1:UUID
        //2:时间戳:毫秒、纳秒
        //3:建多个文件夹
        //4、目录打散 哈希算法  获取一个字符串的哈希值,再通过哈希值的位与运算,获得目录


        //获取一个UUID
        String uuid = UUID.randomUUID().toString();
        //获取一个毫秒级的时间戳
        long time = System.currentTimeMillis();
        //获取一个纳米级的时间戳
//        long time = System.nanoTime();

        //获取文件的后缀名
        String ext = StringUtils.getFilenameExtension(fileName.getSubmittedFileName());

        //获取重命名后的文件名
//        String reFileName = uuid + "." + ext;
        String reFileName = time + "." + ext;
        //获取文件夹路径
        String realPath = request.getServletContext().getRealPath("/uploads");

//        //今天的文件夹是否创建
//        //获取今天时间
//        LocalDate localDate = LocalDate.now();
//        //设置日期格式
//        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
//        //格式化日期
//        String format = dateTimeFormatter.format(localDate);
//        //创建文件对象
//        File file = new File(realPath + "/" + format);
//        //判断文件夹是否存在,不存在就创建一个新的
//        if (!file.exists()) {
//            file.mkdirs();
//        }
//        //文件真实路径=文件夹路径+文件名    "File.separator"  ==>  " / "
//        String path = realPath + File.separator + format + File.separator + reFileName;
//        //写入文件
//        fileName.write(path);


        //目录打散
        //用UUID的哈希值
        int hashcode = UUID.randomUUID().toString().hashCode();
        //0-15
        int dir1 = hashcode & 0xf;
        int dir2 = (hashcode & 0xf0) >> 4;
        String finalFilePath = realPath + "/" + dir1 + "/" + dir2 + "/";
        //创建文件对象
        File file = new File(finalFilePath);
        //判断文件夹是否存在,不存在就创建一个新的
        if (!file.exists()) {
            file.mkdirs();
        }
        //文件真实路径=文件 夹路径+文件名    "File.separator"  ==>  " / "
        String path = finalFilePath + reFileName;
        //写入文件
        fileName.write(path);


        return "http://localhost:8080/uploads/" + dir1 + "/" + dir2 + "/" + reFileName;
    }
}

   编写前端页面

<h2>文件上传</h2>
    <form action="/upload" enctype="multipart/form-data" method="post">
      <input type="file" name="fileName"><br>
      <input type="submit" value="提交">
    </form>

  springmvc.xml配置文件

<?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"
       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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--    开启包扫描-->
    <context:component-scan base-package="top.ftime.wk.controller"/>
<!--    开启MVC的注解驱动,这行的配置等效于配置“处理器映射器”和“处理器适配器-->
    <mvc:annotation-driven />
<!--   开启静态资源访问 -->
    <mvc:default-servlet-handler/>
</beans>
  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">
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
<!--        开启springMVC part方式上传文件-->
        <multipart-config>
<!--    上传文件的最大容量(以字节为单位)。默认是没有限制的。        -->
            <max-file-size>20848820</max-file-size>
<!--    整个multipart请求的最大容量(以字节为单位),不会关心有多少个part以及每个part的大小。默认是没有限制的        -->
            <max-request-size>418018841</max-request-size>
<!--    在上传的过程中,如果文件大小达到了一个指定最大容量(以字节为单位),将会写入到临时文件路径中。默认值为0        -->
            <file-size-threshold>1048576</file-size-threshold>
        </multipart-config>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
13.2.2、多文件上传之part的方式

  @MultipartConfig标注主要是为了辅助Servlet3.0中HttpServletRequest提供的对上传文件的支持。该标注写在Servlet类的声明之前,一表示该Servlet希望处理的请求时multipart/form-data类型的。另外,该标注还提供了若干属性用于简化对上传文件的处理。

@MultipartConfig标注属性

fileSizeThershold         int型        是(可选)     描述:当前数据量大于该值时,内容将被写入文件。

location                        String型        是(可选)     描述:存放生成文件的地址

maxFileSize           long型           是(可选)     描述:允许上传的文件最大值,默认为-1,表示没有限制

maxRequestSize    long型           是(可选)     描述:针对 multipart/form-data 请求的最大数量,默认为-1,表示没有限制

  编写Controller类

@Controller
@MultipartConfig
public class FileController {
    @PostMapping("uploads")
    @ResponseBody
    public String uploads(HttpServletRequest request) throws IOException, ServletException {
        Collection<Part> parts = request.getParts();
        String realPath = request.getServletContext().getRealPath("/WEB-INF/");
        parts.forEach(part -> {
            try {
                part.write(realPath+part.getSubmittedFileName());
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        return "success";
    }
}

 

  编写前端页面
    <h2>文件上传</h2>
    <form action="/uploads" enctype="multipart/form-data" method="post">
      <input type="file" name="fileName" multiple><br>
      <input type="submit" value="提交">
    </form>

13.3、提交表单之Spring MVC的方式

MultipartFile接口
  在 Spring MVC 框架中上传文件时将文件相关信息及操作封装到 MultipartFile 对象中,因此开发者只需要使用 MultipartFile 类型声明模型类的一个属性即可对被上传文件进行操作。该接口具有如下方法。
名称作用
byte[] getBytes() 以字节数组的形式返回文件的内容
String getContentType() 返回文件的内容类型
InputStream getInputStream() 返回一个InputStream,从中读取文件的内容
String getName() 返回请求参数的名称
String getOriginalFillename() 返回客户端提交的原始文件名称
long getSize() 返回文件的大小,单位为字节
boolean isEmpty() 判断被上传文件是否为空
void transferTo(File destination) 将上传文件保存到目标目录下

  在上传文件时需要在配置文件中使用 Spring 的 org.springframework.web.multipart.commons.CommonsMultipartResolver 类配置 MultipartResolver 用于文件上传。

<!--    配置多媒体实体解析器-->
    <bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>

 

  Spring MVC 框架的文件上传是基于 commons-fileupload 组件的文件上传,只不过 Spring MVC 框架在原有文件上传组件上做了进一步封装,简化了文件上传的代码实现,取消了不同上传组件上的编程差异。
  由于 Spring MVC 框架的文件上传是基于 commons-fileupload 组件的文件上传,因此需要将 commons-fileupload 组件相关的 JAR(commons-fileupload-1.3.1.jar 和 commons-io-2.4.jar)复制到 Spring MVC 应用的 WEB-INF/lib 目录下。
 
  编写Controller类
    /**
     *
     * @param request
     * @param fileName  形参MultipartFile 的名字要和前端页面中input标签的name属性值一致
     * @return
     * @throws IOException
     */
    @PostMapping("uploadByFileIo")
    @ResponseBody
    public String uploadByFileIo(HttpServletRequest request, MultipartFile fileName) throws IOException {
        String realPath = request.getServletContext().getRealPath("/file/");
        File file = new File(realPath);
        if (!file.exists()) {
            file.mkdirs();
        }
        //重命名
        long nano = System.nanoTime();
        String ext = StringUtils.getFilenameExtension(fileName.getOriginalFilename());
        String filePath = realPath + nano + "." + ext;
        //写出文件
        fileName.transferTo(new File(filePath));
        return filePath;
    }

   前端页面

    <h2>文件上传</h2>
    <form action="/uploadByFileIo" enctype="multipart/form-data" method="post">
      <input type="file" name="fileName"><br>
      <input type="submit" value="提交">
    </form>

 

  springmvc.xml配置文件
<?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"
       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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--    开启包扫描-->
    <context:component-scan base-package="top.ftime.wk.controller"/>
<!--    开启MVC的注解驱动,这行的配置等效于配置“处理器映射器”和“处理器适配器-->
    <mvc:annotation-driven />
<!--   开启静态资源访问 -->
    <mvc:default-servlet-handler/>
<!--    配置多媒体实体解析器-->
    <bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
 </beans>
  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">
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
<!--        开启springMVC part方式上传文件-->
        <multipart-config/>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
13.4、Base64格式的方式// TODO: 2021/6/2
 

14、文件下载

14.1、通过超链接实现下载

  通过超链接实现下载固然简单,但暴露了下载文件的真实位置,并且只能下载存放在 Web 应用程序所在的目录下的文件。
弊端:不能统计下载次数,并且对于浏览器能够解析的文件,浏览器会直接解析展示,不会下载,需要在通过右键另存为保存文件
演示

14.2、利用程序编码实现下载

  利用程序编码实现下载可以增加安全访问控制,还可以从任意位置提供下载的数据,可以将文件存放到 Web 应用程序以外的目录中,也可以将文件保存到数据库中。
利用程序实现下载需要设置两个报头:
 
  Web 服务器需要告诉浏览器其所输出内容的类型不是普通文本文件或 HTML 文件,而是一个要保存到本地的下载文件,这需要设置 Content-Type 的值为 application/x-msdownload。
 
  Web 服务器希望浏览器不直接处理相应的实体内容,而是由用户选择将相应的实体内容保存到一个文件中,这需要设置 Content-Disposition 报头。该报头指定了接收程序处理数据内容的方式,在 HTTP 应用中只有 attachment 是标准方式,attachment 表示要求用户干预。在 attachment 后面还可以指定 filename 参数,该参数是服务器建议浏览器将实体内容保存到文件中的文件名称。
设置报头的示例如下:
response.setHeader("Content-Type", "application/x-msdownload");
response.setHeader("Content-Disposition", "attachment;filename="+filename);

具体实现如下

1)编写控制器类
  首先编写控制器类 FileDownController,在该类中有 3 个方法,即 show、down 和 toUTF8String。其中,show 方法获取被下载的文件名称;down 方法执行下载功能;toUTF8String 方法是下载保存时中文文件名的字符编码转换方法。
  FileDownController 类的代码如下:
package controller;

import java.io.File;
import java.io.FileInputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class FileDownController {
    // 得到一个用来记录日志的对象,在打印时标记打印的是哪个类的信息
    private static final Log logger = LogFactory
            .getLog(FileDownController.class);

    /**
     * 显示要下载的文件
     */
    @RequestMapping("showDownFiles")
    public String show(HttpServletRequest request, Model model) {
        // 从 workspace\.metadata\.plugins\org.eclipse.wst.server.core\
        // tmp0\wtpwebapps\springMVCDemo11\下载
        String realpath = request.getServletContext()
                .getRealPath("uploadfiles");
        File dir = new File(realpath);
        File files[] = dir.listFiles();
        // 获取该目录下的所有文件名
        ArrayList<String> fileName = new ArrayList<String>();
        for (int i = 0; i < files.length; i++) {
            fileName.add(files[i].getName());
        }
        model.addAttribute("files", fileName);
        return "showDownFiles";
    }

    /**
     * 执行下载
     */
    @RequestMapping("down")
    public String down(@RequestParam String filename,
            HttpServletRequest request, HttpServletResponse response) {
        String aFilePath = null; // 要下载的文件路径
        FileInputStream in = null; // 输入流
        ServletOutputStream out = null; // 输出流
        try {
            // 从workspace\.metadata\.plugins\org.eclipse.wst.server.core\
            // tmp0\wtpwebapps下载
            aFilePath = request.getServletContext().getRealPath("uploadfiles");
            // 设置下载文件使用的报头
            response.setHeader("Content-Type", "application/x-msdownload");
            response.setHeader("Content-Disposition", "attachment; filename="
                    + toUTF8String(filename));
            // 读入文件
            in = new FileInputStream(aFilePath + "\\" + filename);
            // 得到响应对象的输出流,用于向客户端输出二进制数据
            out = response.getOutputStream();
            out.flush();
            int aRead = 0;
            byte b[] = new byte[1024];
            while ((aRead = in.read(b)) != -1 & in != null) {
                out.write(b, 0, aRead);
            }
            out.flush();
            in.close();
            out.close();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        logger.info("下载成功");
        return null;
    }

    /**
     * 下载保存时中文文件名的字符编码转换方法
     */
    public String toUTF8String(String str) {
        StringBuffer sb = new StringBuffer();
        int len = str.length();
        for (int i = 0; i < len; i++) {
            // 取出字符中的每个字符
            char c = str.charAt(i);
            // Unicode码值为0~255时,不做处理
            if (c >= 0 && c <= 255) {
                sb.append(c);
            } else { // 转换 UTF-8 编码
                byte b[];
                try {
                    b = Character.toString(c).getBytes("UTF-8");
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                    b = null;
                }
                // 转换为%HH的字符串形式
                for (int j = 0; j < b.length; j++) {
                    int k = b[j];
                    if (k < 0) {
                        k &= 255;
                    }
                    sb.append("%" + Integer.toHexString(k).toUpperCase());
                }
            }
        }
        return sb.toString();
    }
}

2)创建文件列表页面

  下载文件示例需要一个显示被下载文件的 JSP 页面 showDownFiles.jsp,代码如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <table>
        <tr>
            <td>被下载的文件名</td>
        </tr>
        <!--遍历 model中的 files-->
        <c:forEach items="${files}" var="filename">
            <tr>
                <td>
                    <a href="${pageContext.request.contextPath }/down?filename=${filename}">${filename}</a>
                </td>
            </tr>
        </c:forEach>
    </table>
</body>
</html>

 快速看懂版

15、Spring MVC的静态资源

15.1、要了解的点

    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>        
<!--        <url-pattern>/*</url-pattern>-->
    </servlet-mapping>
  DispatcherServlet可以配置  “ / ” 和 “ /* ”,这两个都表示所有请求都会进入DispatcherServlet,但是配置  “ / ”不会拦截对JSP页面资源的请求,对其他资源文件的请求则会被拦截;配置  “ /* ”会拦截对所有资源文件的请求。因此,如果你的页面不是用的JSP,配置2两个任何一个都可。

15.2、静态资源解决方式一

  静态资源不进入DispatcherServlet就行了,统一Controller的访问
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/*.do</url-pattern>
    </servlet-mapping>

 

  这段代码表示,只有请求路径以“ .do ”结尾的请求,才能进入DispatcherServlet静态资源根本就无法进入DispatcherServlet,所以就谈不上拦截了。
  缺点:不符合Rest风格API

15.3、静态资源解决方式二

  告诉Spring MVC那些请求需要到那些地方找,配置静态资源映射
    <!--    
    告诉Spring MVC那些请求需要到那些地方找
    配置静态资源映射
    Mapping:表示请求匹配,支持通配符
    Location:表示要资源的位置
    -->
    <mvc:resources mapping="pages/*" location="WEB-INF"/>

15.4、静态资源解决方式三

  在Spring MVC的配置文件中开启静态资源访问
<!--   开启静态资源访问 -->
    <mvc:default-servlet-handler/>

16、Spring MVC的异常处理

  我们的开发中 三层架构 dao层 、service层和 controller层的异常, 一般都会抛给SpringMVC来处理,所以我们异常处理 是针对的SpringMVC的异常处理

16.1、方式一

  指定错误页面error,一有异常就跳转到error页面。
  缺点:页面固定,传值步骤繁琐,不利于展示错误信息。

16.2、方式二

  • 第一步:创建一个异常处理类

    • 要求实现 HandlerExceptionResolver

    • 要求加入到springioc容器中  ​

     @Component
      public class MyExeptionHander implements HandlerExceptionResolver {
          @Override
          public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
      ​
              ModelAndView mv  = new ModelAndView();
              mv.addObject("msg",e.getMessage());
              mv.setViewName("error");
              return mv;
          }
      }

     

  • 第二步:修改error.jsp

     第三步: 编写Controller

@Controller
  public class ErrorController {
  ​
      @GetMapping("error")
      public String error(){
          if(true){
            throw new RuntimeException("辉哥真帅");
          }
          return "success";
      }
  }

  特点: 虽然可以动态显示错误信息 但是只能返回jsp页面 想要返回html页面 需要依赖模板引擎

16.3、方式三(必须掌握)

由于实际开发中 jsp页面用的比较少(可以说 基本不用) 那么我们以上2种方式 则不能满足我们的需求

这个时候 我们要返回json数据

第一步:创建枚举类(保存状态码,状态信息)
public enum StatusEnum {

    NAME_ERROR(20001,"用户名错误"),
    PASRROR_ERROR(20002,"密码错误"),
    UNKNOWN_ERROR(40004,"未知错误");


    private int status;

    private String message;

    StatusEnum(int status, String message) {
        this.status = status;
        this.message = message;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

 

第二步:自定义异常
public class DiyExecution extends RuntimeException{
    private StatusEnum statusEnum;

    public StatusEnum getStatusEnum() {
        return statusEnum;
    }

    public void setStatusEnum(StatusEnum statusEnum) {
        this.statusEnum = statusEnum;
    }

    public DiyExecution(StatusEnum statusEnum) {
        this.statusEnum = statusEnum;
    }
}

 

第三步:Controller层抛出自定义异常
@Controller
public class ExceptionController {


    @GetMapping("error2")
    public String fun2(){
        System.out.println("异常二");
        throw  new DiyExecution(StatusEnum.NAME_ERROR);
    }
}

 

第四步:创建异常处理类
@RestControllerAdvice//给Controller添加功能
public class MyCustmerExceptionHander {

    /**
     * 注解@ExceptionHandler表示你要处理的异常
     * Controller层如果出了异常,并且是DiyExecution异常时,会执行这个方法
     * @param diyExecution
     * @return
     */
    @ExceptionHandler({DiyExecution.class})
    public ResponseEntity<Map<String,Object>> hander (DiyExecution diyExecution){
        StatusEnum statusEnum = diyExecution.getStatusEnum();
        Map<String,Object> map = new HashMap<>();
        map.put("status",statusEnum.getStatus());
        map.put("message",statusEnum.getMessage());
        return ResponseEntity.ok(map);

    }
}

 16.4、处理未知异常

@RestControllerAdvice//给Controller添加功能
public class MyCustmerExceptionHander {

    //在不知道哪里出了异常,也不知道出现了什么异常的情况下
    //我们此时要处理这个异常,必须要找这些异常的父类 throwable
    /**
     * 全局解决未知异常
     */
    @ExceptionHandler({Throwable.class})
    public ResponseEntity<Map<String,Object>> unknown (Throwable e){
StatusEnum unknownError
= StatusEnum.UNKNOWN_ERROR;
unknownError.setMessage(e.getMessage());
Map
<String,Object> map = new HashMap<>(); map.put("status",unknownError.getStatus()); map.put("message",unknownError.getMessage()); return ResponseEntity.ok(map); } }

17、Spring MVC拦截器

  Spring MVC 的拦截器(Interceptor)与 Java Servlet 的过滤器(Filter)类似,它主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、记录请求信息的日志、判断用户是否登录等功能上。
  在 Spring MVC 框架中定义一个拦截器需要对拦截器进行定义和配置,定义一个拦截器可以通过两种方式:一种是通过实现 HandlerInterceptor 接口或继承 HandlerInterceptor 接口的实现类来定义;另一种是通过实现 WebRequestInterceptor 接口或继承 WebRequestInterceptor 接口的实现类来定义。
使用步骤:
第一步:创建拦截器
package interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class TestInterceptor implements HandlerInterceptor {
    @Override
    public void afterCompletion(HttpServletRequest request,
            HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("afterCompletion方法在控制器的处理请求方法执行完成后执行,即视图渲染结束之后执行");

    }

    @Override
    public void postHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle方法在控制器的处理请求方法调用之后,解析视图之前执行");
    }

    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle方法在控制器的处理请求方法调用之后,解析视图之前执行");
        return false;
    }
}

   在上述拦截器的定义中实现了 HandlerInterceptor 接口,并实现了接口中的 3 个方法。有关这 3 个方法的描述如下。

    • preHandle 方法:该方法在控制器的处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。
    • postHandle 方法:该方法在控制器的处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步的修改。
    • afterCompletion 方法:该方法在控制器的处理请求方法执行完成后执行,即视图渲染结束后执行,可以通过此方法实现一些资源清理、记录日志信息等工作。

第二步:配置拦截器

  让自定义的拦截器生效需要在 Spring MVC 的配置文件中进行配置,配置示例代码如下:

<!-- 配置拦截器 -->
<mvc:interceptors>
    <!-- 配置一个全局拦截器,拦截所有请求 -->
    <bean class="interceptor.TestInterceptor" /> 
    <mvc:interceptor>
        <!-- 配置拦截器作用的路径 -->
        <mvc:mapping path="/**" />
        <!-- 配置不需要拦截作用的路径 -->
        <mvc:exclude-mapping path="" />
        <!-- 定义<mvc:interceptor>元素中,表示匹配指定路径的请求才进行拦截 -->
        <bean class="interceptor.Interceptor1" />
    </mvc:interceptor>
    <mvc:interceptor>
        <!-- 配置拦截器作用的路径 -->
        <mvc:mapping path="/gotoTest" />
        <!-- 定义在<mvc: interceptor>元素中,表示匹配指定路径的请求才进行拦截 -->
        <bean class="interceptor.Interceptor2" />
    </mvc:interceptor>
</mvc:interceptors>

 

可以配置多个拦截器
 <!-- 配置拦截器  可以配置多个 -->
    <mvc:interceptors>

        <!--表示配置一个-->
        <mvc:interceptor>
            <!--拦截的路径 -->
            <mvc:mapping path="/interceptor/*" />
            <!--拦截后要走的拦截器-->
            <bean class="com.shangma.cn.demo1.FirstInteceptor"></bean>
        </mvc:interceptor>



        <!--表示配置一个-->                                                                                                                       
        <mvc:interceptor>
            <!--拦截的路径 -->
            <mvc:mapping path="/interceptor/*" />
            <!--<mvc:exclude-mapping path=""/>-->

            <!--拦截后要走的拦截器-->
            <bean class="com.shangma.cn.demo1.FisrtInteceptor"></bean>
        </mvc:interceptor>



    </mvc:interceptors>

18、Spring MVC的类型转换问题

18.1、日期类型转换之Date类型

18.1.1、以key-value的形式传参
解决方式一:

 

解决方式二:

  新建类型转换器

//Converter 第一个泛型 表示from
            //第二个泛型 表示 to
public class MyConvert implements Converter<String,Date> {
    @Override
  public Date convert(String s) {
      SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
      try {
          Date parse = sdf.parse(s);
          return parse;
      } catch (ParseException e) {
         SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy/MM/dd");
          try {
              return sdf1.parse(s);
          } catch (ParseException e1) {
              e1.printStackTrace();
          }
      }
      return null;
  }

 

  配置转换器

  <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
          <property name="converters">
              <set>
                  <bean class="com.shangma.cn.MyConvert"/>
              </set>
          </property>
      </bean>

      <mvc:annotation-driven conversion-service="conversionService"/>

18.1.2、以json的形式传参

  如果传递的是个json 那么则key-value的处理方式 则失效 处理json 默认使用了 jackson 我们只需要导入jar包就行了 默认接收的格式 yyyy-MM-dd 其他格式则报错

jackson-annotations-2.9.8
jackson-core-2.9.8
jackson-databind-2.9.8

自定义格式方式一:
自定义格式方式二:
  不管是返回还是接受json都生效
    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="objectMapper" ref="objectMapper"/>
            </bean>

        </mvc:message-converters>
    </mvc:annotation-driven>

    <bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
          p:indentOutput="true"
          p:simpleDateFormat="yyyy-MM-dd HH:mm:ss"
      />

18.2、日期类型转换之LocalDa&LocalDateTime

18.2.1、以key-value的形式传参
第一种解决方式:
第二种解决方式:
  定义2个Converts
public class MyConvert1 implements Converter<String,LocalDateTime> {
    @Override
    public LocalDateTime convert(String s) {
       return LocalDateTime.parse(s,DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }
}


public class MyConvert2 implements Converter<String,LocalDate> {
    @Override
    public LocalDate convert(String s) {
       return LocalDate.parse(s,DateTimeFormatter.ofPattern("yyyy-MM-dd"));
    }
}

   加载Converts

    <mvc:annotation-driven conversion-service="conversionService"/>
    <bean id="conversionService"
          class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="com.shangma.cn.controller.MyConvert1"/>
                <bean class="com.shangma.cn.controller.MyConvert2"/>
            </set>
        </property>
    </bean>

 

18.2.2、以json的形式传参
 注意:如果使用java8中的日期时 此时我们需要一个jackson的支持包
jackson-datatype-jsr310-2.9.8:支持Java8日期和时间API类型
  导入这个包后 什么都不需要配置 直接使用@JsonFormat注解 接收和响应 都会按照指定的格式生效

解决方式一:

解决方式二:

 

 

 


 
posted @ 2021-06-03 23:58  mini9264  阅读(57)  评论(0编辑  收藏  举报