Spring WebMvc基础-json支持与静态资源配置

从 0 开始深入学习 Spring 专栏目录总览

上一章小册在最后留了一个坑,就是 findById 的那个 RESTful 风格的方法,返回值是一个 Department 对象,如何将这个 Department 对象转换为 json 响应给浏览器呢?这一章我们先来解决这个问题。

1. json支持【掌握】

json 格式的数据想必各位都熟悉吧,当下最流行的异步请求数据下,数据响应的承载大多都是 json 格式了。与 xml 对比起来,json 格式的数据更简明,且更符合对象的思维,再加上浏览器的 JavaScript 解析 json 也很方便,所以我们现在基本都用 json 了。SpringWebMvc 当然也对 json 提供了支持,下面我们来讲解 SpringWebMvc 如何支持 json 转换。

1.1 添加依赖

既然是支持 json ,那干活的无非两种:要么 SpringWebMvc 自己干,要么找个第三方的工具去干。SpringWebMvc 选择了后者,它可以整合主流的 json 转换工具,例如 jackson 、fastjson 、gson 等。SpringWebMvc 默认用 jackson 进行 json 格式转换,因为在这几个常用的 json 转换工具中,jackson 的工作效率比较高。

fastjson 老容易曝出一些漏洞,所以有蛮多公司和团队不允许用 fastjson 的,毕竟万一遇到漏洞,触发了,咱自己是担当不起。所以在实际的项目开发中,都是以比较成熟的、问题少的、大家都比较熟悉的技术去选型。

大概了解了背景之后,接下来我们来添加依赖。对于 SpringFramework 5.2.8 而言,小册推荐你使用 jackson 的 2.10.5 版本,所以我们只需要在 pom.xml 中添加以下三个依赖即可:

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>2.10.5</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.10.5</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>2.10.5</version>
    </dependency>

这个版本其实是参考的 SpringBoot 2.2.9 版本,因为 2.2.9 版本刚好对应 SpringFramework 5.2.8 版本,而 2.2.9 的 SpringBoot 依赖的 jackson 版本是 2.10.5 ,所以,你懂得啦,人家帮我们选好了,我们直接拿来用就没有问题。

1.2 配置json转换器

导入依赖还不够,想让jackson 干活,还需要配置一下 json 的转换器。使用 xml 的话有两种办法可以配置,一一来看:

1.2.1 一个标签解决

其实很简单,在 spring-mvc.xml 中添加一个标签:<mvc:annotation-driven/> ,立马就好使了。因为这个标签的背后注册了好多 bean ,也顺便帮我们注册了有关 json 的转换器。

至于里面的原理,小册放到 WebMvc 的原理部分来解释,这里先留一个悬念。

1.2.2 手动配置

手动配置 json 转换器的话,要写的东西就比较复杂了,我们需要在 xml 配置文件中显式的配置一个 HandlerAdapter 。这个东西是啥我们先不用管,到 WebMvc 进阶部分我们会详细解释 WebMvc 中的重要组件,这里先照着小册的写就没问题:

<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
        </list>
    </property>
</bean>

这里面的核心 json 转换器的名称叫 MappingJackson2HttpMessageConverter ,名字比较长,小伙伴们不用刻意的去记,知道有这么个东西就行。

1.2.3 注解配置

emmmm 如果是使用注解驱动的话,压根不需要配置。。。因为 @EnableWebMvc 注解已经帮我们注册好了,所以我们可以直接往下进行就可以。

1.3 @ResponseBody

接下来就是如何将对象转 json 的过程了。回到 RestfulDepartmentController 中,我们只需要在 findById 方法上,标注一个 @ResponseBody 注解,就可以实现 json 数据的响应了。对,就这么简单。

重启 Tomcat ,并在浏览器访问 http://localhost:8080/spring-webmvc/department/6ded6d3bdc8f4fc70bcc4347822a5ca3 ,可以发现 Department 对象已经成功被转换为 json 数据了:

Spring WebMvc基础-json支持与静态资源配置

1.4 @RestController

以后的项目开发中,可能会出现一种情况:一个 Controller 中定义的所有方法,都是以 json 格式的数据返回的,换句话说,这个 Controller 里没有任何视图跳转,那这个时候就没有必要在每个方法上都标注 @ResponseBody 注解了,而是直接在 Controller 类上标注 @ResponseBody 注解即可,也可以用 @RestController 代替 @Controller 注解。

//@Controller
@RestController
public class JsonDepartmentController { ... }

翻开源码,可以发现 @RestController 的本质就是 @Controller + @ResponseBody :

@Controller
@ResponseBody
public @interface RestController 

1.5 @RequestBody

接下来我们继续说 @RequestBody 这个注解。各位一看就知道,它跟 @ResponseBody 是一对,@ResponseBody 是处理响应体数据,将对象转为 json ,那相对应的,@RequestBody 就应该是将请求的 json 数据转换为对象吧!哎它就是这么回事,所以接下来我们来测试一下 @RequestBody 的使用。

再写一个新的 Controller 吧,依然是在里面定义一个 saveJson 的方法(这样就不会跟前面的 save 方法起冲突了):

@Controller
public class JsonDepartmentController {
    
    @PostMapping("/department/saveJson")
    @ResponseBody
    public void saveJson(@RequestBody Department department) {
        System.out.println(department);
    }
}

这里我们为了能使用 json 的方式接收数据,department 参数的前面就要使用 @RequestBody 注解来修饰了,这样 SpringWebMvc 在参数绑定的时候,就不会从请求参数上找了,而是从请求体找。

1.5.1 postman模拟测试

下面我们要模拟请求,还是跟上一章一样,我们先用 postman 来测试一下效果。

用 postman 构建纯 json 的请求,需要在请求头 header 中添加一个 content-type 为 application/json :

Spring WebMvc基础-json支持与静态资源配置

然后,切换到 body 中,这里面不要用 form-data ,用 raw ,并将右边的格式调整为 json ,这样就可以编写 json 数据了。使用 raw 类型的数据,在发送时会按照下面输入框的内容原样发送。

Spring WebMvc基础-json支持与静态资源配置

好,这样一切都搞定了,发送请求,发现 postman 中没有任何响应,再切回 IDE ,发现控制台中确实打印了 Department 的数据:

Department{id='null', name='abaaba'}

说明 @RequestBody 的使用完全没有问题。

1.5.2 jQuery模拟测试

下面我们再在浏览器上,借助 jQuery 简化 ajax 的请求,来试一下效果。

在 deptInfo.jsp 中,引入 webapp 中的 jQuery.min.js :

<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery.min.js"></script>

接下来,在 <body> 之前添加一段 js 脚本:

    $(function() {
        $("#save-button").click(function() {
            $.ajax({
                url: '${pageContext.request.contextPath}/department/saveJson',
                type: 'post',
                data: JSON.stringify({
                    id: $("[name='id']").val(),
                    name: $("[name='name']").val(),
                    tel: $("[name='tel']").val()
                }),  // 这里也可以直接写json字符串,也可以借助ES5中的JSON进行字符串序列化
                contentType: 'application/json;charset=utf-8',
                success: function(data) {
                    alert("修改成功!");
                }
            });
        });
    });

之后重新部署 jsp 页面,或者直接重启 Tomcat ,然后通过 deptList 页面点击修改,跳转到 deptInfo 页:

Spring WebMvc基础-json支持与静态资源配置

接下来我们什么也不改,直接点保存,,,

点保存,,,点,,,咋没反应呢???难不成页面报错了吗?

打开 Chrome 的 Network ,发现 jquery.min.js 报了 404 的错误:

Spring WebMvc基础-json支持与静态资源配置

