Spring MVC 入门

Spring MVC 概述

Spring MVC 介绍

Spring 为表现层提供的基于 MVC 设计理念的 Web 框架。

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

支持 REST 风格的 url 请求。

采用松散耦合可插拔组件结构,比其他 MVC 框架更具有扩展性和灵活性。
在这里插入图片描述

永远的 Hello World

首先,是要导入 jar 包

  • servlet-api-x.y.z.jar
  • commons-logging-x.y.z.jar
  • spring-aop-x.y.z.jar
  • spring-beans-x.y.z.jar
  • spring-context-x.y.z.jar
  • spring-core-x.y.z.jar
  • spring-expression-x.y.z.jar
  • spring-webmvc-x.y.z.jar
  • spring-web-x.y.z.jar

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:application.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

application.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"
       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.kernel.spring.web"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

HelloController.java

package com.kernel.spring.web.controller;

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

@Controller
public class HelloController {
    @RequestMapping("/hello")
    public String hello() {
        return "/success";
    }
}

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>SUCCESS!</h1>
</body>
</html>

HelloWorld 深度解析

  1. 当用户请求 /hello 这个路径时,首先经过 DispatcherServlet 的拦截。
  2. 然后通过 /hello 找到对应的 Controller。
  3. DispatcherServlet 将请求提交给 Controller。
  4. Controller 调用业务处理逻辑后,返回 ModelAndView。
  5. DispatcherServlet 查询一个或多个 ViewResoler 视图解析器,找到 ModelAndView 指定的视图。
  6. 将结果返回给客户端。

在这里插入图片描述

@RequestMapping

@RequestMapping 映射请求注解

在控制器的类定义及方法定义上都可以标注 @RequestMapping。

标记在类上:提供初步的映射信息,相当于 Web 应用的根目录。

标记在方法上:提供细分的映射信息,如果类上未标注该注解,那么方法处标记的 URL 相对于 Web 应用的根目录。

作用:DispatcherServlet 截获请求后,就根据 @RequestMapping 提供的映射信息确定请求所对应的处理方法。

源码参考

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
    String[] value() default {};

    RequestMethod[] method() default {};

    String[] params() default {};

    String[] headers() default {};

    String[] consumes() default {};

    String[] produces() default {};
}

上例代码分析

package com.kernel.spring.web.controller;

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

// 标注该类是一个控制器组件
@Controller
public class HelloController {
    // 映射请求信息
    @RequestMapping("/hello")
    public String hello() {
        // 返回值会通过视图解析器解析为实际的物理视图,然后做转发操作
        // 对于 InternalResourceViewResolver,会通过 prefix + returnValue + suffix 这样的方式得到实际的物理视图
        return "/success";
    }
}

@RequestMapping 映射请求方式

该注解除了可以使用请求 URL 映射请求外,还可以使用请求方法、请求参数和请求头映射请求。

结果使用可以让请求更加精确化

@Controller
public class TestController {
    @RequestMapping(value = "/testMethod", method = RequestMethod.POST)
    public String testMethod(){
        System.out.println("testMethod...");
        return "/success";
    }
}

如果以 get 方式请求,会报 405 请求不支持错误。

@RequestMapping 映射请求参数、请求头

@Controller
public class TestController {
    @RequestMapping(value="/testParamsAndHeaders", 
                    params= {"username","age!=10"}, 
                    headers = { "Accept-Language=en-US,zh;q=0.9" })
    public String testParamsAndHeaders(){
        System.out.println("testParamsAndHeaders...");
        return "/success";
    }
}

测试

http://localhost:8080/testParamsAndHeaders 不可以访问

http://localhost:8080/testParamsAndHeaders?username 可以访问

http://localhost:8080/testParamsAndHeaders?username&age 可以访问

http://localhost:8080/testParamsAndHeaders?username=kernel&age=10 不可以访问

结论:必须至少携带一个参数,参数可以传空,但是 age 不能等于10。

@RequestMapping 映射请求占位符

@PathVariable 可以将请求中的参数,传递给处理请求方法的入参中。

@Controller
public class TestController {
    @RequestMapping("/testPathVariable/{id}")
    public String testPathVariable(@PathVariable("id") Integer id){
        System.out.println("testPathVariable...id" + id);
        return "/success";
    }
}

必须携带一个整数类型的参数才可以访问。

