接收表单复合数据

说明:

(1) 本篇博客的内容:接收复合数据:如下,form表单中的【您的学习目的】,我们可以选择多项,对于这样的复合数据,【Spring MVC】如何接收,就是本篇博客的主要内容;


零:本篇博客内容;

说明:

(1) 可以使用数据或者List接收【复合数据】;

(2) 如果某个参数没有被传递的时候,我们不希望这个参数是null,我们可以使用@RequestParam为这个参数设置默认值;

(3) 使用Map对象一次性接收(请求中的)所有参数:这是一种常见但又容易出错的点;


一:为了演示,引入一个准备好的项目:form:基础设置;

(1)pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
     <groupId>com.imooc</groupId>
     <artifactId>form</artifactId>
     <version>1.0-SNAPSHOT</version>
 
     <repositories>
         <repository>
             <id>aliyun</id>
             <name>aliyun</name>
             <url>https://maven.aliyun.com/repository/public</url>
         </repository>
     </repositories>
     <dependencies>
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-webmvc</artifactId>
             <version>5.1.9.RELEASE</version>
         </dependency>
     </dependencies>
 </project>

说明:

(1) 没什么好说的,就是设置了一些国内仓库;引入了【Spring-webmvc】的依赖;

(2)form.html:

<!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>学员调查问卷</title>
     <style>
         .container {
             position: absolute;
             border: 1px solid #cccccc;
             left: 50%;
             top: 50%;
             width: 400px;
             height: 300px;
             margin-left: -200px;
             margin-top: -150px;
             box-sizing: border-box;
             padding: 10px;
         }
         h2{
             margin: 10px 0px;
             text-align: center;
         }
         h3{
             margin: 10px  0px;
         }
     </style>
 </head>
 <body>
     <div class="container">
         <h2>学员调查问卷</h2>
         <form action="./apply" method="post">
         <h3>您的姓名</h3>
         <input name="name" class="text"  style="width: 150px">
         <h3>您正在学习的技术方向</h3>
         <select name="course" style="width: 150px">
             <option value="java">Java</option>
             <option value="h5">HTML5</option>
             <option value="python">Python</option>
             <option value="php">PHP</option>
         </select>
         <div>
             <h3>您的学习目的:</h3>
             <input type="checkbox" name="purpose" value="1">就业找工作
             <input type="checkbox" name="purpose" value="2">工作要求
             <input type="checkbox" name="purpose" value="3">兴趣爱好
             <input type="checkbox" name="purpose" value="4">其他
         </div>
         <div style="text-align: center;padding-top:10px" >
             <input type="submit" value="提交" style="width:100px">
         </div>
         </form>
 
     </div>
 </body>
 </html>

说明:

(1) form.html就是一个如下图所示的前端文件:

(2) 其中form表单的提交地址【form action=“./apply” method=“post”】中的【./】是指当前路径:

为此就需要介绍下,URI相对路径与绝对路径;

● URI是统一资源定位符,是URL的子集;(即去掉URL中的主机和端口,剩下的就是URI);

● 绝对路径和相对路径如下图:很容易理解啦;

● 在开发时,使用【相对路径】更方便些;

(注):当我们一个IDEA,打开过多个不同的web应用时,我们可以多个不同的Tomcat服务器,然后根据情况为当前工程选择合适的Tomcat服务器!

(1) 配置多个Tomcat服务器,隐约感觉到肯定有很多坑,比如多个服务器的端口尽量不要重复;这些坑需要自己以后慢慢踩、慢慢填;


二: 正式阐述&演示;

1.Example一:使用数组接收【复合数据】;

(1)创建FormController;

package com.imooc.springmvc.controller;
 
 import com.imooc.springmvc.entity.Form;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
 import java.util.List;
 import java.util.Map;
 
 @Controller
 public class FormController {
     @PostMapping("/apply")
     @ResponseBody
     public String apply(String name, String course, Integer[] purpose){
         System.out.println(name);
         System.out.println(course);
         for (Integer p : purpose) {
             System.out.println(p);
         }
         return "SUCCESS";
     }
 
 }

(2)启动Tomcat服务器,观察效果;

2.Example二:使用@RequestParam为参数设置默认值;

(1)FormController演示;

package com.imooc.springmvc.controller;
 
 import com.imooc.springmvc.entity.Form;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
 import java.util.List;
 import java.util.Map;
 
 @Controller
 public class FormController {
     @PostMapping("/apply")
     @ResponseBody
     public String apply(@RequestParam(value = "name",defaultValue = "andy") String name, String course, Integer[] purpose){
System.out.println(name);
System.out.println(course);
for (Integer p : purpose) {
System.out.println(p);
}
return "SUCCESS";
}
}

(2)启动Tomcat,观察效果;

3.Example三:使用List接收【复合数据】;

● 随着编程的深入,会发现在很多情况下,数组不咋好用,数组用的也越来越少了;因为,在Java中,提供了更有效的管理方式:集合;

●【Spring MVC】也默认支持,使用List接收和保存【复合数据】;

