1. SpringMVC 入门
1.1 简介
Spring MVC是Spring提供的一个强大而灵活的web框架,目前最好的实现MVC设计模式的框架。借助于注解,Spring MVC提供了几乎是POJO的开发模式,使得控制器的开发和测试更加简单。这些控制器一般不直接处理请求,而是将其委托给Spring上下文中的其他bean,提供Spring的依赖注入功能,这些bean被注入到控制器中。
Spring MVC主要由DispatcherServlet、处理器映射、适配器、控制器、视图解析器、视图组成。
1.2 MVC设计模式
MVC是一种使用 MVC(Model View Controller 模型-视图-控制器)设计创建 Web 应用程序的模式。
- Controller:负责接收并处理用户请求,响应客户端。
 - Model:模型数据,业务逻辑。
 - View:展示模型,与用户进行交互。
 
1.3 SpringMVC核心组件
- DispatcherServlet:前置控制器。
 - HandlerMapping:将请求映射到Handler。
 - Handler:后端控制器,完成具体业务逻辑。
 - HandlerInterceptor:处理器拦截器。
 - HandlerExecutionChain:处理器执行链。
 - HandlerAdapter:处理器适配器。
 - ModelAndView:装载模型数据和视图信息。
 - ViewResolver:视图解析器。
 
1.4 SpringMVC实现流程
- 客户端请求被DispatcherServlet接收。
 - DispatcherServlet将请求映射到Handler。
 - 生成Handler以及HandlerInterceptor。
 - 返回HandlerExecutionChain(Handler+HandlerInterceptor)。
 - DispatcherServlet通过HandlerAdapter执行Handler。
 - 返回一个ModelAndView。
 - DispatcherServlet通过ViewResolver进行解析。
 - 返回填充了模型数据的View,响应给客户端。
 

2. SpringMVC快速开发
大部分组件由框架提供,开发者只需通过配置进行关联。开发者只需手动编写Handler和View。
2.1 基于XML配置的使用
- SpringMVC基础配置
 - XML配置Controller,HandlerMapping组件映射。
 - XML配置ViewResolver组件映射。
 
2.1.1 示例
- 使用maven创建一个简单的web项目,修改pom包导入相关jar包
 
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!--导入SpringMVC所需jar包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>4.3.1.RELEASE</version>
    </dependency>
    <!--导入servlet相关jar包-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
    </dependency>
</dependencies>
- 创建MyHandler控制器,完成具体业务逻辑
 
public class MyHandler implements Controller {
    @Override
    public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {
        // 装载模型数据和逻辑视图
        ModelAndView modelAndView = new ModelAndView();
        // 添加模型数据
        modelAndView.addObject("name","Tom");
        // 添加逻辑视图
        modelAndView.setViewName("show");
        return modelAndView;
    }
}
- 创建show.jsp,做前端显示
 
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@page isELIgnored="false" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    ${name}
</body>
</html>
- 资源文件夹resources下创建springmvc.xml的配置文件
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--
        URL处理映射的方式有三种:
        1. BeanNameUrlHandlerMapping:通过url名字,找到对应的bean的name的控制器
        2. 在SimpleUrlHandlerMapping中配置:通过key找到bean
        3. ControllerClassNameHandlerMapping:通过类名
    -->
    <!--1. 配置HandlerMapping,将url请求映射到Handler-->
    <bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <!--配置简单url映射-->
        <property name="mappings">
            <props>
                <!--2. 配置test请求对应的handler-->
                <prop key="/test">testHandler</prop>
            </props>
        </property>
    </bean>
    <!--3. 配置控制器-->
    <bean id="testHandler" class="com.zero.handler.MyHandler"/>
    <!--4. 配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--配置前缀-->
        <property name="prefix" value="/"/>
        <!--配置后缀-->
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>
- 在web.xml下配置DispatcherServlet
 
<?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">
  <!-- 配置:所有请求由SpringMVC管理 -->
  <servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>
</web-app>
- 启动服务器,访问/test.do路径资源,查看是否显示姓名。
 
2.2 基于注解方式的使用[重要]
- SpringMVC基础配置。
 - Controller,HandlerMapping通过注解进行映射。
 - XML中配置ViewResolver组件映射。
 
2.2.1 示例
- 其余配置不变,新建一个控制器类,使用注解声明
 
