SSM-回炉重造-Spring MVC


Spring MVC: Spring的web模块,用于简化Java web开发

Spring MVC通过一套MVC注解,让pojo成为处理请求的控制器,无需实现任何接口


HelloWorld

1. 导包

web工程,导包需要将包放至webapp目录的指定路径下,如下图

image-20200920140922046

或者使用maven导入相关依赖(推荐)

2. 配置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>
        <!--    启动时自动加载,值越小优先级越高,越先创建   -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>   <!-- 拦截所有请求 -->
        <!--
         /*和/都会拦截所有请求,但是/不会拦截jsp (jsp和servlet不是静态资源)
        -->
    </servlet-mapping>

</web-app>
  • contextConfigLocation的默认文件是/WEB-INF/xxx-servlet.xml,其中xxx为DispatcherServlet这个类的名字,即上方的servlet-name标签的值
  • /和/*都会拦截静态资源,tomcat默认的DefaultServlet用于处理静态资源,而由于我们写的dispatcherServlet覆盖了tomcat的,所以/ /* 都会拦截静态资源
  • /*不管是否重写,都是拦截所有的资源,通常写/,可以可迎合rest风格

3. 创建springmvc.xml

在resource目录下,创建 spring 配置文件

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

    <context:component-scan base-package="com.oylong.controller"/>
    
</beans>

4. 写页面

5. 写控制器

package com.oylong.controller;

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

/**
 * @ProjectName: springmvc
 * @Description:
 * @Author: OyLong
 * @Date: 2020/9/20 13:24
 */
@Controller
public class HelloController {

    @RequestMapping("/hello")
    public String hello(){
        System.out.println("收到请求");
        return "/WEB-INF/page/hello.jsp";
    }
}

@Controller注解标注在类上,表示这是一个控制器,使用@RequestMapping注解表示在指定url访问时,使用这个方法去处理


配置视图解析器

由于每次都要写完整路径,所以为了简化,可以在spring配置文件中,配置一个视图解析器

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


    <context:component-scan base-package="com.oylong.controller"/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/page/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

视图解析器简化后的controller

@Controller
public class HelloController {

    @RequestMapping("/hello")
    public String hello(){
        System.out.println("收到请求");
        return "hello";
    }
}

视图解析器将自动拼接相应的路径


@RequestMapping注解

  • 标注在方法上

    表示某一个请求的具体路径

  • 标注在类上

    表示该类下所有的请求的前缀

@Controller
@RequestMapping("/test")
public class HelloController {

    @RequestMapping("/hello")
    public String hello(){
        System.out.println("收到请求");
        return "hello";
    }
}

如上面的请求,由于类上标注了/test,所以访问hello就必须使用/test/hello

method属性

public enum RequestMethod {
    GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}

method属性的值的定义如上所示,我们可以用method属性来限定请求方式,假如我们指定了GET,那么该对应的url只能用指定的方式请求,才会被接受,而如果留空,即不指定方法,则任何请求方式都能被接收

@Controller
public class HelloController {

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String hello(){
        System.out.println("收到请求");
        return "hello";
    }
}

params属性

params属性用来规定请求的参数,如果指定了参数,在请求时没有传参的话,就会异常

  @RequestMapping(value = "/hello", params = {"haha"})
    public String hello(){
        System.out.println("收到请求");
        return "hello";
    }

上面这个方法,若想请求,则必须要在hello后面带上参数,如/hello?haha,同时规定多个,可以直接在后面加逗号补充即可

同时params属性还支持一些特殊表达式,比如:

  • params = {"!haha"}表示不能带有haha参数.
  • params = {"haha=123"}表示必须要带有参数为haha,且haha的值为123才能访问

headers属性

规定请求头,类似params属性,也可以使用一些特殊表达式,使用方式也类似params属性

consumes属性

规定接收内容的类型,即请求头中的Content-Type

produces属性

告诉浏览器返回内容的类型,给响应头加上Content-Type


模糊匹配

  • ? : 替代任意的1个字符
  • * : 替代任意多个字符或一层路径
  • **: 替代多层路径

@RequestParam注解

SpringMVC中,获取请求参数时,可以直接使用与参数同名的变量即可自动获取,如下:

@Controller
public class HelloController {

    @RequestMapping(value = "/hello/")
    public String hello(String id){
        System.out.println("收到请求, id:"+id);
        return "hello";
    }
}