HiddenHttpMethodFilter

什么是 REST 风格

REST:

即 Representational State Transfer。(资源)表现层状态转化。是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。

URL 风格:

HTTP 方法说明
POST新增资源
DELETE删除资源
PUT修改资源
GET获取资源

HiddenHttpMethodFilter:

总所周知,浏览器 from 表单只支持两种请求,即 GET 和 POST,而 PUT 和 DELETE 不支持,Spring 3.0 提供了一个过滤器,将这些请求转换为 PUT 和 DELETE 请求。

配置 HiddenHttpMethodFilter

web.xml

<filter>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

delete.jsp

<form action="/testRESTDelete/1" method="POST">
    <input type="hidden" name="_method" value="DELETE">
    <input type="submit" value="testRESTDelete">
</form>

源码参考

public class HiddenHttpMethodFilter extends OncePerRequestFilter {
    public static final String DEFAULT_METHOD_PARAM = "_method";
    private String methodParam = "_method";

    public HiddenHttpMethodFilter() {
    }

    public void setMethodParam(String methodParam) {
        Assert.hasText(methodParam, "'methodParam' must not be empty");
        this.methodParam = methodParam;
    }

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String paramValue = request.getParameter(this.methodParam);
        if ("POST".equals(request.getMethod()) && StringUtils.hasLength(paramValue)) {
            String method = paramValue.toUpperCase(Locale.ENGLISH);
            HttpServletRequest wrapper = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
            filterChain.doFilter(wrapper, response);
        } else {
            filterChain.doFilter(request, response);
        }

    }

    private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
        private final String method;

        public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
            super(request);
            this.method = method;
        }

        public String getMethod() {
            return this.method;
        }
    }
}

从源码看出来,Spring 根据请求中的 _method 参数进行转换,并且只对 POST 进行处理, GET 请求进行处理,所有如果想转换请求,必须要将表单提交方式设置成 POST,必须将 _method 的值设置为 DELETE 或 PUT。

请求数据传入

请求处理方法签名

Spring MVC 通过分析处理方法的签名,HTTP 请求信息绑定到处理方法的入参中。

Spring MVC 对控制器处理方法签名的限制是很宽松的,几乎可以按照喜欢的方式对方法签名。

必要时可以对方法及方法入参标注响应的注解(@PathVariable 、@RequestParam、@RequestHeader 等)。

Spring MVC 会将 HTTP 请求的信息绑定到响应的方法入参中,并根据方法的返回值做出相应的后续处理。

@RequestParam注解

在处理方法入参时使用该注解可以将请求参数传递给方法入参。

value:参数名

required:是否必须,默认为 true,若不存在,抛出异常。

defaultValue:默认值,没有传递参数的时候使用。

@Controller
public class TestController {
    @RequestMapping("/testRequestParam")
    public String testRequestParam(@RequestParam(value = "id",
                                                 required = false, 
                                                 defaultValue = "0") Integer id) {
        System.out.println("testRequestParam...id" + id);
        return "/success";
    }
}

@RequestHeader 注解

在处理方法入参时使用该注解可以将请求头传递给方法入参。

value:参数名

required:是否必须,默认为 true,若不存在,抛出异常。

defaultValue:默认值,没有传递参数的时候使用。

@Controller
public class TestController {
    @RequestMapping("/testRequestHeader")
    public String testHeader(@RequestHeader("Accept-Encoding") String encoding,                                          @RequestHeader("Connection") String connection) {
        System.out.println("testRequestHeader...Accept-Encoding" + encoding);
        System.out.println("testRequestHeader...Connection" + connection);
        return "/success";
    }
}

@CookieValue 注解

使用该注解可以绑定请求中的 Cookie 值。

value:参数名

required:是否必须,默认为 true,若不存在,抛出异常。

defaultValue:默认值,没有传递参数的时候使用。

@Controller
public class TestController {
    @RequestMapping("/testCookieValue")
    public String testCookieValue(@CookieValue("JSESSIONID") String jsessionid) {
        System.out.println("testCookieValue...JSESSIONID" + jsessionid);
        return "/success";
    }
}

使用POJO作为参数

使用 POJO 对象绑定请求参数值。

Spring MVC 会按照请求参数名和 POJO 属性进行自动匹配,还支持级联属性。

TestController.java

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