// 基于注解实现映射
@Controller
public class AnnotationHandler {
    /**
     * 业务方法1:ModelAndView完成数据的传递,视图的解析
     */
    @RequestMapping("/modelAndViewTest")
    public ModelAndView modelAndViewTest(){
        // 创建ModelAndView对象
        ModelAndView modelAndView = new ModelAndView();
        // 填充模型数据
        modelAndView.addObject("name","Tom");
        // 设置逻辑视图
        modelAndView.setViewName("show");
        return modelAndView;
    }
    /**
     * 业务方法2:Model传值,String进行视图解析
     * @return
     */
    @RequestMapping("/modelTest")
    public String ModelTest(Model model){
        // 填充模型数据
        model.addAttribute("name","Jerry");
        // 设置逻辑视图
        return "show";
    }
    /**
     * 业务方法3:Map传值,String进行视图解析
     */
    @RequestMapping("/mapTest")
    public String MapTest(Map<String,String> map){
        // 填充模型数据
        map.put("name","Cat");
        // 设置逻辑视图
        return "show";
    }
}
- mvc配置文件中,开启自动扫描,并删除前面配置的两个bean对象即可
 
<!--1. 配置自动扫描包-->
<context:component-scan base-package="com.zero.backoffice.web.controller"/>
- 重启服务器,访问
/modelAndViewTest等资源路径,测试是否成功。 
2.3 URL处理器映射[了解]
BeanNameUrlHandlerMapping
- 功能:寻找controller,根据url请求去匹配bean的name属性,找到对应的控制器。
 
<!-- 1. 配置处理器映射,mvc默认的处理器映射器 BeanNameUrlHandlerMapping:根据bean的name属性的url去找Controller --> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/> <bean name="/user.do" class="com.zero.backoffice.web.controller.UserController"/>SimpleUrlHandlerMapping
- 功能:寻找controller,根据浏览器url匹配简单url的key,key就是通过controller的id找到对应的Controller
 
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"> <property name="mappings"> <props> <prop key="/user1.do">userController</prop> </props> </property> </bean> <bean id="userController" name="/user.do" class="com.zero.web.controller.UserController"/>ControllerClassNameHandlerMapping
- 功能:寻找controller,根据类名.do来访问,类名首字母小写。如下访问路径就是
userController.do 
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/> <bean class="com.zero.backoffice.web.controller.UserController"/>- 功能:寻找controller,根据类名.do来访问,类名首字母小写。如下访问路径就是
 
2.4 处理器适配器
SimpleControllerHandlerAdapter
- 功能:执行controller,调用controller里面的方法,返回ModelAndView
 
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"/>HttpRequestHandlerAdapter
- 执行控制器:负责调用实现HttpRequestHandler接口的控制器
 
2.5 乱码问题
- POST请求乱码,需在web.xml中配置编码过滤器
 
<!-- 配置编码过滤器  -->
<filter>
    <filter-name>EncodingFilter</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>EncodingFilter</filter-name>
</filter-mapping>
- GET请求乱码:Tomcat8默认进行了url编码,故get请求不会乱码,tomcat7会乱码。
 
3. SpringMVC接收请求参数
3.1 封装参数分析
- 常用的数据绑定类型
- 基本数据类型
 - 包装类
 - 数组
 - 对象(POJO)
 - 集合(List、Set、Map)
 - JSON
 
 
3.2 接收基本数据类型
- 编写一个注册类register.jsp
 
<body>
    <form action="${pageContext.request.contextPath}/user/register.do" method="post">
        用户名:<input type="text" name="username"><br>
        密码:<input type="text" name="password"><br>
        性别:<input type="text" name="gender"><br>
        年龄:<input type="text" name="age"><br>
        生日:<input type="text" name="birthday"><br>
        爱好:<input type="checkbox" name="hobbyIds" value="1">打球
             <input type="checkbox" name="hobbyIds" value="2">看书
             <input type="checkbox" name="hobbyIds" value="3">玩游戏<br>
        <input type="submit">
    </form>
</body>
- 控制器中新增注册方法
 
@Controller
@RequestMapping("user")
public class UserController{
    @RequestMapping("/toRegister")
    public String toRegister(){
        return "user/register";
    }
    /**
     * 第一种接收表单参数的方式:
     *      直接在形参列表写要接受的参数
     *      默认日期格式: MM/DD/YYYY
     * @return
     */
    @RequestMapping("/register")
    public String register(String username, String password,
                           int age, String gender, Date birthday,
                           String[] hobbyIds){
        System.out.println(username);
        return "user/info";
    }
}
3.3 接收POJO类型
- 模型类User新增对应属性
 