访问时:

image-20200920211908703

运行结果:

image-20200920211933450

同时也可以指定参数名字,如下:

@Controller
public class HelloController {

    @RequestMapping(value = "/hello")
    public String hello(@RequestParam(value = "id") String num) {
        System.out.println("收到请求, num:" + num);
        return "hello";
    }
}

运行结果:

image-20200920212131513

同时,标注了@RequestParam注解的属性,默认是必须要有值的,否则无法请求,除非单独设置其required属性,如下:

@RequestParam(value = "id", required = false) String num

同时还能通过其属性defaultValue来设置默认值


@RequestHeader注解

类似@RequestParam注解,只要在形参列表中添加请求头的key,即可获取指定的请求头的值

@Controller
public class HelloController {

    @RequestMapping(value = "/hello")
    public String hello(@RequestHeader(value = "User-Agent") String userAgent) {
        System.out.println("请求头:" + userAgent);
        return "hello";
    }
}

运行结果如下:

image-20200920213027155

同样拥有required属性和defaultValue属性


@CookieValue注解

类似@RequestHeader注解,用于通过key获取请求中附带的cookie的值


实体类的自动赋值

如果传入多个参数,且在spring mvc中使用实体类接收,spring mvc将会通过对应变量名的方式去自动封装实体类.

对于复杂属性,可以级联封装,通过类似car.engine,car.color来封装即可


乱码问题

1. 请求乱码

web.xml中配置CharacterEncodingFilter

    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <!--解决post乱码-->
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <!--解决response乱码-->
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

字符编码的CharacterEncodingFilter通常需要配置在其他的Filter之前,否则可能无法解决乱码问题


2. 响应乱码

设置返回体

response.setContentType("text/html;charset=utf-8");

输出数据到页面

1. 在方法处传入Map,Model,ModelMap

在方法中加入以上类型的参数,如下

    @RequestMapping("/output01")
    public String test(Map<String, Object> map) {
        map.put("msg", "hello");
        return "output";
    }

Model:

@Controller
public class DataController {
    @RequestMapping("/output01")
    public String test(Model model) {
        model.addAttribute("msg", "哈哈");
        return "output";
    }
}

ModelMap:

@Controller
public class DataController {
    @RequestMapping("/output01")
    public String test(ModelMap modelMap) {
        modelMap.addAttribute("msg", "哈哈");
        return "output";
    }
}

以上三种方法,在JSP页面中都可以通过request作用域取值,如下

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>output test</title>
</head>
<body>
request: ${requestScope.msg}
</body>
</html>

运行结果:

image-20200921201914811

这三个类,最终实现的都是一个类BindingAwareModelMap

image-20200921202423266

image-20200921202433860


2. ModelAndView

如下代码,通过使用ModelAndView类去进行数据传递与页面返回,用法如下:

    @RequestMapping("/output02")
    public ModelAndView test01() {
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "haha");
        mv.setViewName("output");
        return mv;
    }
  • 通过addObject方法添加属性
  • 通过setViewName设置需要返回的页面
  • 同样是在request域中可以获取到传递的数据

3.@SessionAttributes注解

  • 这个注解只能标注在类上
  • 注解的值表示会将注解指定的value在保存在BindingAwareModelMap的同时,也会保存在session中去
  • 注解的值也可以存入一个数组
  • 可以使用type属性指定类型,如果不符合相应的类型,将无法保存
  • 不推荐使用,若实在要添加数据至session,建议使用原生api
@SessionAttributes(value = "msg")
@Controller
public class DataController {
    @RequestMapping("/output02")
    public ModelAndView test01() {
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "haha");
        mv.setViewName("output");
        return mv;
    }
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>output test</title>
</head>
<body>
<%--pageContext: ${pageScope.msg}
--%>
request: ${requestScope.msg}
session: ${sessionScope.msg}
<%--application: ${applicationScope.msg}
--%>
</body>
</html>

运行结果:

image-20200921204340741


4.@ModelAttribute注解

貌似使用不多,用到了再来作补充


视图解析器

如果配置视图解析器后,对应的页面不在指定的页面里面,而在上一层,如下:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/page/"/>
    <property name="suffix" value=".jsp"/>
</bean>

此时配置在了/WEB-INF/page/这个目录下,如果我们的hello.jsp页面在/WEB-INF/下,这时候直接返回页面名字则会找不到