(1)FormController演示;

package com.imooc.springmvc.controller;
 
 import com.imooc.springmvc.entity.Form;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
 import java.util.List;
 import java.util.Map;
 
 @Controller
 public class FormController {
 
     @PostMapping("/apply")
     @ResponseBody
     public String apply(String name, String course, @RequestParam List<Integer> purpose){
System.out.println(name);
System.out.println(course);
for (Integer p : purpose) {
System.out.println(p);
}
return "SUCCESS";
}
}

(2)启动Tomcat,观察效果;

说明:【使用数组接收复合数据】和【使用List接收复合数据】没有本质区别;

●【1.Example一:使用数组接收【复合数据】;】和【3.Example三:使用List接收【复合数据】;】,二者没有本质区别,只是一个数据的载体是数组,一个数据的载体是ArrayList;

● 更多是因为List对象有更多的方法,我们操作起来更方便;

在日常开发中,建议使用List接收复合数据;

3.1.Example三(附):使用【JavaBean】接收请求参数时,依旧可以使用List接收【复合数据】;

(1)Form类;

package com.imooc.springmvc.entity;
 
 import java.util.List;
 
 public class Form {
     private String name;
     private String course;
     private List<Integer purpose;
 
     public String getName() {
         return name;
     }
 
     public void setName(String name) {
         this.name = name;
     }
 
     public String getCourse() {
         return course;
     }
 
     public void setCourse(String course) {
         this.course = course;
     }
 
     public List<Integer getPurpose() {
         return purpose;
     }
 
     public void setPurpose(List<Integer purpose) {
         this.purpose = purpose;
     }
 }

(2)FormController演示;

package com.imooc.springmvc.controller;
 
 import com.imooc.springmvc.entity.Form;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
 import java.util.List;
 import java.util.Map;
 
 @Controller
 public class FormController {
 
     @PostMapping("/apply")
     @ResponseBody
     public String apply(Form form){
         return "SUCCESS";
     }
 }

这儿其实就是【Controller接收请求:使用【Java Bean】接收】中的内容,只不过这个【JavaBean】中使用了List接收请求中的复合数据而已;

(3)启动Tomcat,观察效果;

【JavaBean中有List类型的属性,然后再用这个JavaBean去接收“包含复合数据”的请求参数】,这个开发技巧挺好用的,在实际开发中,一定要知道有这个开发技巧;

4.Example四:使用Map一次性接收(请求中的)所有参数

● 在实际开发中,也有很多人习惯【使用Map接收前端的表单数据】;这自然是没问题的,【Spring MVC】也支持;

● 但是,在接收【复合数据】时,Map有天生的缺陷;

(1)FormController演示;

package com.imooc.springmvc.controller;
 
 import com.imooc.springmvc.entity.Form;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
 import java.util.List;
 import java.util.Map;
 
 @Controller
 public class FormController {
 
     @PostMapping("/apply")
     @ResponseBody
     public String apply(@RequestParam Map map){
         System.out.println(map);
         return "SUCCESS";
     }
 }

同样,和List一样,使用Map接收数据的时,也需要使用@RequestParam注解;

(2)启动Tomcat,观察效果;

(3)建议:如果【表单不包含复合数据】,可以使用Map去收;如果【表单包含复合数据】,一定不要使用Map去接收,否则数据会丢失;

关联对象赋值

说明:

(1) 本篇博客必要性说明: ● 【关联对象】以前遇到过多次,就是指在一个对象中引用了另外一个对象;

● 我们需要对这个被引用对象进行赋值,这个操作就是【关联对象赋值】,这也是本篇博客的重点;

(2) 在实际开发中:

● 对于一个结构复杂的表单,不应该傻乎乎的只用一个JavaBean去接收;而是,应该根据面向对象设计原则,使用多个对象去对应表单中不同的数据;然后,后端通过【对象关联】的方式,把这些数据组织起来;

● 为此,在后端接受前端表单数据的时候,就涉及到了【关联对象赋值】;

● 而本篇博客的内容,就是为了说明这个问题的;同时,本篇博客的内容,在日常开发中是非常常见的;

(3) 从程序员的角度来说,实现【对象关联赋值】的步骤很简单,但能感受到背后【Spring MVC】做了支撑。


零:实际案例引入;

1.复杂表单案例说明;【只使用一个JavaBean去接收表单】不太好;

● 自然,对于这份表单,可以创建一个对象,来包含其中所有的属性,然后再使用前面介绍的【Spring MVC】为其赋值;但是,这样做并不好,因为这份表单包含了两类信息,【注册信息】和【个人身份信息】;


2.【使用“关联对象”的方式去接收表单】更好;关联对象赋值的步骤;

如果严格按照【面向对象设计原则】的话,对这个表单接收时,首先应该创建两个类; 需要注意,在User类中,需要实例化IDcard;

然后,在前端的form表单中,设置一下输入项的属性名;


一:演示如下

1.首先,修改下form.html,表单增加了【收货人信息】;