.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <form action="/testPOJO" method="post">
      ID:<input type="text" name="id"><br>
      账户:<input type="text" name="username"><br>
      密码:<input type="text" name="password"><br>
      邮箱:<input type="text" name="email"><br>
      年龄:<input type="text" name="age"><br>
      省份:<input type="text" name="address.province"><br>
      城市:<input type="text" name="address.city"><br>
      <input type="submit" value="提交">
  </form>
  </body>
</html>

使用原生 ServletAPI 作为参数

Spring MVC 的 Handler 方法可以接受哪些 ServletAPI 类型的参数?

  • HttpServletRequest
  • HttpServletResponse
  • HttpSession
  • java.security.Principal
  • Locale
  • InputStream
  • OutputStream
  • Reader
  • Writer

响应数据传出

Spring MVC 提供了以下几种途径输出模型数据:

ModelAndView:处理方法返回值为 ModelAndView 时,即可以通过该对象添加模型数据。

Map、Model:入参为 org.springframework.ui.Model、org.springframework.ui.ModelMap 或 java.uti.Map 时,处理方法返回时,Map 中的数据会自动添加到模型中。

@SessionAttributes:将模型中的数据暂存到 HttpSession中,以便在多个请求中共享这个属性。

@ModelAttribute:方法入参标注该注解后,入参的对象会放入到模型数据中。

ModelAndView

控制器如果返回类型是 ModelAndView,则其既可以包含视图信息,又可以包含模型数据信息。

添加模型:

MoelAndView addObject(String attributeName, Object attributeValue)

ModelAndView addAllObject(Map<String, ?> modelMap)

设置视图:

void setView(View view)

void setViewName(String viewName)

@Controller
public class TestController {
    @RequestMapping("/testModelAndView")
    public ModelAndView testModelAndView(){
        System.out.println("testModelAndView...");
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("/success");
        modelAndView.addObject("time",new Date());
        return modelAndView;
    }
}

事实上,返回的类型可以是很多种,可以是 ModelAndView 类型,也可以是 String 类型,还可以是 View 类型,还有很多。但是他们最终会被解析为 ModelAndView 类型的对象。

Map

Spring MVC 在内部使用了一个 org.springframework.ui.Model 接口存储模型数据。

具体使用步骤:

Spring MVC 在调用方法之前会创建一个隐含的模型对象作为模型对象的存储容器。

如果方法的入参为 Map 或者 Model 类型,Spring MVC 会将隐含模型的引用传递给入参。

在方法体内,可以通过这个入参对象访问到模型中的所有数据,也可以向模型中添加新的属性数据。

@Controller
public class TestController {
    @RequestMapping("/testMap")
    public String testMap(Map<String, User> map) {
        map.put("user", new User(1, "kernel", "123456", "kernel@qq.com", 18, 
                                 new Address("山东", "德州")));
        return "/success";
    }
}

@SessionAttribute

若希望在多个请求之间共用某个模型对象,可以在控制器上标志这个注解,Spring MVC 将对应的模型属性暂存到HttpSession 中。

@SessionAttributes(types=User.class) 会将隐含模型的所有类型为 User 的属性全部添加到会话中。
@SessionAttributes(value={“user1”, “user2”}) 将 user1、user2 添加到会话中。