哎?我的 js 明明存在啊?为啥你给我报个 404 呢?哎,这就牵扯到 SpringWebMvc 对静态资源的处理了。

2. 静态资源配置【掌握】

我们先来解释上面报 404 的问题。

2.1 静态资源找不到的原因

之所以报 404 ,全都是因为我们之前在 web.xml 中配置了这个东东:

    <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:spring-mvc-withdao.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!-- !!!!!这里!!!!! -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

当 url-pattern 配置为 / 时,会拦截静态资源的!但 SpringWebMvc 并不懂静态资源怎么解析,也不知道去哪里找,于是就给我们直接扔了个 404 。

那要解决这个问题,两种方案:要么 DispatcherServlet 放弃拦截 / ,但这种方式貌似有点不太现实。。。因为要拦截的路径实在是多,还是直接写 / 比较省心;要么就告诉 SpringWebMvc ,如果碰到静态资源文件,应该去哪里找。很明显,比较合适的方式是第二种,所以接下来我们来学习如何配置静态资源的解析。

2.2 静态资源的解析配置

针对 xml 配置文件,和注解配置的方式,我们分别来讲解。

2.2.1 xml配置文件配置

xml 配置文件的方式,只需要在 spring-mvc.xml 中添加这样一句配置:

    <mvc:resources mapping="/js/**" location="/js/" />

如果回头再有 css ,即便是放在 /WEB-INF 下,那也可以这样写:

    <mvc:resources mapping="/css/**" location="/WEB-INF/css/" />

2.2.2 注解方式配置

注解驱动的配置,要找到之前的那个 EnableWebMvcConfiguration ,因为它已经实现了 WebMvcConfigurer 接口了,所以我们可以直接重写它的 addResourceHandlers 方法来配置静态资源解析规则:

public class EnableWebMvcConfiguration implements WebMvcConfigurer {
    // ......
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
    }
}

2.3 静态资源的缓存

一般来讲,静态资源都会在浏览器中缓存,这样就不会每次请求时重复加载。webmvc 支持配置这些静态资源的缓存时长,具体设置也很简单,直接在 mvc:resources 标签上配置 cache-period 标签即可,单位为秒:

    <mvc:resources mapping="/js/**" location="/js/" cache-period="2592000"/>