<!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>学员调查问卷</title>
     <style>
         .container {
             position: absolute;
             border: 1px solid #cccccc;
             left: 50%;
             top: 50%;
             width: 400px;
             height: 600px;
             margin-left: -200px;
             margin-top: -300px;
             box-sizing: border-box;
             padding: 10px;
         }
         h2{
             margin: 10px 0px;
             text-align: center;
         }
         h3{
             margin: 10px  0px;
         }
     </style>
 </head>
 <body>
     <div class="container">
         <h2>学员调查问卷</h2>
         <form action="./apply" method="post">
         <h3>您的姓名</h3>
         <input name="name" class="text"  style="width: 150px">
         <h3>您正在学习的技术方向</h3>
         <select name="course" style="width: 150px">
             <option value="java">Java</option>
             <option value="h5">HTML5</option>
             <option value="python">Python</option>
             <option value="php">PHP</option>
         </select>
         <div>
             <h3>您的学习目的:</h3>
             <input type="checkbox" name="purpose" value="1">就业找工作
             <input type="checkbox" name="purpose" value="2">工作要求
             <input type="checkbox" name="purpose" value="3">兴趣爱好
             <input type="checkbox" name="purpose" value="4">其他
         </div>
         <h3>收货人</h3>
         <input name="name" class="text" style="width: 150px">
         <h3>联系电话</h3>
         <input name="mobile" class="text" style="width: 150px">
         <h3>收货地址</h3>
         <input name="address" class="text" style="width: 150px">
 
         <div style="text-align: center;padding-top:10px" >
             <input type="submit" value="提交" style="width:100px">
         </div>
         </form>
 
     </div>
 </body>
 </html>

说明:

(1) form.html改动说明;

(2) 修改后,form.html效果;

2.然后,为了应对前端表单的变化,增加Delivery实体类,去对应接收【收货人信息】;

package com.imooc.springmvc.entity;
 
 public class Delivery {
     private String name;
     private String mobile;
     private String address;
 
     public String getName() {
         return name;
     }
 
     public void setName(String name) {
         this.name = name;
     }
 
     public String getMobile() {
         return mobile;
     }
 
     public void setMobile(String mobile) {
         this.mobile = mobile;
     }
 
     public String getAddress() {
         return address;
     }
 
     public void setAddress(String address) {
         this.address = address;
     }
 }

3.然后,把Form和Delivery这俩对象关联一下,在Form类中增加Delivery的属性;(需要实例化!!!)

4.然后,记得再去修改下form.html;增加delivery前缀;

5.FormController演示;

package com.imooc.springmvc.controller;
 
 import com.imooc.springmvc.entity.Form;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
 import java.util.List;
 import java.util.Map;
 
 @Controller
 public class FormController {
 
     @PostMapping("/apply")
     @ResponseBody
     public String applyDelivery(Form form){
         return "SUCCESS";
     }
 }

6.启动Tomcat,观察效果;

日期类型转换

说明:

(1) 本篇博客必要性说明:

● 世界各地,日期的表达方式存在差异;欧美常采用【月、日、年】的方式;中国常采用【年、月、日】的方式;

● 正是由于这种差异,我们在开发中,需要根据系统的实际需求,采用合适的日期格式进行处理;

●【 如何接收程序中(尤其是前端的输入)的日期数据、并将其转换为日期对象】,就是本篇博客的重点; (2) 本篇博客包含【@DateTimeFormat注解的使用】和【Converter转换器类的使用和配置】两部分;


零:日期问题的引入:如果我们不设置,其自己无法完成String和Data的转换;

(1) 增加一个日期;

(2) 启动Tomcat,观察效果;


一:后端通过【方法参数】接收前端参数

使用形如【@DateTimeFormat(pattern = “yyyy-MM-dd”)】的注解,解决日期转换问题;

(1) 在方法参数

使用【@DateTimeFormat(pattern = “yyyy-MM-dd”) 】注解;

(2) 启动Tomcat,观察效果;


二:后端通过【JavaBean】接收前端参数

也可使用形如【@DateTimeFormat(pattern = “yyyy-MM-dd”)】的注解,解决日期抓换问题;

(1)在类属性上

使用【@DateTimeFormat(pattern = “yyyy-MM-dd”) 】注解;

(2)启动Tomcat,观察效果;


注:但是,使用注解比较麻烦;使用【日期转换器】更好;

● 使用【@DateTimeFormat(pattern = “yyyy-MM-dd”) 】注解,是很简单的;

● 但是,使用注解开发,也有不方便的地方;设想一下,对于一个大型的系统中,会有很多地方都需要进行Date日期类型的转换,难道对于每个地方我们都要手动增加【@DateTimeFormat(pattern = “yyyy-MM-dd”) 】注解吗?这样做太麻烦了;

● 为此,可以引入“全局的默认时间转换器”,即可以设置全局的默认时间格式;


三:编写【Converter日期转换器】;(本篇博客核心!!!)

(1)编写Converter日期转换器类;

MyDateConverter类:

package com.imooc.springmvc.converter;
 
 import org.springframework.core.convert.converter.Converter;
 
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
 public class MyDateConverter implements Converter<String,Date> {
     public Date convert(String source) {
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
         try {
             Date date = sdf.parse(source);
             return date;
         } catch (ParseException e) {
             //如果前端输入的不是一个日期,或者日期的格式不符合这儿写的【"yyyy-MM-dd"】;那么程序会报错,这儿我们直接返回一个null;
return null;
}
}
}

说明:

但是,至此,MyDateConverter类只是一个标准的Java类,Spring MVC还不知道这个类的存在;为此就需要在applicationContext.xml配置文件中进行设置,让Spring MVC知道MyDateConverter类是一个转化器类,并且让其起作用;

(2)在applicationContext.xml中配置转换器类;

<?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mv="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 标签作用
     在Spring IOC初始化过程中,自动创建并管理com.imooc.springmvc及子包中
     拥有以下注解的对象.
     @Repository
     @Service
     @Controller
     @Component
     -->
     <context:component-scan base-
package="com.imooc.springmvc"></context:component-scan>
 
     <mvc:annotation-driven conversion-service="conversionService"/>
     <mvc:default-servlet-handler/>
 
     <bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
         <property name="converters">
             <set>
                 <bean
class="com.imooc.springmvc.converter.MyDateConverter"/>
             </set>
         </property>
     </bean>
 </beans>

说明:

(3)测试一下;

这儿我们用get方法测试一下,在方法参数中增加一个【Date类型的createTime】;

package com.imooc.springmvc.controller;
 
 import com.imooc.springmvc.entity.User;
 import org.springframework.format.annotation.DateTimeFormat;
 import org.springframework.scheduling.support.SimpleTriggerContext;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.*;
 
 import javax.swing.*;
 import java.util.Date;
 
 @Controller
 @RequestMapping("um")
 public class URLMappingController {
 
     @GetMapping("/g")
     @ResponseBody
     public String getMapping(@RequestParam("manager_name") String managerName,Date createTime) {
       return "This is getMethod."+managerName;
   }
}

启动Tomcat,观察效果;

(4)日期转换器类似乎可以这样理解:

● 上面我们编写了MyDateConverter日期转换器类,这个转换器在泛型中已经定义了<String,Date>,即是把String类型转为Date类型;

● 然后,经过我们在applicationContext.xml中配置转换器类后,Spring MVC就会知道,以后只要是把String类型的数据转换为Date类型数据时,就去找MyDateConverter类;

● 上面的理解很粗糙,但似乎目前只能这样说服自己;也能感受到在背后,Spring MVC做了强大的支撑;


四:【@DateTimeFormat注解】和【日期转换器类】并存时的优先级;

● 在实际中,如果即使用了【@DateTimeFormat注解】,又编写了【日期转换器类】时,是以【转换器类】为准的;

● Spring MVC的强制要求:一旦增加了【日期转换器类】,那么优先去使用【转换器类】来处理日期类型的转换工作,【@DateTimeFormat注解】就会被忽略掉;


● 所以,在实际开发中,一种比较好的开发方式是:【@DateTimeFormat注解】和【日期转换器类】,只能二者选其一;要么使用【@DateTimeFormat注解】对所有的日期类型加以转换,要么使用【日期转换器类】,全局统一转换;


● 但是,在一个大型系统中,往往会出现这种情况:99%的情况下,前端输入的日期格式是形如【2020-05-04】的格式,此时我们的转换器类是没问题的:

但是,保不齐,该大型系统中某些地方日期的输入格式是形如【20200504】的格式,那么为此,我们就需要修改下MyDateConverter日期转换器类了:根据实际的业务逻辑,在MyDateConverter类中,增加对不同情况的判断和分别处理就行了;

中文乱码问题解决

说明:

(1) 本篇博客必要性说明:默认情况下,Spring MVC对中文的支持度不高,很多时候会出现中文乱码的情况;本篇博客就是介绍中文乱码问题的解决策略; (2) 本篇博客内容虽多,但实操起来并不复杂,需要时,及时查阅即可;


零:中文乱码简述;

(1) 中文乱码问题由来和解决思路;

(0) 前面在介绍Servlet的时候,就接触过中文乱码的问题;

(1) 中文乱码根源是字符集的问题;

● 计算机要识别某种自然语言,就必须依赖于字符集,不同的字符集使用不同的编码方式;字符集就像是一个【专为计算机使用的字典】,计算机通过特定的字符集把【指令】准换为【特定的字符】;

● Tomcat早期的版本中,使用ISO-8859-1字符集;这个字符集是西欧字符集,包含了英文字母、拉丁字母、数字、标准符号等;ISO-8859-1不支持中文;所以,使用ISO-8859-1字符集处理中文时,会出现乱码问题;

(2) 解决策略;

● 核心策略就是把ISO-8859-1编码方式,转换为UTF-8的编码方式;

● UTF-8(Universal Character Set/Unicode Transformation Format)就是Unicode码的8位交换集格式;