解决办法:

  1. 使用相对路径,Controller返回时,使用../这样的格式
  2. 使用forward进行转发

    @RequestMapping(value = "/hello")
    public String hello() {
        return "forward:/hello.jsp";   //一定要加上 '/'  否则就是相对路径
    }
  3. 使用redirect重定向

    @RequestMapping(value = "/hello")
    public String hello() {
        return "redirect:/hello.jsp";  
    }

区分请求和静态资源

SpringMVC中如果dispatcherServlet指定的是所有url,那么静态资源也会无法访问,解决方法如下:

在springmvc的配置文件中添加下面两个注解:

    <mvc:default-servlet-handler/>
    
    <mvc:annotation-driven/>

JSR303 数据校验

1.使用方式

  1. 在java bean的属性上添加相应的注解
  2. 在Spring MVC封装对象时,添加@Valid表示改对象需要被校验

2.校验结果

在封装的对象后面添加BindingResult属性的变量即可获取相应的校验结果的封装

3. 校验注解

3145530-8ae74d19e6c65b4c.webp (654×454)

Hibernate Validator 附加的 constraint

2


Json格式的数据

1. 返回Json格式的数据

只需要在原来的Controller方法的基础上,添加一个@ResponseBody即可

@Controller
public class DataController {
    
    @ResponseBody
    @RequestMapping("/output03")
    public String test02(){
        return "hello";
    }
    ...
}

2. 接收Json格式的数据

Controller方法的参数上加上@RequestBody注解即可

@ResponseBody
@RequestMapping("/output03")
public String test02(@RequestBody Teacher teacher){
    return "hello";
}

同时接收和返回数据还可以使用带泛型的HttpEntityResponseEntity进行更复杂的操作


拦截器

SpringMVC提供了拦截器机制,能在目标方法之前或者之后进行一些处理工作

拦截器的preHandle处理方法返回true表示放行,否则不放行,不放行则不会往后面继续执行

拦截器接口:HandlerInterceptor

image-20201005102445775

此接口,有三个方法:

  • preHandle: 目标方法运行前调用
  • postHandle: 目标方法运行后调用
  • afterCompletion: 整个请求完成后,调用

使用方法

1. 实现接口

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("处理前");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("处理后");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("请求后");
    }
}

2. 配置文件

在Spring MVC的配置文件中配置拦截器,如下

<mvc:interceptors>
    <bean class="com.oylong.interceptor.MyInterceptor"></bean>
</mvc:interceptors>

这种不指定路径的配置方式,表示将所有的请求都进行拦截

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/output05"/>
        <bean class="com.oylong.interceptor.MyInterceptor"></bean>
    </mvc:interceptor>
</mvc:interceptors>

这种表示只拦截指定的请求url

注:只要放行了,一定会执行afterCompletion方法

 拦截器与过滤器

  • filter更通用
  • interceptor可以配合Spring ioc容器使用,更加强大

异常处理

SpringMVC提供了一套异常处理的机制,在Controller中出现异常时,可以通过@ExceptionHandler去捕获指定的异常类型,如下:

@ExceptionHandler(NullPointerException.class)
public String exceptionHandler(Exception exception){
    return  exception.getMessage();
}

上面的写法是将异常的处理写在某个Controller里面,也可以用一个@ControllerAdvice注解了来处理所有的Controller的异常,如下:

@ControllerAdvice
public class MyExceptionController {
    @ExceptionHandler(NullPointerException.class)
    public String nullPointerException(Exception e){
        return e.getMessage();
    }
}

更改返回状态码以及返回信息

可以通过注解@ResponseStatus来指定返回的状态,需要注意的是,不太建议把这个注解标注在正常的Controller方法上,而是标注在异常处理的方法上,如下:

@ResponseStatus(reason = "错误了", value = HttpStatus.SERVICE_UNAVAILABLE)
@ExceptionHandler(NullPointerException.class)
public String exceptionHandler(Exception exception){
    return  exception.getMessage();
}

标注此注解后,会将响应的原因和状态码进行一个更改,如下图

image-20201005183159861

也可以标注在自定义异常上,那么抛出相应的自定义异常时就会将响应的消息和响应的状态码进行更改

Last modification:October 5, 2020
If you think my article is useful to you, please feel free to appreciate