源码参考

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SessionAttributes {
    String[] value() default {}; // 推荐使用

    Class<?>[] types() default {}; // 作用范围太广
}
@Controller
@SessionAttribue("user)
public class TestController {
    @RequestMapping("/testMap")
    public String testMap(Map<String, User> map) {
        // 如果该类有 @SessionAttribute 注解,会同时将 user 存放到 request 作用域和 session 作用域
        // 否则将 user 放到 request 作用域。
        map.put("user", new User(1, "kernel", "123456", "kernel@qq.com", 18, 
                                 new Address("山东", "德州")));
        return "/success";
    }
}

@ModelAttribute

使用场景?

假如说我修改一个订单的时候,订单的创建时间是不允许被修改的,所以我更新时,因为时间字段没有值,所有更新会将该字段更新为 null。

解决方法有:

隐藏域,字段多太麻烦,还有用户可以在源代码中看到隐藏域中的信息,不安全。

先查询数据库,然后一一赋值,比较麻烦。

下面使用 @ModelAttribute

在方法定义上使用该注解,会在调用目标方法之前逐个调用方法上标注该注解的的方法。

在方法的入参上标注该注解,可以从隐含对象中获取隐含模型数据中获取对象,再将请求参数绑定到对象中,在传入入参。

@Controller
@SessionAttribue("user)
public class TestController {
    @ModelAttribute
    public void getUser(@RequestParam(value = "id", required = false) Integer id,
    Map<String, User> map) {
        if (id != null) {
            User user = new User(1, "kernel", "123456", "kernel@qq.com", 18, 
            new Address("山东", "德州"));
            System.out.println(user);
            map.put("user", user);
        }
    }

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

原理分析:

执行 @ModelAttribute 注解所修饰的方法,将从数据库中获取的对象存放到 Map 集合中,key 为 user。

Spring MVC 从 map 中查找 user 对象,将表单数据封装到与参数名称对应的 user 对象属性上。

SpringMVC将user对象作为参数,传递给目标方法。

SpringMVC 确定目标方法 POJO 类型入参的过程:

确定一个 key:若目标方法的 POJO 类型的参数木有使用 @ModelAttribute 作为修饰,则 key 为 POJO 类名第一个字母的小写,若使用了@ModelAttribute 来修饰,,则 key 为 @ModelAttribute 注解的 value 属性值。

在 implicitModel 中查找 key 对应的对象, 若存在,则作为入参传入。

若 implicitModel 中不存在 key 对应的对象,则检查当前的 Handler 是否使用 @SessionAttributes 注解修饰。

若使用了该注解,且 @SessionAttributes 注解的 value 属性值中包含了 key,则会从 HttpSession 中来获取 key 所对应的 value 值,若存在则直接传入到目标方法的入参中,若不存在则将抛出异常。

若 Handler 没有标识 @SessionAttributes 注解或 @SessionAttributes 注解的 value 值中不包含 key,则会通过反射来创建 POJO 类型的参数,传入为目标方法的参数。

SpringMVC 会把 key 和 POJO 类型的对象保存到 implicitModel 中,进而会保存到 request 中。

视图解析

视图和视图解析器

请求处理方法执行后,会最终返回一个 ModelAndView 对象,对于那些返回了 String、Model、Map 等的处理方法,内部也会被装配成一个ModelAndView,它包含了逻辑名和模型对象的视图。

通过视图解析器得到最终的视图对象,最终的视图可以是 JSP、Excel、Pdf 等各种形式。

对于采取什么视图对模型渲染,处理器并不关心,从而实现 MVC 的充分解耦。

常见的视图类:

在这里插入图片描述
每个视图解析器都实现了 Ordered 接口并开放出一个 order 属性,可以通过 order 属性指定解析器的优先顺序,order 越小优先级越高。

SpringMVC 会按视图解析器顺序的优先顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则将抛出 ServletException 异常。

JstlView

若项目中使用了 JSTL,则 InternalResourceView 会转换成 JstlView。

若想使用 JSTL 的 fmt 标签,则必须配置国际化资源文件。

i8n国际化

# i18n.properties
i18n.username=Username
i18n.password=Password
# i18n_zh_CN.properties
i18n.username=用户名
i18n.password=密码
# i18n_en_US.properties
i18n.username=Username
i18n.password=Password

application.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"
       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.kernel.spring.web"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <bean name="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="i18n"/>
    </bean>
    <!-- 直接配置响应的页面:无需经过控制器来执行结果 -->
    <mvc:view-controller path="/success" view-name="success"/>
</beans>

自定义视图

若希望使用 Excel 展示数据列表,仅需要扩展 SpringMVC 提供的 AbstractExcelView 或 AbstractJExcelView 即可。

实现 buildExcelDocument() 方法,在方法中使用模型数据对象构建 Excel 文档就可以了。

AbstractExcelView 基于 POI API,而 AbstractJExcelView 是基于 JExcelAPI 的。

视图对象需要配置 IOC 容器中的一个 Bean ,使用 BeanNameViewResolver 作为视图解析器即可。

若希望直接在浏览器中直接下载 Excel 文档,则可以设置响应头 Content-Disposition 的值为attachment;filename=xxx.xls。

application.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"
       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.kernel.spring.web"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <bean name="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="i18n"/>
    </bean>
    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
        <property name="order" value="100"/>
    </bean>
</beans>

HelloView.java

@Component
public class HelloView implements View {
    @Override
    public String getContentType() {
        return "text/html";
    }

    @Override
    public void render(Map<String, ?> map, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        httpServletResponse.getWriter().print(new Date());
    }
}

测试

@Controller
@SessionAttribue("user)
public class TestController {
    @RequestMapping("/testView")
    public String testView(){
        System.out.println("testView...");
        // 与视图对象的 id 一致
        return "helloView"; 
    }
}

重定向

一般情况下,控制器方法返回字符串类型的值会被当成逻辑视图名处理

如果返回的字符串中带 forward: 或 redirect:前缀时,SpringMVC 会对他们进行特殊处理。

redirect:success.jsp:会完成一个到 success.jsp 的重定向的操作。

forward:success.jsp:会完成一个到 success.jsp 的转发操作。

数据绑定

数据绑定流程

Spring MVC 将 ServletRequest 对象及目标方法的入参传递给 WebDataBinderFactory,以创建 DataBinder 实例对象。

DataBinder 调用装配在上下文中的 ConversionService 组件进行数据类型转换、数据格式化工作。

调用 Vaidator 组件对已绑定了请求消息的入参对象进行数据合法性校验,最终生成数据绑定 BindingData 对象。

Spring MVC 抽取 BindingResult 中的入参对象和校验错误对象,将它们赋给处理方法的响应入参。

Spring MVC 通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是 DataBinder,运行机制如下:

在这里插入图片描述

自定义类型转换器

ConversionService 是 Spring 类型转换器的核心。

可以使用 ConversionServiceFactoryBean 在 Spring IOC 容器中定义一个 ConversionService,Spring 将自动识别出 IOC 容器中的 ConversionService,并在 Bean 属性配置及 Sprng MVC 处理方法入参绑定等场合使用它进行数据转换。

Spring 支持的转换器类型:

Converter<S, T>:将 S 类型对象转为 T 类型对象

ConverterFactory:将相同系列多个 “同质” Converter 封装在一起。如果希望将一种类型的对象转换为另一种类型及其子类的对象。

GenericConverter:会根据源类对象及目标类对象所在的宿主类中的上下文信息进行类型转换

StringToUserConverter.java

@Component
public class StringToUserConverter implements Converter<String, User> {

    @Override
    public User convert(String s) {
        if (s != null){
            String[] vals = s.split("-");
            String username = vals[0];
            String password = vals[1];
            String email = vals[2];
            Integer age = Integer.valueOf(vals[3]);
            String province = vals[4];
            String city = vals[5];
            return new User(null, username, password,email,age , new Address(province, city));
        }
        return null;
    }
}

application.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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <context:component-scan base-package="com.kernel.spring.web"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <bean name="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="i18n"/>
    </bean>
    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
        <property name="order" value="100"/>
    </bean>
    <bean id="serviceFactoryBean" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <ref bean="stringToUserConverter"/>
            </set>
        </property>
    </bean>
    <mvc:annotation-driven conversion-service="serviceFactoryBean"/>
</beans>

测试

@Controller
@SessionAttribue("user)
public class TestController {
    @RequestMapping("/testConverter")
    public String testConverter(User user) {
        System.out.println(user);
        return "/success";
    }
}

<mvc:annotation-driven>

直接配置响应的页面,无需经过控制器的处理,但是直接影响其他请求路径失效。

找不到静态资源需要配置,\<mvc:default-servlet-handler> 将在 SpringMVC 上下文中定义一个DefaultServletHttpRequestHandler,它会对进入 DispatcherServlet 的请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由 WEB 应用服务器默认的 Servlet 处理,如果不是静态资源的请求,才由 DispatcherServlet 继续处理。

配置类型转换器时,需要指定转换器引用。

完成 JSR303 数据验证,也需要配置该标签。

作用:

会自动注册:RequestMappingHandlerMapping、RequestMappingHandlerAdapter 与ExceptionHandlerExceptionResolver 三个 bean。

还将提供以下支持:

支持使用 ConversionService 实例对表单参数进行类型转换。

支持使用 @NumberFormat、@DateTimeFormat 注解完成数据类型的格式化。

支持使用 @Valid 注解对 JavaBean 实例进行 JSR 303 验证。

支持使用 @RequestBody 和 @ResponseBody 注解。

@InitBinder注解

由 @InitBinder 标识的方法,可以对 WebDataBinder 对象进行初始化,WebDataBinder 是 DataBinder 的子类,用于完成从表单字段到 JavaBean 的绑定。

由 @InitBinder 标识的方法不能有返回值,必须是 void 类型。

由 @InitBinder 表示的方法入参通常是 WebDataBinder。

数据的格式化

Spring 在格式化模块中定义了一个实现了 ConversionService 接口的 FormattingConversionService 实现类,该实现类扩展了 GenericConversionService 实现类,该类既有类型转换的功能,又有格式化的功能。

FormattingConversionService 拥有一个 FormattingConversionServiceFactory 工厂类,后者用于在 Spring 上下文中给构造前者,FormattingConversionServiceFactory 内部注册了:

NumberFormatAnnotationFormatterFactroy:支持对数字使用 @NumberFormat 注解。

odaDateTimeFormatAnnotationFormatterFactroy:支持对日期类型使用 @DataTimeFormat 注解。

装配了 FormattingConversionServiceFactroyBean 后,就可以在 Spring MVC 入参绑定及模型数据输出时使用注解驱动了。

@DataTimeFormat 可以对 Date、Calendar、Long 时间类型进行标注。

pattern:类型为字符串,指定解析/格式化字符串的模式。

iso:类型为 DateTimeFormat.ISO,指定解析/格式化字符串的 ISO 模式,包括四种:ISO.NONE(不使用,默认)、ISO.DATE(yyyy-MM-dd)、ISO.TIME(hh:mm:ss:SSSZ)、ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ)

style:字符串类型,通过样式指定日期时间的格式,由两位字符组成,第一位表示日期的格式,第二位表示时间的格式:S(段日期/时间格式)、M(中日期/时间格式)、L(长日期/时间格式)、F(完整日期/时间格式)。

@NumberFormat,可对类似数字类型的属性进行标注。

pattern:类型为 String,自定义样式,如 "#,###"。

style:用于指定样式类型,包括Style.NUMBER(正常数字类型)、Style.CURRENCY(货币类型)、
Style.PERCENT(百分数类型)。

JSR303 数据校验

使用 JSR303 验证标准

加入 hibernate validator 验证框架

在 application.xml 文件中配置 \<mvc:annotation-driven/>

在 Bean 属性上增加对应的验证注解

在目标方法的 Bean 类型的前面增加 @Valid 注解

错误回显及格式化

Spring MVC 除了会将表单/命令对象的校验结果保存到对应的 BindingResult 或 Errors 对象中外,还会将所有校验结果保存到 “隐含模型”。
即使处理方法的签名中没有对应于表单/命令对象的结果入参,校验结果也会保存在 “隐含对象” 中。
隐含模型中的所有数据最终将通过 HttpServletRequest 的属性列表暴露给 JSP 视图对象,因此在 JSP 中可以获取错误信息。
在 JSP 页面上可通过 <form:errors path=“userName”> 显示错误消息。

<form:errors path="*"/> 显示所有的错误信息

<form:errors path="lastName"/> 显示某个表单域的错误信息

每个属性在数据绑定和数据校验发生错误时,都会生成一个对应的 FieldError 对象。

当一个属性校验失败后,校验框架会为该属性生成 4 个消息代码,这些代码以校验注解类名为前缀,结合 modleAttribute、属性名及属性类型名生成多个对应的消息代码:

例如 User 类中的 password 属性标注了一个 @Pattern 注解,当该属性值不满足 @Pattern 所定义的规则时, 就会产生以下 4 个错误代码:
Pattern.user.password
Pattern.password
Pattern.java.lang.String
Pattern
当使用 Spring MVC 标签显示错误消息时, Spring MVC 会查看 WEB 上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息,否则使用国际化消息。
若数据类型转换或数据格式转换时发生错误,或该有的参数不存在,或调用处理方法时发生错误,都会在隐含模型中创建错误消息。

其错误代码前缀说明如下:
required:必要的参数不存在。如 @RequiredParam(“param1”) 标注了一个入参,但是该参数不存在
typeMismatch:在数据绑定时,发生数据类型不匹配的问题
methodInvocation:Spring MVC 在调用处理方法时发生了错误

国际化

国际化页面中获取国际化资源信息

使用 JSTL

application.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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <context:component-scan base-package="com.kernel"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="i18n"/>
    </bean>
    <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"/>
    <mvc:interceptors>
        <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
    </mvc:interceptors>
    <mvc:view-controller path="/i18n1" view-name="i18n1"/>
    <mvc:view-controller path="/i18n2" view-name="i18n2"/>
    <mvc:annotation-driven/>
</beans>

i18n1.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <fmt:message key="i18n.username"/>
    <a href="i18n2">i18n2</a>
</body>
</html>

i18n2.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <fmt:message key="i18n.password"/>
    <a href="i18n1">i18n1</a>
</body>
</html>

在 Bean 中获取国际化资源文件 Locale 对应的信息

@Controller
public class TestController {
    @Autowired
    private ResourceBundleMessageSource messageSource;

    @RequestMapping("/i18n")
    public String testi18n(Locale locale){
        System.out.println(locale);
        String userName = messageSource.getMessage("i18n.username", null, locale);
        System.out.println("i18n.username="+userName);
        return "i18n";
    }
}

通过超链接切换 Locale,而不再依赖于浏览器的语言设置情况。

<?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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <context:component-scan base-package="com.kernel"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="i18n"/>
    </bean>
    <!-- 配置SessionLocaleResolver -->
    <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"/>
    <!-- 配置LocaleChangeInterceptor拦截器 -->
    <mvc:interceptors>
        <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
    </mvc:interceptors>
    <mvc:view-controller path="/i18n1" view-name="i18n1"/>
    <mvc:view-controller path="/i18n2" view-name="i18n2"/>
    <mvc:annotation-driven/>
</beans>

测试:

http://localhost:8080/i18n?locale=zh_CN

http://localhost:8080/i18n?locale=en_US

文件上传

Spring MVC 为文件上传提供了直接的支持,这种支持是通过即插即用的 MultipartResolver 实现的。
Spring 用 Jakarta Commons FileUpload 技术实现了一个 MultipartResolver 实现类:

CommonsMultipartResovler :
Spring MVC 上下文中默认没有装配 MultipartResovler,因此默认情况下不能处理文件的上传工作,如果想使用 Spring 的文件上传功能,需现在上下文中配置 MultipartResolver。

application.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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <context:component-scan base-package="com.kernel"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <!-- 配置文件上传解析器 -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="defaultEncoding" value="UTF-8"/>
        <property name="maxUploadSize" value="5242880"/>
    </bean>
    <mvc:annotation-driven/>
</beans>

upload.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <form action="testUpload" method="post" enctype="multipart/form-data">
      文件: <input type="file" name="file"/><br><br>
      描述: <input type="text" name="desc"/><br><br>
      <input type="submit" value="提交"/>
  </form>

  </body>
</html>

测试

@Controller
public class TestController {
    @Autowired
    private ResourceBundleMessageSource messageSource;

    private static void writeToLocal(String destination, InputStream input)
            throws IOException {
        int index;
        byte[] bytes = new byte[1024];
        FileOutputStream downloadFile = new FileOutputStream(destination);
        while ((index = input.read(bytes)) != -1) {
            downloadFile.write(bytes, 0, index);
            downloadFile.flush();
        }
        downloadFile.close();
        input.close();
    }

    @RequestMapping(value = "/testUpload", method = RequestMethod.POST)
    public String testUpload(@RequestParam(value = "desc",required = false) String desc,
                             @RequestParam("file")MultipartFile multipartFile) throws IOException {
        System.out.println("desc " + desc);
        System.out.println("OriginalFilename" + multipartFile.getOriginalFilename());
        InputStream inputStream = multipartFile.getInputStream();
        System.out.println("available " + inputStream.available());
        System.out.println("inputStream " + inputStream);
        writeToLocal(multipartFile.getOriginalFilename(), inputStream);
        return "success";
    }
}

拦截器

自定义拦截器

Spring MVC 可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定功能,自定义拦截器必须实现 HandlerInterceptor 接口。

preHandle():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理。如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true;如果程序员决定不需要再调用其他的组件去处理请求,则返回false。

postHandle():这个方法在业务处理器处理完请求后,但是DispatcherServlet 向客户端返回响应前被调用,在该方法中对用户请求request进行处理。

afterCompletion():这个方法在 DispatcherServlet 完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。

<?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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <context:component-scan base-package="com.kernel"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <mvc:interceptors>
        <bean class="com.kernel.interceptor.FirstHandlerInterceptor"/>
        <bean class="com.kernel.interceptor.SecondHandlerInterceptor"/>
    </mvc:interceptors>
    <mvc:annotation-driven/>
</beans>

多个拦截器的执行顺序

按照拦截器的配置的顺序依次调用每个拦截器的 preHandle 方法,当请求业务处理器执行完毕后,依次对倒叙对每个拦截器放行,执行 postHandle 方法,当请求完全处理完毕后,然后依次倒叙执行每个拦截器的 afterCompletion 方法。

异常处理

Spring MVC 通过 HandlerExceptionResolver 处理程序的异常,包括 Handler 映射、数据绑定及目标方法执行时发生的异常。

HandlerExceptionResolver

@ExceptionHandler

可以通过 @ExceptionHandler(value = {java.lang.RuntimeException.class}) 的方式捕捉一个异常,如果捕捉成功,自动执行标志该注解的方法。

@ExceptionHandler(value = {java.lang.RuntimeException.class})
public ModelAndView handlerException2(Exception ex) {
    ModelAndView mv = new ModelAndView("error");
    System.out.println("出现异常啦!" + ex);
    mv.addObject("exception", ex);
    return mv;
}

如何将异常对象从控制器携带给页面

可以通过 ModelAndView 对象将异常对象添加。

异常对象捕捉的优先级

Spring MVC 是有优先级的,他会执行离捕捉异常离发生异常最近的那个方法。

@ControllerAdvice

该注解是定义在类级别上的,在类上标注了该注解后,所有控制器上发生了注解之后都会通过这个类的方法处理。

ExceptionHandlerExceptionResolver

主要处理 Handler 中用 @ExceptionHandler 注解定义的方法。

@ExceptionHandler 注解定义的方法优先级问题:

例如发生的是NullPointerException,但是声明的异常有 RuntimeException 和 Exception,此候会根据异常的最近继承关系找到继承深度最浅的那个 @ExceptionHandler 注解方法,即标记了 RuntimeException 的方法

ExceptionHandlerMethodResolver 内部若找不到 @ExceptionHandler 注解的话,会找 @ControllerAdvice 中的@ExceptionHandler 注解方法。

ResponseStatusExceptionResolver

在类上标注 @ResponseStatus 注解后,可以自定义状态码及提示信息。

@ResponseStatus(value=HttpStatus.FORBIDDEN, reason="用户名和密码不匹配")

DefaultHandlerExceptionResolver

对一些特殊的异常进行处理,比如:
NoSuchRequestHandlingMethodException、HttpRequestMethodNotSupportedException、HttpMediaTypeNotSupportedException、HttpMediaTypeNotAcceptableException等。

SimpleMappingExceptionResolver

如果希望对所有异常进行统一处理,可以使用 SimpleMappingExceptionResolver,它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <prop key="java.lang.ArithmeticException">error</prop>
        </props>
    </property>
</bean>

Spring Web 运行流程

  1. 用户向服务器发送请求,请求被 Spring MVC DispatcherServlet 捕获。
  2. DispatcherServlet 对请求 URL 进行解析,得到请求资源标识符(URI),判断是否存在。
  3. 如果不存在,判断是否配置了 \<mvc:default-servlet-handler>,如果配置了,执行目标资源(一般为静态资源);如果没有配置,客户端显示 404 错误。
  4. 如果存在,根据该 URI,调用 HandlerMapping 获得该 Handler 配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以 HandlerExecutionChain 对象的形式返回。
  5. DispatcherServlet 根据获得的 Handler,选择一个合适的 HandlerAdapter。
  6. 获得 HandlerAdapter 后,开始正序执行拦截器的 preHandler 方法。
  7. 提取 Request 中的模型数据,填充 Handler 入参,开始执行 Handler 方法,处理请求(数据转换、数据格式化、数据验证)。
  8. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象。
  9. 逆向执行拦截器的 postHandler 方法。
  10. 根据返回的 ModelAndView 选择一个合适的视图解析器来渲染视图。
  11. 在返回给客户端时需要逆向执行拦截器的 AfterCompletion 方法。
  12. 将渲染结果返回给客户端。

转载于:https://blog.51cto.com/13559120/2371543

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值