● Unicode字符集,包含了世界上已知的各种语言,所以UTF-8自然支持中文;

(3) 在开发中,请求和响应都需要设置UTF-8;

(2)具体的解决策略;

(0) 基本上,在任何一个项目中,都需要设置以下三步;

(1) 首先,解决get请求的乱码:需要在Tomcat的server.xml中增加URIEncoding属性,配置UTF-8;

(2) 然后,解决post请求的乱码:在web.xml中配置CharacterEncodingFilter过滤器,通过Spring提供的这个过滤器,来解决post请求中的中文乱码问题;

(3) 最后,解决响应的乱码:在Spring的配置文件applicationContext.xml中配置StringHTTPMessageConverter这个转换器;


一:解决【get请求的中文乱码】

设置Tomcat配置文件server.xml中的URIEncoding属性,配置UTF-8;

(1)需要配置Tomcat的server.xml配置文件中的URIEncoding;

(2)启动Tomcat,观察效果;


二:解决【post请求的中文乱码】

在web.xml中配置CharacterEncodingFilter过滤器;

(0)没有设置前,Post请求乱码演示:启动Tomcat,效果如下:

对于Spring MVC,为了解决post请求的中文乱码问题,目前广泛采用的方法是【增加一个过滤器,转换字符集】;

(1)Spring MVC中配置过滤器;(核心内容!!!)

