Spring MVC基础知识
1. 创建web应用
新建Maven项目
 点击File -> Project Structure -> Facets -> +号 -> Web
 
 修改文件描述符路径为硬盘:\项目名\src\main\存储页面的文件夹(如:webapp)\WEB-INF\web.xml
 修改Web页面路径为硬盘:\项目名\src\main\存储页面的文件夹(如:webapp)
 创建Artifact
 
 点击右上角Current File -> Edit Configurations
 
 点击 +号,找到 Tomcat Server -> local
 点击 Application Server 后面的 Configure,将Tomcat Home设为保存Tomcat的路径。
 
 点击Deployment, 选择加号添加 Artifact。
 将Application Context改为 \。
 
 返回Server,将更新设为页面或源代码发生变化就更新(热部署)。
 可以修改端口,如80端口。
 
 点击OK创建完成。
 注:如果遇到404的问题可以参考这个解决方法。
 关于Tomcat在本地启动没问题,在IDEA部署发生404的解决
2. Spring MVC 环境配置
1.Maven依赖spring-webmvc
<!-- 引入阿里云镜像仓库解决找不到依赖的问题 -->
    <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>
2.web.xml配置DispatcherServlet
<servlet>
        <servlet-name>springmvc</servlet-name>
        <!-- DispatcherServlet是Spring MVC最核心的对象DispatcherServlet用于栏截Http请求,并根据请求的URL调用与之对应的Controller方法,来完成Http请求的处理 -->
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 增加初始化参数,通知DispatcherServlet在启动web应用的时候加载哪个xml文件 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <!-- 在Web应用启动时自动创建Spring IOC容器并初始化DispatcherServlet, 如果不设置这项,则会在第一次访问url的时候创建Spring IOC容器 -->
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!-- "/"代表拦截所有请求 -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
<url-pattern>/</url-pattern>
/: 会匹配到/login这样的路径型url,不会匹配到模式为*.jsp这样的后缀型url
2、 <url-pattern>/*</url-pattern>
/*:会匹配所有url(只匹配当前文件夹下文件,不匹配子文件夹下文件):路径型的和后缀型的url(包括/login,*.jsp,*.js和*.html等)
一般情况下DispatcherServlet只需要处理我们向后台服务器的请求,不需要处理静态页面等内容,所以,建议使用/。
3.配置applicationContext的mvc标记
<?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">
    <!-- 注意xmlns:mvc="http://www.springframework.org/schema/mvc" 命名空间,要想使mvc生效必须使用这个命名空间下的标签才可以。 -->
    <!--
    context:component-scan 标签作用
    在Spring IOC初始化过程中,自动创建并管理com.imooc.springmvc及子包中
    拥有以下注解的对象.
    @Repository
    @Service
    @Controller
    @Component
    -->
    <context:component-scan base-package="com.imooc.springmvc"></context:component-scan>
    <!--启用Spring MVC的注解开发模式-->
    <mvc:annotation-driven/>
   	<!-- 将图片/JS/CSS等静态资源排除在外,可提高执行效率 -->
    <mvc:default-servlet-handler/>
4.开发Controller控制器
 在src/main/java文件夹下创建包扫描的文件夹.
 创建测试控制类。
package com.imooc.springmvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class TestController {
    @GetMapping("/t") //localhost/t
    @ResponseBody //直接向响应输出字符串数据,不跳转页面。把方法返回的结果直接写进http响应体中。
    public  String test(){
        return "SUCCESS";
    }
}
更新maven依赖
 因为新添加了springmvc的maven依赖,所以要更新maven依赖。
 Edit Configurations -> Deployment -> Edit Artifact -> 选中在Available Elements 的所有依赖 -> 点击 Put into /WEB-INF /lib.
 
之后访问http://localhost/t即可得到测试页面.
 
3. Spring MVC执行示意图
 
4. URL Mapping URL映射
问题:处理业务的方法如何和URL绑定在一起
 @RequestMapping - 通用绑定, 放在类上面为全局的方法都加上前缀,放在方法上面,不再区分get和post请求。
 @GetMapping - 绑定Get请求
 @PostMapping - 绑定Post请求
 @RequestMapping 绑定指定的类型请求方法:
 @RequestMapping(value=“/g”,method = RequestMethod.GET)。
package com.imooc.springmvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/um") //放在类上面为全局的方法都加上前缀,放在方法上面,不再区分get和post请求。
public class URLMappingController {
    @GetMapping("/g")
    @ResponseBody
    public String getMapping(){
        return "This is get method.";
    }
    @PostMapping ("/p")
    @ResponseBody
    public String postMapping(){
        return "This is post method.";
    }
}
5. Controller方法接受请求参数
使用Controller方法参数接收
 前端页面。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/um/p" method="post">
    <input name="username"><br/>
    <input name="password"><br/>
    <input type="submit" value="提交">
</form>
</body>
</html>
处理业务用的Controller类
package com.imooc.springmvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/um") //放在类上面为全局的方法都加上前缀,放在方法上面,不再区分get和post请求。
public class URLMappingController {
    @GetMapping("/g")
    @ResponseBody
    //Get请求中如果有不符合命名规范的参数可以使用@RequestParam()将不规范命名的参数注入后面的自定义参数中。
    public String getMapping(@RequestParam("user_name") String username){
        System.out.println("username:" + username);
        return "This is get method.";
    }
    @PostMapping ("/p")
    @ResponseBody
//    注:方法中的类型可以自动转换,如页面中的表单输入为字符串,
//    如果定义为public String postMapping(String username, Long password){},
//    则password参数自动转为Long类型。参数名一定要和前端页面中的表单参数名一样。
//    可能遇到的问题:如果页面上输入的数据带有非数字内容,可能会出现400转换错误,需要检查吧表单校验是否严谨。
    public String postMapping(String username, String password){
        System.out.println(username+":"+password);
        return "This is post method.";
    }
}
使用Java Bean接收数据
 如果表单的参数比较多,则可以使用实体对象来接收。
 首先准备一个实体类。
package com.imooc.springmvc.entity;
public class User {
    private String username;
    private Long password;
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public Long getPassword() {
        return password;
    }
    public void setPassword(Long password) {
        this.password = password;
    }
}
然后将接收的方法参数改为实体类,springmvc会自动找到这个实体类并根据类的参数赋值。
@PostMapping ("/p1")
    @ResponseBody
    public String postMappingBean(User user){
        System.out.println(user.getUsername()+":"+user.getPassword());
        return "This is Java Bean post method.";
    }
6. 接受表单复合数据
相对路径和绝对路径的区别。
 
 对于表单的复合数据如checkbox,可以使用数组,列表来接收,比如下面这个表单:
<!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="delivery.name" class="text" style="width: 150px">
        <h3>联系电话</h3>
        <input name="delivery.mobile" class="text" style="width: 150px">
        <h3>收货地址</h3>
        <input name="delivery.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>
可以用下面的几个方法进行接收:
 注:如果在方法的参数列表里用List(默认为ArrrayList)接收数据,则需要在参数前面加上@RequestParam注解,如果是用实体类接收数据,实体类中的List属性则不用加上@RequestParam注解。
 用Map接收数据也是。
 如果有参数可能为空,需要设置默认值则可以使用下面的方法:
 @RequestParam(value = “n”,defaultValue = “ANON”)。
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 = "n",defaultValue = "ANON") String name, String course, Integer[] purpose){
        System.out.println(name);
        System.out.println(course);
        for (Integer p : purpose) {
            System.out.println(p);
        }
        return "SUCCESS";
    }
//    @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";
    }
//    @PostMapping("/apply")
    @ResponseBody
    public String apply(Form form){
        return "SUCCESS";
    }
}
实体类的定义如下:
package com.imooc.springmvc.entity;
import java.util.List;
public class Form {
    private String name;
    private String course;
    private List<Integer> purpose;
    private Delivery delivery = new Delivery();
    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;
    }
    public Delivery getDelivery() {
        return delivery;
    }
    public void setDelivery(Delivery delivery) {
        this.delivery = delivery;
    }
}
注:Map也可以接收表单数据,但无法接收复合数据,如果接收复合数据则只会得到一个值,其他数据会丢失。
//    @PostMapping("/apply")
    @ResponseBody
    public String apply(@RequestParam Map map){
        System.out.println(map);
        return "SUCCESS";
    }
7. 关联对象赋值
如果我们想要在接收数据的实体类中引用另外一个实体类,则需要修改表单中的name属性,值应该为被引用的实体类对象在接收数据的实体类中的参数名.原本的参数名。
 举个例子如下,接收数据的实体类如下:
package com.imooc.springmvc.entity;
import java.util.List;
public class Form {
    private String name;
    private String course;
    private List<Integer> purpose;
    private Delivery delivery = new Delivery();
    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;
    }
    public Delivery getDelivery() {
        return delivery;
    }
    public void setDelivery(Delivery delivery) {
        this.delivery = delivery;
    }
}
被引用的实体类:
ackage com.imooc.springmvc.entity;
public class Delivery {
    private String name;
    private String address;
    private String mobile;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public String getMobile() {
        return mobile;
    }
    public void setMobile(String mobile) {
        this.mobile = mobile;
    }
}
表单中的属性值为:
<h3>收货人</h3>
        <input name="delivery.name" class="text" style="width: 150px">
        <h3>联系电话</h3>
        <input name="delivery.mobile" class="text" style="width: 150px">
        <h3>收货地址</h3>
        <input name="delivery.address" class="text" style="width: 150px">
使用注解将字符串转换为指定格式的额日期类型:
public String postMapping1(User user , String username ,@DateTimeFormat(pattern = "yyyy-MM-dd") Date createTime){
        return "<h1>这是Post响应</h1>";
    }
8. 解决中文乱码问题
对于Get方法,在8版本及以前的Tomcat找到安装文件夹下的conf目录中server.xml配置文件找到8080端口代码段进行修改
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443"/>
<!--修改为下面的代码-->
<Connector
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" URIEncoding="UTF-8"/>
对于Post方法,则需要在web.xml中配置过滤器
<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>
解决Response响应乱码-Spring项目的applicationContext.xml配置StringHttpMessageConverter
<mvc:annotation-driven conversion-service="conversionService">
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <!-- response.setContentType("text/html;charset=utf-8") -->
                        <value>text/plain;charset=utf-8</value>
                        <value>text/html;charset=utf-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
9. 响应结果输出
注解 @ResponseBody
 @ResponseBody - 产生响应文本
 @ResponseBody直接产生响应体的数据,过程不涉及任何视图
 @ResponseBody可产生标准字符串/JSON/XML等格式数据
 @ResponseBody被StringHttpMessageConverter所影响
@GetMapping("/g")
@ResponseBody
public String getMapping(@RequestParam("manager_name") String managerName , Date createTime){
    System.out.println("managerName:"+managerName);
    return "This is get method";
    }
ModelAndView对象
 ModelAndView对象是指"模型(数据)与视图(界面)”对象
 通过ModelAndView可将包含数据对象与模板引警进行绑定
 SpringMVC中默认的View是JSP也可以配置其他模板引擎
 这个模式的设计理念是将模型(即生成数据的Controller部分)和视图(即页面如jsp部分)进行解耦(通过EL表达式传输数据),实现动态页面。
@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("lily");
        }else if(userId == 2){
            user.setUsername("smith");
        }else if(userId == 3){
            user.setUsername("lina");
        }
        mav.addObject("u" , user);
        return mav;
    }
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>I'm view page</h1>
    <hr>
    <h3>Username:${u.username}</h3>
</body>
</html>
mav.addObject()方法设置的属性默认存放在当前请求中
 默认ModelAndView使用请求转发(forward)至页面
 重定向使用 new ModelAndView(“redirect:/indexjsp”)
 由于转发是将当前的请求发送给jsp页面,所以添加在mav.addObject()方法中的数据能用在jsp页面中。但是重定向是让服务器重新生成一个请求发送给jsp页面,所以mav.addObject()方法中的数据会丢失。
 一种替代的模型和视图的方法例子:
//String与ModelMap
    //Controller方法返回String的情况
    //1. 方法被@ResponseBody描述,SpringMVC直接响应String字符串本身
    //2. 方法不存在@ResponseBody,则SpringMVC处理String指代的视图(页面)
    @GetMapping("/xxxx")
//    @ResponseBody
    public String showView1(Integer userId , ModelMap modelMap){
        String view = "/um/view.jsp";
        User user = new User();
        if(userId == 1){
            user.setUsername("lily");
        }else if(userId == 2){
            user.setUsername("smith");
        }else if(userId == 3){
            user.setUsername("lina");
        }
        modelMap.addAttribute("u", user);
        return view;
    }
10. SpringMVC整合Freemarker
- 在pom.xml中引入依赖(不要忘记手动更新artifact的依赖)
<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>
- 在applicationContext.xml中配置freemarker
<!-- 让SpringMVC知道freemarker的存在  -->
<bean id="ViewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
		<!-- 设置响应输出,并解决中文乱乱码-->
        <property name="contentType" value="text/html;charset=utf-8"/>
        <!-- 指定Freemarker模板文件扩展名-->
        <property name="suffix" value=".ftl"/>
    </bean>
<!-- 配置freemarker的详细参数 -->
    <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
        <!--设置模板保存的目录-->
        <property name="templateLoaderPath" value="/WEB-INF/ftl"/>
       	<!--其他模板引擎设置-->
        <property name="freemarkerSettings">
            <props>
            	<!--设置Freemarker脚本与数据渲染时使用的字符集-->
                <prop key="defaultEncoding">UTF-8</prop>
            </props>
        </property>
    </bean>
整合完成,创建Controller测试用类
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("/test");
        User user = new User();
        user.setUsername("andy");
        mav.addObject("u", user);
        return mav;
    }
}
创建test.ftl页面
<h1>${u.username}</h1>


