对应的,注解配置的 WebMvcConfigure 写起来稍微费点劲,具体可以看下面,不过好处是可以直接指定缓存的单位,也挺方便:

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/js/**").addResourceLocations("/js/")
                .setCacheControl(CacheControl.maxAge(Duration.ofDays(30)));
    }

2.4 检验@RequestBody的测试

配置完成后,重启 Tomcat ,并重新访问 http://localhost:8080/spring-webmvc/department/edit?id=84fef077c8dd97be89b2dff7de601073 ,点击保存,浏览器可以弹出修改成功的提示,证明一切正确。

3. ServletAPI的获取和使用【掌握】

本章最后一小节,小册多介绍一点有关 SpringWebMvc 与原生 Servlet API 的使用。

3.1 request和response的获取

除了之前我们学过的,可以在 Controller 的方法参数上声明 HttpServletRequest 和 HttpServletResponse ,以及直接在 Controller 类中 @Autowired 之外,还可以通过一个全局的 API 来获取,它的使用方式也很简单:

    public HttpServletRequest getRequest() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        return servletRequestAttributes.getRequest();
    }

借助 RequestContextHolder ,就可以取到当前请求中的一个 “请求属性” ,这里面就组合了 request 和 response 。而且这个 RequestContextHolder 还是静态的,所以我们可以封装一些类似于 ServletUtils 之类的工具类:

public class ServletUtils {
    public static HttpServletRequest getRequest() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes.getRequest();
    }
    
    public static HttpServletResponse getResponse() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes.getResponse();
    }
}

3.2 Servlet原生的请求转发和重定向

利用 Servlet 原生的 API 也能实现转发和重定向,而且写法也都是我们之前学习的熟悉的方法。例如下面的方法就是一个重定向到 /department/list 的处理:

@RequestMapping("/department/save2")
public void save2(HttpServletRequest request, HttpServletResponse response, Department department)
    throws IOException, ServletException {
    System.out.println(department);
    request.getRequestDispatcher(request.getContextPath() + "/department/list").forward(request, response);
}

重定向的也很简单:

@RequestMapping("/department/save2")
public void save2(HttpServletRequest request, HttpServletResponse response, Department department)
    throws IOException {
    System.out.println(department);
    response.sendRedirect(request.getContextPath() + "/department/list");
}

当然,如果要这么写了,那返回值就一定是 void ,否则两边不一致,产生的结果可能是意料之外的,那就麻烦大了。

上一章小册在最后留了一个坑,就是 findById 的那个 RESTful 风格的方法,返回值是一个 Department 对象,如何将这个 Department 对象转换为 json 响应给浏览器呢?这一章我们先来解决这个问题。

1. json支持【掌握】

json 格式的数据想必各位都熟悉吧,当下最流行的异步请求数据下,数据响应的承载大多都是 json 格式了。与 xml 对比起来,json 格式的数据更简明,且更符合对象的思维,再加上浏览器的 JavaScript 解析 json 也很方便,所以我们现在基本都用 json 了。SpringWebMvc 当然也对 json 提供了支持,下面我们来讲解 SpringWebMvc 如何支持 json 转换。

1.1 添加依赖

既然是支持 json ,那干活的无非两种:要么 SpringWebMvc 自己干,要么找个第三方的工具去干。SpringWebMvc 选择了后者,它可以整合主流的 json 转换工具,例如 jackson 、fastjson 、gson 等。SpringWebMvc 默认用 jackson 进行 json 格式转换,因为在这几个常用的 json 转换工具中,jackson 的工作效率比较高。

fastjson 老容易曝出一些漏洞,所以有蛮多公司和团队不允许用 fastjson 的,毕竟万一遇到漏洞,触发了,咱自己是担当不起。所以在实际的项目开发中,都是以比较成熟的、问题少的、大家都比较熟悉的技术去选型。

大概了解了背景之后,接下来我们来添加依赖。对于 SpringFramework 5.2.8 而言,小册推荐你使用 jackson 的 2.10.5 版本,所以我们只需要在 pom.xml 中添加以下三个依赖即可:

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>2.10.5</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.10.5</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>2.10.5</version>
    </dependency>

这个版本其实是参考的 SpringBoot 2.2.9 版本,因为 2.2.9 版本刚好对应 SpringFramework 5.2.8 版本,而 2.2.9 的 SpringBoot 依赖的 jackson 版本是 2.10.5 ,所以,你懂得啦,人家帮我们选好了,我们直接拿来用就没有问题。

1.2 配置json转换器

导入依赖还不够,想让jackson 干活,还需要配置一下 json 的转换器。使用 xml 的话有两种办法可以配置,一一来看:

1.2.1 一个标签解决

其实很简单,在 spring-mvc.xml 中添加一个标签:<mvc:annotation-driven/> ,立马就好使了。因为这个标签的背后注册了好多 bean ,也顺便帮我们注册了有关 json 的转换器。

至于里面的原理,小册放到 WebMvc 的原理部分来解释,这里先留一个悬念。

1.2.2 手动配置

手动配置 json 转换器的话,要写的东西就比较复杂了,我们需要在 xml 配置文件中显式的配置一个 HandlerAdapter 。这个东西是啥我们先不用管,到 WebMvc 进阶部分我们会详细解释 WebMvc 中的重要组件,这里先照着小册的写就没问题:

<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
        </list>
    </property>
</bean>

这里面的核心 json 转换器的名称叫 MappingJackson2HttpMessageConverter ,名字比较长,小伙伴们不用刻意的去记,知道有这么个东西就行。

1.2.3 注解配置

emmmm 如果是使用注解驱动的话,压根不需要配置。。。因为 @EnableWebMvc 注解已经帮我们注册好了,所以我们可以直接往下进行就可以。

1.3 @ResponseBody

接下来就是如何将对象转 json 的过程了。回到 RestfulDepartmentController 中,我们只需要在 findById 方法上,标注一个 @ResponseBody 注解,就可以实现 json 数据的响应了。对,就这么简单。

重启 Tomcat ,并在浏览器访问 http://localhost:8080/spring-webmvc/department/6ded6d3bdc8f4fc70bcc4347822a5ca3 ,可以发现 Department 对象已经成功被转换为 json 数据了:

Spring WebMvc基础-json支持与静态资源配置

1.4 @RestController

以后的项目开发中,可能会出现一种情况:一个 Controller 中定义的所有方法,都是以 json 格式的数据返回的,换句话说,这个 Controller 里没有任何视图跳转,那这个时候就没有必要在每个方法上都标注 @ResponseBody 注解了,而是直接在 Controller 类上标注 @ResponseBody 注解即可,也可以用 @RestController 代替 @Controller 注解。

//@Controller
@RestController
public class JsonDepartmentController { ... }

翻开源码,可以发现 @RestController 的本质就是 @Controller + @ResponseBody :

@Controller
@ResponseBody
public @interface RestController 

1.5 @RequestBody

接下来我们继续说 @RequestBody 这个注解。各位一看就知道,它跟 @ResponseBody 是一对,@ResponseBody 是处理响应体数据,将对象转为 json ,那相对应的,@RequestBody 就应该是将请求的 json 数据转换为对象吧!哎它就是这么回事,所以接下来我们来测试一下 @RequestBody 的使用。

再写一个新的 Controller 吧,依然是在里面定义一个 saveJson 的方法(这样就不会跟前面的 save 方法起冲突了):

@Controller
public class JsonDepartmentController {
    
    @PostMapping("/department/saveJson")
    @ResponseBody
    public void saveJson(@RequestBody Department department) {
        System.out.println(department);
    }
}

这里我们为了能使用 json 的方式接收数据,department 参数的前面就要使用 @RequestBody 注解来修饰了,这样 SpringWebMvc 在参数绑定的时候,就不会从请求参数上找了,而是从请求体找。

1.5.1 postman模拟测试

下面我们要模拟请求,还是跟上一章一样,我们先用 postman 来测试一下效果。

用 postman 构建纯 json 的请求,需要在请求头 header 中添加一个 content-type 为 application/json :

Spring WebMvc基础-json支持与静态资源配置

然后,切换到 body 中,这里面不要用 form-data ,用 raw ,并将右边的格式调整为 json ,这样就可以编写 json 数据了。使用 raw 类型的数据,在发送时会按照下面输入框的内容原样发送。

Spring WebMvc基础-json支持与静态资源配置

好,这样一切都搞定了,发送请求,发现 postman 中没有任何响应,再切回 IDE ,发现控制台中确实打印了 Department 的数据:

Department{id='null', name='abaaba'}

说明 @RequestBody 的使用完全没有问题。

1.5.2 jQuery模拟测试

下面我们再在浏览器上,借助 jQuery 简化 ajax 的请求,来试一下效果。

在 deptInfo.jsp 中,引入 webapp 中的 jQuery.min.js :

<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery.min.js"></script>

接下来,在 <body> 之前添加一段 js 脚本:

    $(function() {
        $("#save-button").click(function() {
            $.ajax({
                url: '${pageContext.request.contextPath}/department/saveJson',
                type: 'post',
                data: JSON.stringify({
                    id: $("[name='id']").val(),
                    name: $("[name='name']").val(),
                    tel: $("[name='tel']").val()
                }),  // 这里也可以直接写json字符串,也可以借助ES5中的JSON进行字符串序列化
                contentType: 'application/json;charset=utf-8',
                success: function(data) {
                    alert("修改成功!");
                }
            });
        });
    });

之后重新部署 jsp 页面,或者直接重启 Tomcat ,然后通过 deptList 页面点击修改,跳转到 deptInfo 页:

Spring WebMvc基础-json支持与静态资源配置

接下来我们什么也不改,直接点保存,,,

点保存,,,点,,,咋没反应呢???难不成页面报错了吗?

打开 Chrome 的 Network ,发现 jquery.min.js 报了 404 的错误:

Spring WebMvc基础-json支持与静态资源配置

哎?我的 js 明明存在啊?为啥你给我报个 404 呢?哎,这就牵扯到 SpringWebMvc 对静态资源的处理了。

2. 静态资源配置【掌握】

我们先来解释上面报 404 的问题。

2.1 静态资源找不到的原因

之所以报 404 ,全都是因为我们之前在 web.xml 中配置了这个东东:

    <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:spring-mvc-withdao.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!-- !!!!!这里!!!!! -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

当 url-pattern 配置为 / 时,会拦截静态资源的!但 SpringWebMvc 并不懂静态资源怎么解析,也不知道去哪里找,于是就给我们直接扔了个 404 。

那要解决这个问题,两种方案:要么 DispatcherServlet 放弃拦截 / ,但这种方式貌似有点不太现实。。。因为要拦截的路径实在是多,还是直接写 / 比较省心;要么就告诉 SpringWebMvc ,如果碰到静态资源文件,应该去哪里找。很明显,比较合适的方式是第二种,所以接下来我们来学习如何配置静态资源的解析。

2.2 静态资源的解析配置

针对 xml 配置文件,和注解配置的方式,我们分别来讲解。

2.2.1 xml配置文件配置

xml 配置文件的方式,只需要在 spring-mvc.xml 中添加这样一句配置:

    <mvc:resources mapping="/js/**" location="/js/" />

如果回头再有 css ,即便是放在 /WEB-INF 下,那也可以这样写:

    <mvc:resources mapping="/css/**" location="/WEB-INF/css/" />

2.2.2 注解方式配置

注解驱动的配置,要找到之前的那个 EnableWebMvcConfiguration ,因为它已经实现了 WebMvcConfigurer 接口了,所以我们可以直接重写它的 addResourceHandlers 方法来配置静态资源解析规则:

public class EnableWebMvcConfiguration implements WebMvcConfigurer {
    // ......
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
    }
}

2.3 静态资源的缓存

一般来讲,静态资源都会在浏览器中缓存,这样就不会每次请求时重复加载。webmvc 支持配置这些静态资源的缓存时长,具体设置也很简单,直接在 mvc:resources 标签上配置 cache-period 标签即可,单位为秒:

    <mvc:resources mapping="/js/**" location="/js/" cache-period="2592000"/>

对应的,注解配置的 WebMvcConfigure 写起来稍微费点劲,具体可以看下面,不过好处是可以直接指定缓存的单位,也挺方便:

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/js/**").addResourceLocations("/js/")
                .setCacheControl(CacheControl.maxAge(Duration.ofDays(30)));
    }

2.4 检验@RequestBody的测试

配置完成后,重启 Tomcat ,并重新访问 http://localhost:8080/spring-webmvc/department/edit?id=84fef077c8dd97be89b2dff7de601073 ,点击保存,浏览器可以弹出修改成功的提示,证明一切正确。

3. ServletAPI的获取和使用【掌握】

本章最后一小节,小册多介绍一点有关 SpringWebMvc 与原生 Servlet API 的使用。

3.1 request和response的获取

除了之前我们学过的,可以在 Controller 的方法参数上声明 HttpServletRequest 和 HttpServletResponse ,以及直接在 Controller 类中 @Autowired 之外,还可以通过一个全局的 API 来获取,它的使用方式也很简单:

    public HttpServletRequest getRequest() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        return servletRequestAttributes.getRequest();
    }

借助 RequestContextHolder ,就可以取到当前请求中的一个 “请求属性” ,这里面就组合了 request 和 response 。而且这个 RequestContextHolder 还是静态的,所以我们可以封装一些类似于 ServletUtils 之类的工具类:

public class ServletUtils {
    public static HttpServletRequest getRequest() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes.getRequest();
    }
    
    public static HttpServletResponse getResponse() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes.getResponse();
    }
}

3.2 Servlet原生的请求转发和重定向

利用 Servlet 原生的 API 也能实现转发和重定向,而且写法也都是我们之前学习的熟悉的方法。例如下面的方法就是一个重定向到 /department/list 的处理:

@RequestMapping("/department/save2")
public void save2(HttpServletRequest request, HttpServletResponse response, Department department)
    throws IOException, ServletException {
    System.out.println(department);
    request.getRequestDispatcher(request.getContextPath() + "/department/list").forward(request, response);
}

重定向的也很简单:

@RequestMapping("/department/save2")
public void save2(HttpServletRequest request, HttpServletResponse response, Department department)
    throws IOException {
    System.out.println(department);
    response.sendRedirect(request.getContextPath() + "/department/list");
}

当然,如果要这么写了,那返回值就一定是 void ,否则两边不一致,产生的结果可能是意料之外的,那就麻烦大了。

上一章小册在最后留了一个坑,就是 findById 的那个 RESTful 风格的方法,返回值是一个 Department 对象,如何将这个 Department 对象转换为 json 响应给浏览器呢?这一章我们先来解决这个问题。

1. json支持【掌握】

json 格式的数据想必各位都熟悉吧,当下最流行的异步请求数据下,数据响应的承载大多都是 json 格式了。与 xml 对比起来,json 格式的数据更简明,且更符合对象的思维,再加上浏览器的 JavaScript 解析 json 也很方便,所以我们现在基本都用 json 了。SpringWebMvc 当然也对 json 提供了支持,下面我们来讲解 SpringWebMvc 如何支持 json 转换。

1.1 添加依赖

既然是支持 json ,那干活的无非两种:要么 SpringWebMvc 自己干,要么找个第三方的工具去干。SpringWebMvc 选择了后者,它可以整合主流的 json 转换工具,例如 jackson 、fastjson 、gson 等。SpringWebMvc 默认用 jackson 进行 json 格式转换,因为在这几个常用的 json 转换工具中,jackson 的工作效率比较高。

fastjson 老容易曝出一些漏洞,所以有蛮多公司和团队不允许用 fastjson 的,毕竟万一遇到漏洞,触发了,咱自己是担当不起。所以在实际的项目开发中,都是以比较成熟的、问题少的、大家都比较熟悉的技术去选型。

大概了解了背景之后,接下来我们来添加依赖。对于 SpringFramework 5.2.8 而言,小册推荐你使用 jackson 的 2.10.5 版本,所以我们只需要在 pom.xml 中添加以下三个依赖即可:

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>2.10.5</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.10.5</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>2.10.5</version>
    </dependency>

这个版本其实是参考的 SpringBoot 2.2.9 版本,因为 2.2.9 版本刚好对应 SpringFramework 5.2.8 版本,而 2.2.9 的 SpringBoot 依赖的 jackson 版本是 2.10.5 ,所以,你懂得啦,人家帮我们选好了,我们直接拿来用就没有问题。

1.2 配置json转换器

导入依赖还不够,想让jackson 干活,还需要配置一下 json 的转换器。使用 xml 的话有两种办法可以配置,一一来看:

1.2.1 一个标签解决

其实很简单,在 spring-mvc.xml 中添加一个标签:<mvc:annotation-driven/> ,立马就好使了。因为这个标签的背后注册了好多 bean ,也顺便帮我们注册了有关 json 的转换器。

至于里面的原理,小册放到 WebMvc 的原理部分来解释,这里先留一个悬念。

1.2.2 手动配置

手动配置 json 转换器的话,要写的东西就比较复杂了,我们需要在 xml 配置文件中显式的配置一个 HandlerAdapter 。这个东西是啥我们先不用管,到 WebMvc 进阶部分我们会详细解释 WebMvc 中的重要组件,这里先照着小册的写就没问题:

<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
        </list>
    </property>
</bean>

这里面的核心 json 转换器的名称叫 MappingJackson2HttpMessageConverter ,名字比较长,小伙伴们不用刻意的去记,知道有这么个东西就行。

1.2.3 注解配置

emmmm 如果是使用注解驱动的话,压根不需要配置。。。因为 @EnableWebMvc 注解已经帮我们注册好了,所以我们可以直接往下进行就可以。

1.3 @ResponseBody

接下来就是如何将对象转 json 的过程了。回到 RestfulDepartmentController 中,我们只需要在 findById 方法上,标注一个 @ResponseBody 注解,就可以实现 json 数据的响应了。对,就这么简单。

重启 Tomcat ,并在浏览器访问 http://localhost:8080/spring-webmvc/department/6ded6d3bdc8f4fc70bcc4347822a5ca3 ,可以发现 Department 对象已经成功被转换为 json 数据了:

Spring WebMvc基础-json支持与静态资源配置

1.4 @RestController

以后的项目开发中,可能会出现一种情况:一个 Controller 中定义的所有方法,都是以 json 格式的数据返回的,换句话说,这个 Controller 里没有任何视图跳转,那这个时候就没有必要在每个方法上都标注 @ResponseBody 注解了,而是直接在 Controller 类上标注 @ResponseBody 注解即可,也可以用 @RestController 代替 @Controller 注解。

//@Controller
@RestController
public class JsonDepartmentController { ... }

翻开源码,可以发现 @RestController 的本质就是 @Controller + @ResponseBody :

@Controller
@ResponseBody
public @interface RestController 

1.5 @RequestBody

接下来我们继续说 @RequestBody 这个注解。各位一看就知道,它跟 @ResponseBody 是一对,@ResponseBody 是处理响应体数据,将对象转为 json ,那相对应的,@RequestBody 就应该是将请求的 json 数据转换为对象吧!哎它就是这么回事,所以接下来我们来测试一下 @RequestBody 的使用。

再写一个新的 Controller 吧,依然是在里面定义一个 saveJson 的方法(这样就不会跟前面的 save 方法起冲突了):

@Controller
public class JsonDepartmentController {
    
    @PostMapping("/department/saveJson")
    @ResponseBody
    public void saveJson(@RequestBody Department department) {
        System.out.println(department);
    }
}

这里我们为了能使用 json 的方式接收数据,department 参数的前面就要使用 @RequestBody 注解来修饰了,这样 SpringWebMvc 在参数绑定的时候,就不会从请求参数上找了,而是从请求体找。

1.5.1 postman模拟测试

下面我们要模拟请求,还是跟上一章一样,我们先用 postman 来测试一下效果。

用 postman 构建纯 json 的请求,需要在请求头 header 中添加一个 content-type 为 application/json :

Spring WebMvc基础-json支持与静态资源配置

然后,切换到 body 中,这里面不要用 form-data ,用 raw ,并将右边的格式调整为 json ,这样就可以编写 json 数据了。使用 raw 类型的数据,在发送时会按照下面输入框的内容原样发送。

Spring WebMvc基础-json支持与静态资源配置

好,这样一切都搞定了,发送请求,发现 postman 中没有任何响应,再切回 IDE ,发现控制台中确实打印了 Department 的数据:

Department{id='null', name='abaaba'}

说明 @RequestBody 的使用完全没有问题。

1.5.2 jQuery模拟测试

下面我们再在浏览器上,借助 jQuery 简化 ajax 的请求,来试一下效果。

在 deptInfo.jsp 中,引入 webapp 中的 jQuery.min.js :

<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery.min.js"></script>

接下来,在 <body> 之前添加一段 js 脚本:

    $(function() {
        $("#save-button").click(function() {
            $.ajax({
                url: '${pageContext.request.contextPath}/department/saveJson',
                type: 'post',
                data: JSON.stringify({
                    id: $("[name='id']").val(),
                    name: $("[name='name']").val(),
                    tel: $("[name='tel']").val()
                }),  // 这里也可以直接写json字符串,也可以借助ES5中的JSON进行字符串序列化
                contentType: 'application/json;charset=utf-8',
                success: function(data) {
                    alert("修改成功!");
                }
            });
        });
    });

之后重新部署 jsp 页面,或者直接重启 Tomcat ,然后通过 deptList 页面点击修改,跳转到 deptInfo 页:

Spring WebMvc基础-json支持与静态资源配置

接下来我们什么也不改,直接点保存,,,

点保存,,,点,,,咋没反应呢???难不成页面报错了吗?

打开 Chrome 的 Network ,发现 jquery.min.js 报了 404 的错误:

Spring WebMvc基础-json支持与静态资源配置

哎?我的 js 明明存在啊?为啥你给我报个 404 呢?哎,这就牵扯到 SpringWebMvc 对静态资源的处理了。

2. 静态资源配置【掌握】

我们先来解释上面报 404 的问题。

2.1 静态资源找不到的原因

之所以报 404 ,全都是因为我们之前在 web.xml 中配置了这个东东:

    <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:spring-mvc-withdao.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!-- !!!!!这里!!!!! -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

当 url-pattern 配置为 / 时,会拦截静态资源的!但 SpringWebMvc 并不懂静态资源怎么解析,也不知道去哪里找,于是就给我们直接扔了个 404 。

那要解决这个问题,两种方案:要么 DispatcherServlet 放弃拦截 / ,但这种方式貌似有点不太现实。。。因为要拦截的路径实在是多,还是直接写 / 比较省心;要么就告诉 SpringWebMvc ,如果碰到静态资源文件,应该去哪里找。很明显,比较合适的方式是第二种,所以接下来我们来学习如何配置静态资源的解析。

2.2 静态资源的解析配置

针对 xml 配置文件,和注解配置的方式,我们分别来讲解。

2.2.1 xml配置文件配置

xml 配置文件的方式,只需要在 spring-mvc.xml 中添加这样一句配置:

    <mvc:resources mapping="/js/**" location="/js/" />

如果回头再有 css ,即便是放在 /WEB-INF 下,那也可以这样写:

    <mvc:resources mapping="/css/**" location="/WEB-INF/css/" />

2.2.2 注解方式配置

注解驱动的配置,要找到之前的那个 EnableWebMvcConfiguration ,因为它已经实现了 WebMvcConfigurer 接口了,所以我们可以直接重写它的 addResourceHandlers 方法来配置静态资源解析规则:

public class EnableWebMvcConfiguration implements WebMvcConfigurer {
    // ......
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
    }
}

2.3 静态资源的缓存

一般来讲,静态资源都会在浏览器中缓存,这样就不会每次请求时重复加载。webmvc 支持配置这些静态资源的缓存时长,具体设置也很简单,直接在 mvc:resources 标签上配置 cache-period 标签即可,单位为秒:

    <mvc:resources mapping="/js/**" location="/js/" cache-period="2592000"/>

对应的,注解配置的 WebMvcConfigure 写起来稍微费点劲,具体可以看下面,不过好处是可以直接指定缓存的单位,也挺方便:

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/js/**").addResourceLocations("/js/")
                .setCacheControl(CacheControl.maxAge(Duration.ofDays(30)));
    }

2.4 检验@RequestBody的测试

配置完成后,重启 Tomcat ,并重新访问 http://localhost:8080/spring-webmvc/department/edit?id=84fef077c8dd97be89b2dff7de601073 ,点击保存,浏览器可以弹出修改成功的提示,证明一切正确。

3. ServletAPI的获取和使用【掌握】

本章最后一小节,小册多介绍一点有关 SpringWebMvc 与原生 Servlet API 的使用。

3.1 request和response的获取

除了之前我们学过的,可以在 Controller 的方法参数上声明 HttpServletRequest 和 HttpServletResponse ,以及直接在 Controller 类中 @Autowired 之外,还可以通过一个全局的 API 来获取,它的使用方式也很简单:

    public HttpServletRequest getRequest() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        return servletRequestAttributes.getRequest();
    }

借助 RequestContextHolder ,就可以取到当前请求中的一个 “请求属性” ,这里面就组合了 request 和 response 。而且这个 RequestContextHolder 还是静态的,所以我们可以封装一些类似于 ServletUtils 之类的工具类:

public class ServletUtils {
    public static HttpServletRequest getRequest() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes.getRequest();
    }
    
    public static HttpServletResponse getResponse() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes.getResponse();
    }
}

3.2 Servlet原生的请求转发和重定向

利用 Servlet 原生的 API 也能实现转发和重定向,而且写法也都是我们之前学习的熟悉的方法。例如下面的方法就是一个重定向到 /department/list 的处理:

@RequestMapping("/department/save2")
public void save2(HttpServletRequest request, HttpServletResponse response, Department department)
    throws IOException, ServletException {
    System.out.println(department);
    request.getRequestDispatcher(request.getContextPath() + "/department/list").forward(request, response);
}

重定向的也很简单:

@RequestMapping("/department/save2")
public void save2(HttpServletRequest request, HttpServletResponse response, Department department)
    throws IOException {
    System.out.println(department);
    response.sendRedirect(request.getContextPath() + "/department/list");
}

当然,如果要这么写了,那返回值就一定是 void ,否则两边不一致,产生的结果可能是意料之外的,那就麻烦大了。

上一章小册在最后留了一个坑,就是 findById 的那个 RESTful 风格的方法,返回值是一个 Department 对象,如何将这个 Department 对象转换为 json 响应给浏览器呢?这一章我们先来解决这个问题。

1. json支持【掌握】

json 格式的数据想必各位都熟悉吧,当下最流行的异步请求数据下,数据响应的承载大多都是 json 格式了。与 xml 对比起来,json 格式的数据更简明,且更符合对象的思维,再加上浏览器的 JavaScript 解析 json 也很方便,所以我们现在基本都用 json 了。SpringWebMvc 当然也对 json 提供了支持,下面我们来讲解 SpringWebMvc 如何支持 json 转换。

1.1 添加依赖

既然是支持 json ,那干活的无非两种:要么 SpringWebMvc 自己干,要么找个第三方的工具去干。SpringWebMvc 选择了后者,它可以整合主流的 json 转换工具,例如 jackson 、fastjson 、gson 等。SpringWebMvc 默认用 jackson 进行 json 格式转换,因为在这几个常用的 json 转换工具中,jackson 的工作效率比较高。

fastjson 老容易曝出一些漏洞,所以有蛮多公司和团队不允许用 fastjson 的,毕竟万一遇到漏洞,触发了,咱自己是担当不起。所以在实际的项目开发中,都是以比较成熟的、问题少的、大家都比较熟悉的技术去选型。

大概了解了背景之后,接下来我们来添加依赖。对于 SpringFramework 5.2.8 而言,小册推荐你使用 jackson 的 2.10.5 版本,所以我们只需要在 pom.xml 中添加以下三个依赖即可:

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>2.10.5</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.10.5</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>2.10.5</version>
    </dependency>

这个版本其实是参考的 SpringBoot 2.2.9 版本,因为 2.2.9 版本刚好对应 SpringFramework 5.2.8 版本,而 2.2.9 的 SpringBoot 依赖的 jackson 版本是 2.10.5 ,所以,你懂得啦,人家帮我们选好了,我们直接拿来用就没有问题。

1.2 配置json转换器

导入依赖还不够,想让jackson 干活,还需要配置一下 json 的转换器。使用 xml 的话有两种办法可以配置,一一来看:

1.2.1 一个标签解决

其实很简单,在 spring-mvc.xml 中添加一个标签:<mvc:annotation-driven/> ,立马就好使了。因为这个标签的背后注册了好多 bean ,也顺便帮我们注册了有关 json 的转换器。

至于里面的原理,小册放到 WebMvc 的原理部分来解释,这里先留一个悬念。

1.2.2 手动配置

手动配置 json 转换器的话,要写的东西就比较复杂了,我们需要在 xml 配置文件中显式的配置一个 HandlerAdapter 。这个东西是啥我们先不用管,到 WebMvc 进阶部分我们会详细解释 WebMvc 中的重要组件,这里先照着小册的写就没问题:

<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
        </list>
    </property>
</bean>

这里面的核心 json 转换器的名称叫 MappingJackson2HttpMessageConverter ,名字比较长,小伙伴们不用刻意的去记,知道有这么个东西就行。

1.2.3 注解配置

emmmm 如果是使用注解驱动的话,压根不需要配置。。。因为 @EnableWebMvc 注解已经帮我们注册好了,所以我们可以直接往下进行就可以。

1.3 @ResponseBody

接下来就是如何将对象转 json 的过程了。回到 RestfulDepartmentController 中,我们只需要在 findById 方法上,标注一个 @ResponseBody 注解,就可以实现 json 数据的响应了。对,就这么简单。

重启 Tomcat ,并在浏览器访问 http://localhost:8080/spring-webmvc/department/6ded6d3bdc8f4fc70bcc4347822a5ca3 ,可以发现 Department 对象已经成功被转换为 json 数据了:

Spring WebMvc基础-json支持与静态资源配置

1.4 @RestController

以后的项目开发中,可能会出现一种情况:一个 Controller 中定义的所有方法,都是以 json 格式的数据返回的,换句话说,这个 Controller 里没有任何视图跳转,那这个时候就没有必要在每个方法上都标注 @ResponseBody 注解了,而是直接在 Controller 类上标注 @ResponseBody 注解即可,也可以用 @RestController 代替 @Controller 注解。

//@Controller
@RestController
public class JsonDepartmentController { ... }

翻开源码,可以发现 @RestController 的本质就是 @Controller + @ResponseBody :

@Controller
@ResponseBody
public @interface RestController 

1.5 @RequestBody

接下来我们继续说 @RequestBody 这个注解。各位一看就知道,它跟 @ResponseBody 是一对,@ResponseBody 是处理响应体数据,将对象转为 json ,那相对应的,@RequestBody 就应该是将请求的 json 数据转换为对象吧!哎它就是这么回事,所以接下来我们来测试一下 @RequestBody 的使用。

再写一个新的 Controller 吧,依然是在里面定义一个 saveJson 的方法(这样就不会跟前面的 save 方法起冲突了):

@Controller
public class JsonDepartmentController {
    
    @PostMapping("/department/saveJson")
    @ResponseBody
    public void saveJson(@RequestBody Department department) {
        System.out.println(department);
    }
}

这里我们为了能使用 json 的方式接收数据,department 参数的前面就要使用 @RequestBody 注解来修饰了,这样 SpringWebMvc 在参数绑定的时候,就不会从请求参数上找了,而是从请求体找。

1.5.1 postman模拟测试

下面我们要模拟请求,还是跟上一章一样,我们先用 postman 来测试一下效果。

用 postman 构建纯 json 的请求,需要在请求头 header 中添加一个 content-type 为 application/json :

Spring WebMvc基础-json支持与静态资源配置

然后,切换到 body 中,这里面不要用 form-data ,用 raw ,并将右边的格式调整为 json ,这样就可以编写 json 数据了。使用 raw 类型的数据,在发送时会按照下面输入框的内容原样发送。

Spring WebMvc基础-json支持与静态资源配置

好,这样一切都搞定了,发送请求,发现 postman 中没有任何响应,再切回 IDE ,发现控制台中确实打印了 Department 的数据:

Department{id='null', name='abaaba'}

说明 @RequestBody 的使用完全没有问题。

1.5.2 jQuery模拟测试

下面我们再在浏览器上,借助 jQuery 简化 ajax 的请求,来试一下效果。

在 deptInfo.jsp 中,引入 webapp 中的 jQuery.min.js :

<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery.min.js"></script>

接下来,在 <body> 之前添加一段 js 脚本:

    $(function() {
        $("#save-button").click(function() {
            $.ajax({
                url: '${pageContext.request.contextPath}/department/saveJson',
                type: 'post',
                data: JSON.stringify({
                    id: $("[name='id']").val(),
                    name: $("[name='name']").val(),
                    tel: $("[name='tel']").val()
                }),  // 这里也可以直接写json字符串,也可以借助ES5中的JSON进行字符串序列化
                contentType: 'application/json;charset=utf-8',
                success: function(data) {
                    alert("修改成功!");
                }
            });
        });
    });

之后重新部署 jsp 页面,或者直接重启 Tomcat ,然后通过 deptList 页面点击修改,跳转到 deptInfo 页:

Spring WebMvc基础-json支持与静态资源配置

接下来我们什么也不改,直接点保存,,,

点保存,,,点,,,咋没反应呢???难不成页面报错了吗?

打开 Chrome 的 Network ,发现 jquery.min.js 报了 404 的错误:

Spring WebMvc基础-json支持与静态资源配置

哎?我的 js 明明存在啊?为啥你给我报个 404 呢?哎,这就牵扯到 SpringWebMvc 对静态资源的处理了。

2. 静态资源配置【掌握】

我们先来解释上面报 404 的问题。

2.1 静态资源找不到的原因

之所以报 404 ,全都是因为我们之前在 web.xml 中配置了这个东东:

    <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:spring-mvc-withdao.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!-- !!!!!这里!!!!! -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

当 url-pattern 配置为 / 时,会拦截静态资源的!但 SpringWebMvc 并不懂静态资源怎么解析,也不知道去哪里找,于是就给我们直接扔了个 404 。

那要解决这个问题,两种方案:要么 DispatcherServlet 放弃拦截 / ,但这种方式貌似有点不太现实。。。因为要拦截的路径实在是多,还是直接写 / 比较省心;要么就告诉 SpringWebMvc ,如果碰到静态资源文件,应该去哪里找。很明显,比较合适的方式是第二种,所以接下来我们来学习如何配置静态资源的解析。

2.2 静态资源的解析配置

针对 xml 配置文件,和注解配置的方式,我们分别来讲解。

2.2.1 xml配置文件配置

xml 配置文件的方式,只需要在 spring-mvc.xml 中添加这样一句配置:

    <mvc:resources mapping="/js/**" location="/js/" />

如果回头再有 css ,即便是放在 /WEB-INF 下,那也可以这样写:

    <mvc:resources mapping="/css/**" location="/WEB-INF/css/" />

2.2.2 注解方式配置

注解驱动的配置,要找到之前的那个 EnableWebMvcConfiguration ,因为它已经实现了 WebMvcConfigurer 接口了,所以我们可以直接重写它的 addResourceHandlers 方法来配置静态资源解析规则:

public class EnableWebMvcConfiguration implements WebMvcConfigurer {
    // ......
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
    }
}

2.3 静态资源的缓存

一般来讲,静态资源都会在浏览器中缓存,这样就不会每次请求时重复加载。webmvc 支持配置这些静态资源的缓存时长,具体设置也很简单,直接在 mvc:resources 标签上配置 cache-period 标签即可,单位为秒:

    <mvc:resources mapping="/js/**" location="/js/" cache-period="2592000"/>

对应的,注解配置的 WebMvcConfigure 写起来稍微费点劲,具体可以看下面,不过好处是可以直接指定缓存的单位,也挺方便:

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/js/**").addResourceLocations("/js/")
                .setCacheControl(CacheControl.maxAge(Duration.ofDays(30)));
    }

2.4 检验@RequestBody的测试

配置完成后,重启 Tomcat ,并重新访问 http://localhost:8080/spring-webmvc/department/edit?id=84fef077c8dd97be89b2dff7de601073 ,点击保存,浏览器可以弹出修改成功的提示,证明一切正确。

3. ServletAPI的获取和使用【掌握】

本章最后一小节,小册多介绍一点有关 SpringWebMvc 与原生 Servlet API 的使用。

3.1 request和response的获取

除了之前我们学过的,可以在 Controller 的方法参数上声明 HttpServletRequest 和 HttpServletResponse ,以及直接在 Controller 类中 @Autowired 之外,还可以通过一个全局的 API 来获取,它的使用方式也很简单:

    public HttpServletRequest getRequest() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        return servletRequestAttributes.getRequest();
    }

借助 RequestContextHolder ,就可以取到当前请求中的一个 “请求属性” ,这里面就组合了 request 和 response 。而且这个 RequestContextHolder 还是静态的,所以我们可以封装一些类似于 ServletUtils 之类的工具类:

public class ServletUtils {
    public static HttpServletRequest getRequest() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes.getRequest();
    }
    
    public static HttpServletResponse getResponse() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes.getResponse();
    }
}

3.2 Servlet原生的请求转发和重定向

利用 Servlet 原生的 API 也能实现转发和重定向,而且写法也都是我们之前学习的熟悉的方法。例如下面的方法就是一个重定向到 /department/list 的处理:

@RequestMapping("/department/save2")
public void save2(HttpServletRequest request, HttpServletResponse response, Department department)
    throws IOException, ServletException {
    System.out.println(department);
    request.getRequestDispatcher(request.getContextPath() + "/department/list").forward(request, response);
}

重定向的也很简单:

@RequestMapping("/department/save2")
public void save2(HttpServletRequest request, HttpServletResponse response, Department department)
    throws IOException {
    System.out.println(department);
    response.sendRedirect(request.getContextPath() + "/department/list");
}

当然,如果要这么写了,那返回值就一定是 void ,否则两边不一致,产生的结果可能是意料之外的,那就麻烦大了。

上一章小册在最后留了一个坑,就是 findById 的那个 RESTful 风格的方法,返回值是一个 Department 对象,如何将这个 Department 对象转换为 json 响应给浏览器呢?这一章我们先来解决这个问题。

1. json支持【掌握】

json 格式的数据想必各位都熟悉吧,当下最流行的异步请求数据下,数据响应的承载大多都是 json 格式了。与 xml 对比起来,json 格式的数据更简明,且更符合对象的思维,再加上浏览器的 JavaScript 解析 json 也很方便,所以我们现在基本都用 json 了。SpringWebMvc 当然也对 json 提供了支持,下面我们来讲解 SpringWebMvc 如何支持 json 转换。

1.1 添加依赖

既然是支持 json ,那干活的无非两种:要么 SpringWebMvc 自己干,要么找个第三方的工具去干。SpringWebMvc 选择了后者,它可以整合主流的 json 转换工具,例如 jackson 、fastjson 、gson 等。SpringWebMvc 默认用 jackson 进行 json 格式转换,因为在这几个常用的 json 转换工具中,jackson 的工作效率比较高。

fastjson 老容易曝出一些漏洞,所以有蛮多公司和团队不允许用 fastjson 的,毕竟万一遇到漏洞,触发了,咱自己是担当不起。所以在实际的项目开发中,都是以比较成熟的、问题少的、大家都比较熟悉的技术去选型。

大概了解了背景之后,接下来我们来添加依赖。对于 SpringFramework 5.2.8 而言,小册推荐你使用 jackson 的 2.10.5 版本,所以我们只需要在 pom.xml 中添加以下三个依赖即可:

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>2.10.5</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.10.5</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>2.10.5</version>
    </dependency>

这个版本其实是参考的 SpringBoot 2.2.9 版本,因为 2.2.9 版本刚好对应 SpringFramework 5.2.8 版本,而 2.2.9 的 SpringBoot 依赖的 jackson 版本是 2.10.5 ,所以,你懂得啦,人家帮我们选好了,我们直接拿来用就没有问题。

1.2 配置json转换器

导入依赖还不够,想让jackson 干活,还需要配置一下 json 的转换器。使用 xml 的话有两种办法可以配置,一一来看:

1.2.1 一个标签解决

其实很简单,在 spring-mvc.xml 中添加一个标签:<mvc:annotation-driven/> ,立马就好使了。因为这个标签的背后注册了好多 bean ,也顺便帮我们注册了有关 json 的转换器。

至于里面的原理,小册放到 WebMvc 的原理部分来解释,这里先留一个悬念。

1.2.2 手动配置

手动配置 json 转换器的话,要写的东西就比较复杂了,我们需要在 xml 配置文件中显式的配置一个 HandlerAdapter 。这个东西是啥我们先不用管,到 WebMvc 进阶部分我们会详细解释 WebMvc 中的重要组件,这里先照着小册的写就没问题:

<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
        </list>
    </property>
</bean>

这里面的核心 json 转换器的名称叫 MappingJackson2HttpMessageConverter ,名字比较长,小伙伴们不用刻意的去记,知道有这么个东西就行。

1.2.3 注解配置

emmmm 如果是使用注解驱动的话,压根不需要配置。。。因为 @EnableWebMvc 注解已经帮我们注册好了,所以我们可以直接往下进行就可以。

1.3 @ResponseBody

接下来就是如何将对象转 json 的过程了。回到 RestfulDepartmentController 中,我们只需要在 findById 方法上,标注一个 @ResponseBody 注解,就可以实现 json 数据的响应了。对,就这么简单。

重启 Tomcat ,并在浏览器访问 http://localhost:8080/spring-webmvc/department/6ded6d3bdc8f4fc70bcc4347822a5ca3 ,可以发现 Department 对象已经成功被转换为 json 数据了:

Spring WebMvc基础-json支持与静态资源配置

1.4 @RestController

以后的项目开发中,可能会出现一种情况:一个 Controller 中定义的所有方法,都是以 json 格式的数据返回的,换句话说,这个 Controller 里没有任何视图跳转,那这个时候就没有必要在每个方法上都标注 @ResponseBody 注解了,而是直接在 Controller 类上标注 @ResponseBody 注解即可,也可以用 @RestController 代替 @Controller 注解。

//@Controller
@RestController
public class JsonDepartmentController { ... }

翻开源码,可以发现 @RestController 的本质就是 @Controller + @ResponseBody :

@Controller
@ResponseBody
public @interface RestController 

1.5 @RequestBody

接下来我们继续说 @RequestBody 这个注解。各位一看就知道,它跟 @ResponseBody 是一对,@ResponseBody 是处理响应体数据,将对象转为 json ,那相对应的,@RequestBody 就应该是将请求的 json 数据转换为对象吧!哎它就是这么回事,所以接下来我们来测试一下 @RequestBody 的使用。

再写一个新的 Controller 吧,依然是在里面定义一个 saveJson 的方法(这样就不会跟前面的 save 方法起冲突了):

@Controller
public class JsonDepartmentController {
    
    @PostMapping("/department/saveJson")
    @ResponseBody
    public void saveJson(@RequestBody Department department) {
        System.out.println(department);
    }
}

这里我们为了能使用 json 的方式接收数据,department 参数的前面就要使用 @RequestBody 注解来修饰了,这样 SpringWebMvc 在参数绑定的时候,就不会从请求参数上找了,而是从请求体找。

1.5.1 postman模拟测试

下面我们要模拟请求,还是跟上一章一样,我们先用 postman 来测试一下效果。

用 postman 构建纯 json 的请求,需要在请求头 header 中添加一个 content-type 为 application/json :

Spring WebMvc基础-json支持与静态资源配置

然后,切换到 body 中,这里面不要用 form-data ,用 raw ,并将右边的格式调整为 json ,这样就可以编写 json 数据了。使用 raw 类型的数据,在发送时会按照下面输入框的内容原样发送。

Spring WebMvc基础-json支持与静态资源配置

好,这样一切都搞定了,发送请求,发现 postman 中没有任何响应,再切回 IDE ,发现控制台中确实打印了 Department 的数据:

Department{id='null', name='abaaba'}

说明 @RequestBody 的使用完全没有问题。

1.5.2 jQuery模拟测试

下面我们再在浏览器上,借助 jQuery 简化 ajax 的请求,来试一下效果。

在 deptInfo.jsp 中,引入 webapp 中的 jQuery.min.js :

<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery.min.js"></script>

接下来,在 <body> 之前添加一段 js 脚本:

    $(function() {
        $("#save-button").click(function() {
            $.ajax({
                url: '${pageContext.request.contextPath}/department/saveJson',
                type: 'post',
                data: JSON.stringify({
                    id: $("[name='id']").val(),
                    name: $("[name='name']").val(),
                    tel: $("[name='tel']").val()
                }),  // 这里也可以直接写json字符串,也可以借助ES5中的JSON进行字符串序列化
                contentType: 'application/json;charset=utf-8',
                success: function(data) {
                    alert("修改成功!");
                }
            });
        });
    });

之后重新部署 jsp 页面,或者直接重启 Tomcat ,然后通过 deptList 页面点击修改,跳转到 deptInfo 页:

Spring WebMvc基础-json支持与静态资源配置

接下来我们什么也不改,直接点保存,,,

点保存,,,点,,,咋没反应呢???难不成页面报错了吗?

打开 Chrome 的 Network ,发现 jquery.min.js 报了 404 的错误:

Spring WebMvc基础-json支持与静态资源配置

哎?我的 js 明明存在啊?为啥你给我报个 404 呢?哎,这就牵扯到 SpringWebMvc 对静态资源的处理了。

2. 静态资源配置【掌握】

我们先来解释上面报 404 的问题。

2.1 静态资源找不到的原因

之所以报 404 ,全都是因为我们之前在 web.xml 中配置了这个东东:

    <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:spring-mvc-withdao.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!-- !!!!!这里!!!!! -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

当 url-pattern 配置为 / 时,会拦截静态资源的!但 SpringWebMvc 并不懂静态资源怎么解析,也不知道去哪里找,于是就给我们直接扔了个 404 。

那要解决这个问题,两种方案:要么 DispatcherServlet 放弃拦截 / ,但这种方式貌似有点不太现实。。。因为要拦截的路径实在是多,还是直接写 / 比较省心;要么就告诉 SpringWebMvc ,如果碰到静态资源文件,应该去哪里找。很明显,比较合适的方式是第二种,所以接下来我们来学习如何配置静态资源的解析。

2.2 静态资源的解析配置

针对 xml 配置文件,和注解配置的方式,我们分别来讲解。

2.2.1 xml配置文件配置

xml 配置文件的方式,只需要在 spring-mvc.xml 中添加这样一句配置:

    <mvc:resources mapping="/js/**" location="/js/" />

如果回头再有 css ,即便是放在 /WEB-INF 下,那也可以这样写:

    <mvc:resources mapping="/css/**" location="/WEB-INF/css/" />

2.2.2 注解方式配置

注解驱动的配置,要找到之前的那个 EnableWebMvcConfiguration ,因为它已经实现了 WebMvcConfigurer 接口了,所以我们可以直接重写它的 addResourceHandlers 方法来配置静态资源解析规则:

public class EnableWebMvcConfiguration implements WebMvcConfigurer {
    // ......
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
    }
}

2.3 静态资源的缓存

一般来讲,静态资源都会在浏览器中缓存,这样就不会每次请求时重复加载。webmvc 支持配置这些静态资源的缓存时长,具体设置也很简单,直接在 mvc:resources 标签上配置 cache-period 标签即可,单位为秒:

    <mvc:resources mapping="/js/**" location="/js/" cache-period="2592000"/>

对应的,注解配置的 WebMvcConfigure 写起来稍微费点劲,具体可以看下面,不过好处是可以直接指定缓存的单位,也挺方便:

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/js/**").addResourceLocations("/js/")
                .setCacheControl(CacheControl.maxAge(Duration.ofDays(30)));
    }

2.4 检验@RequestBody的测试

配置完成后,重启 Tomcat ,并重新访问 http://localhost:8080/spring-webmvc/department/edit?id=84fef077c8dd97be89b2dff7de601073 ,点击保存,浏览器可以弹出修改成功的提示,证明一切正确。

3. ServletAPI的获取和使用【掌握】

本章最后一小节,小册多介绍一点有关 SpringWebMvc 与原生 Servlet API 的使用。

3.1 request和response的获取

除了之前我们学过的,可以在 Controller 的方法参数上声明 HttpServletRequest 和 HttpServletResponse ,以及直接在 Controller 类中 @Autowired 之外,还可以通过一个全局的 API 来获取,它的使用方式也很简单:

    public HttpServletRequest getRequest() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        return servletRequestAttributes.getRequest();
    }

借助 RequestContextHolder ,就可以取到当前请求中的一个 “请求属性” ,这里面就组合了 request 和 response 。而且这个 RequestContextHolder 还是静态的,所以我们可以封装一些类似于 ServletUtils 之类的工具类:

public class ServletUtils {
    public static HttpServletRequest getRequest() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes.getRequest();
    }
    
    public static HttpServletResponse getResponse() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes.getResponse();
    }
}

3.2 Servlet原生的请求转发和重定向

利用 Servlet 原生的 API 也能实现转发和重定向,而且写法也都是我们之前学习的熟悉的方法。例如下面的方法就是一个重定向到 /department/list 的处理:

@RequestMapping("/department/save2")
public void save2(HttpServletRequest request, HttpServletResponse response, Department department)
    throws IOException, ServletException {
    System.out.println(department);
    request.getRequestDispatcher(request.getContextPath() + "/department/list").forward(request, response);
}

重定向的也很简单:

@RequestMapping("/department/save2")
public void save2(HttpServletRequest request, HttpServletResponse response, Department department)
    throws IOException {
    System.out.println(department);
    response.sendRedirect(request.getContextPath() + "/department/list");
}

当然,如果要这么写了,那返回值就一定是 void ,否则两边不一致,产生的结果可能是意料之外的,那就麻烦大了。

 

免责声明:
1.本站所有内容由本站原创、网络转载、消息撰写、网友投稿等几部分组成。
2.本站原创文字内容若未经特别声明,则遵循协议CC3.0共享协议,转载请务必注明原文链接。
3.本站部分来源于网络转载的文章信息是出于传递更多信息之目的,不意味着赞同其观点。
4.本站所有源码与软件均为原作者提供,仅供学习和研究使用。
5.如您对本网站的相关版权有任何异议,或者认为侵犯了您的合法权益,请及时通知我们处理。
火焰兔 » Spring WebMvc基础-json支持与静态资源配置