在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_3_1.xsd"
          version="3.1">
     <servlet>
         <servlet-name>springmvc</servlet-name>
         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
         <init-param>
             <param-name>contextConfigLocation</param-name>
             <param-value>classpath:applicationContext.xml</param-value>
         </init-param>
         <load-on-startup>0</load-on-startup>
     </servlet>
     <servlet-mapping>
         <servlet-name>springmvc</servlet-name>
         <url-pattern>/</url-pattern>
     </servlet-mapping>
 
     <filter>
         <filter-name>characterFilter</filter-name>
         <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
         <init-param>
             <param-name>encoding</param-name>
             <param-value>UTF-8</param-value>
         </init-param>
     </filter>
     <filter-mapping>
         <filter-name>characterFilter</filter-name>
         <url-pattern>/*</url-pattern>
     </filter-mapping>
 
 </web-app>

说明: (1) 只是这儿Spring MVC已经帮我们写好了过滤器类,我们拿来用就行了;

(2)设置后,Post请求乱码演示:启动Tomcat,效果如下:


三:解决【响应的中文乱码】

在applicationContext.xml中配置StringHttpMessageConverter这个转换器;

(0)没有设置前,响应的中文乱码演示:启动Tomcat,效果如下:

对于Spring MVC,为了解决响应的中文乱码问题,目前广泛采用的方法是【在Spring的配置文件applicationContext.xml中配置StringHTTPMessageConverter这个转换器】;

(1)在Spring的配置文件applicationContext.xml中配置

StringHTTPMessageConverter这个转换器;(核心内容!!!)

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mv="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 标签作用
     在Spring IOC初始化过程中,自动创建并管理com.imooc.springmvc及子包中
     拥有以下注解的对象.
     @Repository
     @Service
     @Controller
     @Component
     -->
     <context:component-scan base-package="com.imooc.springmvc"></context:component-scan>
 
     <mvc:annotation-driven conversion-service="conversionService">
         <mvc:message-converters>
             <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                 <property name="supportedMediaTypes">
                     <list>
                         <value>text/html;charset=utf-8</value>
                     </list>
                 </property>
             </bean>
         </mvc:message-converters>
     </mvc:annotation-driven>
     <mvc:default-servlet-handler/>
 
     <bean id="conversionService"
             class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
         <property name="converters">
             <set>
                 <bean
              class="com.imooc.springmvc.converter.MyDateConverter"/>
             </set>
         </property>
     </bean>
 </beans>

说明:

(1) 配置了哪些内容:

(2) 配置内容说明:

(3) 可以看到,这儿的套路,和以前在Controller中使用如 【response.setContentType(“text/html;charset=utf-8”)】原理是一样的;只是,这儿我们是在配置文件中配置的,而这会让程序更加灵活和易于维护;

(2)设置后,响应的中文乱码演示

启动Tomcat,效果如下:

响应输出结果

说明:

(1) 本篇博客必要性说明:

● 在前面几篇博客中,介绍的都是【请求】的内容;即【后端的Controller如何设置url映射】、【后端的Controller如何接收前端请求的数据】等;

● 本篇博客就介绍响应的相关内容,即【后端给前端的响应,是如何输出结果的】; (2) 本篇博客涉及到了JSP的一些内容,如有需要可以快速参考【(4)JavaWeb基础(网页搭建与JavaWeb基础)】中的相关内容;

(3)本篇博客的内容:

● 简单介绍了@ResponseBody,具体ResponseBody在实际开发中常用的场景,有待积累和总结;

● 引入了ModelAndView对象,有关该对象的详细内容,会在下篇博客中介绍;


一:响应产生结果的两种方式

简介;(@ResponseBody注解,ModelAndView对象)

说明:

(1) 第一种:@ResponseBody:在Controller的方法上,使用@ResponseBody注解,直接产生响应文本;前面接触过很多次,如下图;

(2) 第二种:ModelAndView:

● 但有的时候,【响应中只包含文本,即浏览器只显示一个文本】是不够的;

● 后端在处理请求后,往往需要跳转到一个新的页面,然后这个新的页面获取响应中的数据并展示(这个页面也称作View视图),为此就需要【响应包含一些比较复杂的、有组织的数据】;

● 在Spring MVC中,可以利用【ModelAndView对象】(承载数据),再结合【模板引擎】(JSP或FreeMarker等),在前端生成对应的页面;


二:第一种方式:@ResponseBody注解;

1.@ResponseBody简介;

说明:

(1) Controller方法直接return一个字符串,这个返回的字符串就是单纯给前端浏览器显示的,不涉及任何模板引擎的内容;

(2) 在实际开发中,Controller方法return的字符串,一般是JSON格式的字符串;

(3) ●在【中文乱码问题】知道,StringHTTPMessageConverter是Spring MVC定义的一个转换器,这个转换器的作用是解决响应中的中文乱码;

● 响应中的文本信息(字符串、JSON、XML等)都会 StringHttpMessageConverter的配置所影响;

● 自然,Controller方法return的字符串,会被StringHttpMessageConverter所影响;

2.@ResponseBody演示;返回的HTML字符串,会被浏览器解释、渲染,然后显示;

启动Tomcat,观察效果;

说明:

(1) 在实际开发中,后端一般不返回HTML片段;因为,这种方式太笨了;尤其对于复杂的页面,这样做的工作量太大了;

(2) 在实际工作中,一般采用的策略:【后端产生数据】+【前端的模板引擎】,得到最终的前端页面;

注:虽然,目前看来@ResponseBody在【显示前端页面】上不太适合;但,@ResponseBody还是有很多适用的场景的;


三:第二种方式:ModelAndView对象;

1.ModelAndView对象简介;

说明:

(1) ModelAndView:通过名字可知,是Model和View的意思;

(2) ModelAndView对象的目的,就是将【数据对象】和【模板引擎】进行绑定;

(3) Spring MVC默认的模板引擎是JSP;自然,也可以通过配置,使用FreeMarker等其他模板引擎;

2.ModelAndView的一个入门案例;

通过一个简单的入门案例,演示通过ModelAndView如何实现【页面的跳转】和【使用JSP显示后端产生的数据】;

(1)最简单的一个案例:后端没有传递数据:仅仅演示一下页面的跳转;

首先,在URLMappingController中定义一个后端的方法;

package com.imooc.springmvc.controller;
 
 import com.imooc.springmvc.entity.User;
 import org.springframework.format.annotation.DateTimeFormat;
 import org.springframework.scheduling.support.SimpleTriggerContext;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.servlet.ModelAndView;
 
 import javax.swing.*;
 import java.util.Date;
 
 @Controller
 @RequestMapping("um")
 public class URLMappingController {
 
     @GetMapping("/view")
     public ModelAndView showView() {
         ModelAndView mav = new ModelAndView("/view.jsp");
         return mav;
     }
 }

然后,编写要跳转到的前端页面view.jsp;

启动Tomcat,观察效果;

说明:

(1) 上面的案例,只是演示了一下【使用ModelAndView对象方式的时候,如何实现页面的跳转】,并没有传递数据。而在实际开发中,一般后端会传递数据的。


(2) 稍微复杂的一个案例:后端传递了数据;

首先,在URLMappingController中,编写一个有传递数据的方法;

package com.imooc.springmvc.controller;
 
 import com.imooc.springmvc.entity.User;
 import org.springframework.format.annotation.DateTimeFormat;
 import org.springframework.scheduling.support.SimpleTriggerContext;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.servlet.ModelAndView;
 
 import javax.swing.*;
 import java.util.Date;
 
 @Controller
 @RequestMapping("um")
 public class URLMappingController {
 
     @GetMapping("/view")
     public ModelAndView showView(Integer userId) {
         ModelAndView mav = new ModelAndView("/view.jsp");
         User user = new User();
         if (userId == 1) {
             user.setUsername("zhang");
         } else if (userId == 2) {
             user.setUsername("li");
         }
         mav.addObject("u", user);
         return mav;
     }
 }

然后, 在前端的view.jsp中接收后端传过来的数据;

启动Tomcat,观察效果;

说明:

(1) 有关JSP的EL表达式等内容, 如有需要可以快速参考【JavaWeb基础】中的相关内容;

(2) 上面的案例可以看到:

● 【数据:是后端Controller动态产生的】,【界面:是由JSP动态渲染的】;

● 上面的案例也体现了MVC的设计理念:视图和模型解耦;

● 而在Spring MVC中,就通过ModelAndView对象,来实现【视图和模型的解耦】,即【“数据产生的过程”和“界面展现的过程”,实现了解耦】;(已经知道,在没有Spring MVC的时候,我们一般把数据放在Servlet/Session/Context对象中;而这儿,我们把数据放在了ModelAndView对象中,可以感受到Spring MVC作了封装~~)

即在Spring MVC中,通过ModelAndView对象,就能很好的贯彻【MVC的设计理念】;

ModelAndView对象

说明: (1) 本篇博客内容:

● 已知,在Spring MVC中,要想跳转页面,需要使用ModelAndView对象,来实现【数据和页面的绑定】;

● 本篇博客,就是介绍ModelAndView对象的详细内容;

● 其中包括【ModelAndView对象的setViewName()方法】,【绝对路径和相对路径】;

● 最后提到了扩展内容:【String和ModelMap】替代【ModelAndView】;


一:ModelAndView简介;

已知,在Spring MVC中,要想跳转页面,需要使用ModelAndView对象,来实现【数据和页面的绑定】;

说明:

(1) ModelAndView的addObject()方法:

● 该方法用于设置【前端页面要显示的数据是什么】;

● 该方法的参数:可以是任何一个有效的Java对象;

● 该方法默认把对象,存放在当前请求中的;

(2) ModelAndView对象在进行页面跳转的时候,默认使用【请求转发】的方式来实现;即底层使用了forward;

(3) ModelAndView对象,如果想通过【响应重定向】来实现页面跳转,那么就需要额外增加【redirect:】;

(4) 请求转发和响应重定向的内容,如有需要可以快速参考【JavaWeb基础]】专栏中的内容;


二:在进行页面跳转的时候,ModelAndView对象默认使用

【请求转发】的方式来实现页面跳转;可以增加【redirect:】,来使用【页面重定向】的方式实现页面跳转;

1. ModelAndView对象默认使用【请求转发】的方式来实现页面跳转;(使用的场景比较多)

说明:

(1) ModelAndView对象默认的【请求转发】的方式,也是常用的方式;

(2)【请求转发】的时候,Controller和view.jsp可以共享同一个请求对象,而这也方便Controller向view.jsp传递数据;

2. 可以增加【redirect:】,来使用【页面重定向】的方式实现页面跳转;(使用的场景相对少)

说明:

(1) 通过上面的案例,可以看到【页面重定向】会导致(原请求中的)数据的丢失;

(2.1) 因此引出的一个问题:既然【页面重定向】有这么个不给力的地方,那么【页面重定向】的使用场景有哪些?:

(2.2) 感觉(2.1)的水还是有点深的,目前见过的例子比较少,似乎不能很好的hold住;慢慢积累和总结吧。


三:也可以使用ModelAndView对象的【setViewName(“/view.jsp”)】方法来确定跳转的页面;


四:ModelAndView对象跳转页面时:【绝对路径】和【相对路径】;

1.原先使用如【mav.setViewName(“/view.jsp”);】时,前面有【/】:这是绝对路径;(推荐使用)

2.如果【mav.setViewName(“view.jsp”);】时,前面没有【/】:这是相对路径;(不推荐使用)

说明:

(1.1) 这种不带【/】前缀,相对路径的使用方式,在实际开发中还是很可能遇到的(你可能有一个喜欢使用这种方式的同事);

(1.2) 但是不建议在开发中使用这种方式:

(2) 在实际开发中,推荐使用绝对路径的方式;


五:扩展:使用【String和ModelMap】替代【ModelAndView】;

如下代码中的showView1()方法:就可以使用【String和ModelMap】来替代【ModelAndView】:

package com.imooc.springmvc.controller;
 
 import com.imooc.springmvc.entity.User;
 import org.springframework.format.annotation.DateTimeFormat;
 import org.springframework.scheduling.support.SimpleTriggerContext;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.ModelMap;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.servlet.ModelAndView;
 
 import javax.swing.*;
 import java.util.Date;
 
 @Controller
 @RequestMapping("um")
 public class URLMappingController {
 
     @GetMapping("/view")
     public ModelAndView showView(Integer userId) {
 //        ModelAndView mav = new ModelAndView("redirect:/view.jsp");
         ModelAndView mav = new ModelAndView();
         mav.setViewName("/um/view.jsp");
         User user = new User();
         if (userId == 1) {
             user.setUsername("zhang");
         } else if (userId == 2) {
             user.setUsername("li");
         }
         mav.addObject("u", user);
         return mav;
     }
 
     //利用【String和ModelMap】,来替代【ModelAndView】;
     @GetMapping("/heihei")
     public String showView1(Integer userId, ModelMap modelMap) {
         String view = "/um/view.jsp";
         User user = new User();
         if (userId == 1) {
             user.setUsername("zhang");
         } else if (userId == 2) {
             user.setUsername("li");
         }
         modelMap.addAttribute("u",user);
         return view;
     }
 }

说明:

(1) showView1()方法和showView()方法,效果是一样的;

(2) showView1()方法分析;

(3) 通过showView1()方法,能够感觉到;【String和ModelMap】就是把【ModelAndView对象】拆分成了两个不同的部分,分别存储:

(4) 在实际开发中,这种【String和ModelMap】的方式,也是比较常见的; 甚至有的项目中,会发现【Controller中的方法返回值都是String,而不是ModelAndView】,也就是这个Controller中使用的全部是【String和ModelMap】的方式,没有使用【ModelAndView】的方式;

(5) 当【Controller的方法,返回值是String】,即使用【String和ModelMap】这种方式时,存在两种情况:

● 情况一:方法使用了@ResponseBody注解:这种也是没有接触【ModelAndView】之前,见到过的形式;

● 情况二:方法没有使用@ResponseBody注解:

(6) 有的场景下,ModelMap不是必需的:对于那些不需要向前端传递数据的情况,是可以不使用ModelMap的;

Spring MVC整合FreeMarker

说明:

(1) 有关FreeMarker的内容,如有需要可以快速参考【常用功能与过滤器、监听器、FreeMarker】中的内容;

(2) 本篇博客内容:

● Spring MVC默认使用JSP作为模板引擎,但又因为FreeMarker比JSP好用;所以本篇博客就介绍【Spring MVC整合FreeMarker】;

● 本篇博客仅仅说明了【Spring MVC整合FreeMarker】; (3) 本篇博客又提到了那个容易忘记的点:我们引入新的依赖后,需要及时把这个依赖添加到发布中去;


一:【Spring MVC整合FreeMarker】步骤;

第一步:在Maven的pom.xml中引入【FreeMarker的依赖】和【spring-context-support】;

第二步:在applicationContext.xml中配置:通知Spring MVC【我们要使用FreeMarker模板引擎】;

说明:

(1) 这儿设置UTF-8编码方式是:在【Controller发过来的数据】和【FreeMarker模板引擎】渲染完成后,向客户端浏览器返回响应时,响应体中使用的字符串集编码是UTF-8;

(2) 经过上面的设置以后,Spring MVC就启用了FreeMarker,Spring MVC已经知道了FreeMarker的存在;但是还不够,我们还需要对FreeMarker本身进行参数设置;

第三步:在applicationContext.xml中配置:配置FreeMarker参数;

说明:

(1) 这儿设置的UTF-8编码方式是:在【Controller发过来的数据】和【FreeMarker模板引擎】渲染的过程中,所有的字符按UTF-8字符集进行编码;


二:【Spring MVC整合FreeMarker】演示

第一步:在Maven的pom.xml中引入【FreeMarker的依赖】和【spring-context-support】;

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
     <groupId>com.imooc</groupId>
     <artifactId>first-springmvc</artifactId>
     <version>1.0-SNAPSHOT</version>
     <repositories>
         <repository>
             <id>aliyun</id>
             <name>aliyun</name>
             <url>https://maven.aliyun.com/repository/public</url>
         </repository>
     </repositories>
 
     <dependencies>
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-webmvc</artifactId>
             <version>5.1.9.RELEASE</version>
         </dependency>
 
         <dependency>
             <groupId>org.freemarker</groupId>
             <artifactId>freemarker</artifactId>
             <version>2.3.28</version>
         </dependency>
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-context-support</artifactId>
             <version>5.1.9.RELEASE</version>
         </dependency>
     </dependencies>
 
 </project>

说明:

第二步:在applicationContext.xml中配置:通知Spring MVC【我们要使用FreeMarker模板引擎】;

第三步:在applicationContext.xml中配置:配置FreeMarker参数;


三:【在Spring MVC中实际使用FreeMarker】测试;

1.创建FreeMarkerController,test.ftl:用于演示;

FreeMarkerController:

package com.imooc.springmvc.controller;
 
 import com.imooc.springmvc.entity.User;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.servlet.ModelAndView;
 
 @Controller
 @RequestMapping("/fm")
 public class FreeMarkerController {
 
     @GetMapping("test")
     public ModelAndView showTest() {
         ModelAndView mav = new ModelAndView();
 
mav.setViewName("/test");//因为我们已经把FreeMarker为默认模板引擎了,所以我们在跳转的时候就可以
         User user = new User();
         user.setUsername("zhangsan");
         mav.addObject("u", user);
         return mav;
     }
 }
 
test.ftl:
 
 
 <h1>${u.username}</h1>

说明:

(1) 关于跳转的进一步说明;

(2) 有关FreeMarker的内容,如有需要可以快速参考【常用功能与过滤器、监听器、FreeMarker】中的内容;

此时,还不能直接运行;还是那个容易忘记的点:我们引入新的依赖后,需要及时把这个依赖添加到发布中去;

如有需要可以参考:附加:IDEA的Artifacts;(这篇博客,以后有了更深的理解时,随时补充……)

2.容易忘记的一个点:将新引入的依赖,添加到发布中去;

说明:

(1) 上面的步骤虽然与【附加:IDEA的Artifacts】中的存在差异,但经过实测,其本质是一样的,殊途同归。

3.启动Tomcat,观察效果;

这样就说明,在我们的Spring MVC项目中,FreeMarker整合成功了,可以愉快的开始后续的开发了。