public class User {
    private String username;
    private String password;
    private String gender;
    private int age;
    private String birthday;
    private String[] hobbyIds;
}
- 控制器中新增方法
 
/**
     * 第二种接收表单参数的方式:
     *     使用POJO对象接收
     * @return
     */
@RequestMapping("/register2")
public String register2(User user){
    System.out.println(user);
    return "user/info";
}
register.jsp中修改
<form action="${pageContext.request.contextPath}/user/register.do" method="post">info.jsp做数据展示
<body>
<h4>用户信息</h4>
用户名:${user.username}<br>
密码:${user.password}<br>
性别:${user.gender}<br>
年龄:${user.age}<br>
生日:${user.birthday}<br>
爱好:${user.hobbyIds}<br>
</body>
3.4 接收包装类型参数
- 创建UserExt类,把User写成一个类的属性,模型里面有模型
 
public class UserExt {
    private User user;
    @Override
    public String toString() {
        return "UserExt{" +
                "user=" + user +
                '}';
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
}
- 控制器添加注册方法
 
/**
     * 第三种接收表单参数的方式:接收包装类型参数
     *     使用包装类,相当于模型里面有模型
     * @return
     */
@RequestMapping("/register3")
public String register3(UserExt user){
    System.out.println(user);
    return "user/info";
}
- 修改form表单
 
<body>
    <form action="${pageContext.request.contextPath}/user/register3.do" method="post">
        用户名:<input type="text" name="user.username"><br>
        密码:<input type="text" name="user.password"><br>
        性别:<input type="text" name="user.gender"><br>
        年龄:<input type="text" name="user.age"><br>
        生日:<input type="text" name="user.birthday"><br>
        爱好:<input type="checkbox" name="user.hobbyIds" value="1">打球
             <input type="checkbox" name="user.hobbyIds" value="2">看书
             <input type="checkbox" name="user.hobbyIds" value="3">玩游戏<br>
        <input type="submit">
    </form>
</body>
3.5 接收List集合类型参数
- 修改UserExt类,添加list集合属性
 
public class UserExt {
    private User user;
    private List<User> users = new ArrayList<>();
    @Override
    public String toString() {
        return "UserExt{" +
                "user=" + user +
                ", users=" + users +
                '}';
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    public List<User> getUsers() {
        return users;
    }
    public void setUsers(List<User> users) {
        this.users = users;
    }
}
- register.jsp中新增一个测试form表单
 
<h4>接收集合类型的参数</h4>
<form action="${pageContext.request.contextPath}/user/register4.do" method="post">
    用户名1:<input type="text" name="users[0].username"><br>
    密码1:<input type="text" name="users[0].password"><br>
    <hr>
    用户名2:<input type="text" name="users[1].username"><br>
    密码2:<input type="text" name="users[1].password"><br>
    <input type="submit" value="保存">
</form>
- 添加注册方法
 
/**
     * 第四种接收表单参数的方式:接收List集合类型参数
     * @return
     */
@RequestMapping("/register4")
public String register4(UserExt user){
    System.out.println(user.getUsers());
    return "user/info";
}
3.6 接收Map集合类型参数
- UserExt添加一个map集合属性
 
public class UserExt {
    private User user;
    private List<User> users = new ArrayList<>();
    private Map<String,Object> infos = new HashMap<String,Object>(); // 使用map来接收参数
}
- 添加注册方法
 
/**
     * 第五种接收表单参数的方式:接收Map集合类型参数
     * @return
     */
@RequestMapping("/register5")
public String register5(UserExt user){
    System.out.println(user.getInfos());
    return "user/info";
}
- 修改表单
 
<h4>
    表单数据用map接收
</h4>
<form action="${pageContext.request.contextPath}/user/register5.do" method="post">
        用户名:<input type="text" name="infos['username']"><br>
        密码:<input type="text" name="infos['password']"><br>
        性别:<input type="text" name="infos['gender']"><br>
        年龄:<input type="text" name="infos['age']"><br>
        <input type="submit">
</form>
4. 页面回显
- 控制器配置一个列表方法
 
@RequestMapping("/list")
    public String userlist(Model model){
        // 1. 模拟一个数据库中的数据
        List<User> users = new ArrayList<>();
        users.add(new User("小明","male",22,"2018"));
        users.add(new User("小李","male",12,"2019"));
        users.add(new User("小哈","male",32,"2014"));
        // 将数据存储到model中
        model.addAttribute("userList",users);
        return "user/userList";
    }
- 前端jsp展示数据
 
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
    <title>UserList</title>
</head>
<body>
用户列表:
<table border="1">
    <tr>
        <td>姓名:</td>
        <td>年龄</td>
        <td>性别</td>
        <td>生日</td>
    </tr>
    <c:forEach items="${userList}" var="user">
        <tr>
            <td>${user.username}</td>
            <td>${user.age}</td>
            <td>${user.gender}</td>
            <td>${user.birthday}</td>
        </tr>
    </c:forEach>
</table>
</body>
</html>
- 修改用户方法示例
 
// 编辑页面
@RequestMapping("/edit")
public String edit(Integer id, Model model){
    // 模拟通过id在数据库中查询数据,返回一个user对象,再将user对象存入model
    System.out.println("修改的用户id:"+id);
    User user = new User(id, "小明", "male", 22, "2018");
    model.addAttribute("user",user);
    return "user/userEdit";
}
// 修改用户信息
@RequestMapping("/update")
public String update(Integer id,Model model){
    System.out.println("修改的用户id:"+id);
    return "user/userList";
}
- 编辑jsp界面
 
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Edit</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/user/update.do?id=${user.id}" method="post">
    用户名:<input type="text" name="username" value="${user.username}"><br>
    年龄:<input type="text" name="age" value="${user.age}"><br>
    性别:<input type="text" name="gender" value="${user.gender}"><br>
    生日:<input type="text" name="birthday" value="${user.birthday}"><br>
    <button type="submit" value="提交修改"/>
</form>
</body>
</html>
5. URL模板映射
模板映射可以RESTful软件架构风格。
- 修改url格式,体验RESTful
 
<a href="${pageContext.request.contextPath}/user/edit1/${user.id}.do">RESTful修改</a>
<!--配置接收url模板映射:
    {}:匹配接收页面url路径参数-->
- 控制器中添加方法
 
// 编辑页面,使用RESTful风格。{id}里面参数通过@PathVariable注入到后面参数Integer id中去
@RequestMapping("/edit1/{id}")
public String edit1(@PathVariable Integer id, Model model){
    // 模拟通过id在数据库中查询数据,返回一个user对象,再将user对象存入model
    System.out.println("修改的用户id:"+id);
    User user = new User(id, "小明", "male", 22, "2018");
    model.addAttribute("user",user);
    return "user/userEdit";
}
- 需在web.xml中配置rest路径
 
<servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>*.do</url-pattern>
</servlet-mapping>
<!--RESTful风格-->
<servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/rest/*</url-pattern>
</servlet-mapping>
- 测试页面,
http://localhost/rest/user/edit1/1 
6. 转发和重定向
- 转发到同一个控制器的方法:
forward 
// 示例转发到list.do。同一个控制器中转发
@RequestMapping("/test1")
public String test1(){
    return "forward:list.do";
}
- 转发到不同的控制器
 
@Controller
@RequestMapping("stu")
public class StudentController {
    // 示例转发到list.do。不同控制器中转发,加上域名/user即可
    @RequestMapping("/test1")
    public String test1(){
        return "forward:/user/list.do";
    }
}
- 重定向,只需改成
redirect即可 
@Controller
@RequestMapping("stu")
public class StudentController {
    // 示例重定向到list.do。不同控制器中转发
    @RequestMapping("/test1")
    public String test1(){
        return "redirect:/user/list.do";
    }
}
7. RequestParam
- 该注解是对参数的一些说明描述
- value:参数名称
 - defaultValue:默认值
 - required:参数是否必须有值。如果为true,参数又是空且未定义默认值,则会报错。
 
 
- 示例
 
@Controller
@RequestMapping("stu")
public class StudentController {
    // 示例注解请求参数配置
    @RequestMapping("/test1")
    public String test1(@RequestParam(value = "uid",required = true,defaultValue = "1")Integer uid){
        System.out.println(uid);
        return "redirect:/user/list.do";
    }
}