一、SpringMVC-简介

SpringMVC属于Spring框架的后续产品,用在基于MVC结构的控制层开发,属于SpringWeb模块,有点类似于Struts2 + Spring。

其中MVC是一种设计模式,M代表Model可以由service+dao+entity构成;V代表View表示前端视图,由html、jsp等构成;C代表Controller即控制器,顾名思义充当了一个指挥塔的中心作用:接收请求、调用接口、转发页面。

二、SpringMVC-核心组件

1.前端控制器-DispatcherServlet

DispatcherServlet在整个SpringMVC中起到核心作用,是整个流程控制的中心,由它调用其它组件处理用户的请求,其存在降低了组件之间的耦合性。

2.处理器映射器-HandlerMapping

HandlerMapping负责根据用户请求找到对应的处理器Handler,SpringMVC提供了配置文件方式、实现接口方式、注解方式等多种不同的映射实现方式。

3.处理器适配器-HandlerAdapter

HandlerAdapter根据特定的规则去执行Handler并获得模型视图ModelAndView

4.处理器-Handler(Controller)

Handler是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。

由于Handler涉及到具体的用户业务请求,所以需要根据业务需求开发Handler。即后端控制器用Controller表示。

5.视图解析器-ViewResolver

ViewResolver负责进行视图解析,根据逻辑视图名解析成真正的View视图。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要根据业务需求开发具体的页面。

6.视图-View

View是一个接口,会根据传进来的Model模型数据对视图进行渲染,实现类支持不同的View类型(jsp、freemarker、pdf…)。

另外多说一句,之前工作的时候用过JSP,后来要求前后端分离,基本上是前端发送AJAX请求到后台控制层,然后控制层直接响应前端请求的数据,并不需要后台去进行视图渲染之类的,前端拿到数据自行渲染。

三、SpringMVC-工作流程

流程图

spring controller实例 springmvc的controller_MVC

如上图所示,SpringMVC的流程具体如下

  • 1.客户端发出http请求,只要符合web.xml文件中配置的形式(如*.do),就由前端控制器DispatcherServlet进行处理。
  • 2.DispatcherServlet将请求交给处理器映射器HandlerMapping去根据配置或注解找到要执行的Handler并将Handler返回给DispatcherServlet
  • 3.DispatcherServlet调用适配器HandlerAdapter来执行Handeler进入Controller层执行实现的方法后返回ModelAndViewHandlerAdapterHandlerAdapter再返回给DispatcherServlet
  • 4.DispatcherServlet调用视图解析器ViewResolverModelAndView进行视图解析,将逻辑视图解析成真正的视图国际化处理并给DispatcherServlet返回View
  • 5.DispatcherServlet调用View根据传进来的Model模型数据进行视图渲染。
  • 6.DispatcherServletModelAndView中的Model数据转为response响应转发到的渲染后的视图页面回馈给用户。

四、数据传递

SpringMVC会以方法参数的形式收集数据,因此可以通过以下几种方式来收集前端传来的数据,这里以注解方式为例

1.设置普通变量收集参数

在调用的函数中设置需要使用的参数,SpringMVC会自动为其传入,基础类型的参数会根据前端传来的参数名字对号入座,当然也可以设置例如Model(ModelAndVie),HttpSevletRequest等,但是一般不提倡将requestresponse传入,会产生一定程度上的耦合,但是不排除一些地方需要进行转发、重定向等,所以要用就用吧。

  • 控制器
@Controller
@RequestMapping("/AC")
public class AnnotationController {
    @RequestMapping("/methodTwo", String name)
    public String methodTwo(Model model) {
        model.addAttribute("message", "this is methodTwo");
        model.addAttribute("name", name);
        return "methodView";
    }
}
  • 前端-显示端(JSP)
<body>
    ${message}<br/>
    ${name}
</body>
  • 前端-提交端(JSP)
<body>
    <div align="center">
        <form action="/AC/methodThree.do" method="post">
            <table align="center">
                <tr>
                    <td>名字:<input type="text" name="name"/></td>
                </tr>
                <tr>
                    <td><input type="submit" value="提交"></td>
                </tr>
            </table>
        </form>
    </div>
</body>
2.使用实体类收集数据

当前端传入的参数过多时,我们可以通过创建一个实体类,让实体类中的成员变量名与前端传入的参数名一致,再将该实体类作为控制层业务方法的一个参数,这样SpringMVC就可以将所有的前端参数封装到该实体类中来完成数据传递(这里会出现日期格式转换问题,在第三章中说明)

  • 控制器
@Controller
@RequestMapping("/AC")
public class AnnotationController {
    /**日期转换处理*/
    @InitBinder
    public void initBinder(ServletRequestDataBinder binder){
        binder.registerCustomEditor(Date.class,
                new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
    }

    @RequestMapping("/methodThree")
    public String methodThree(Model model, User user) {
        model.addAttribute("user", user);
        return "userView";
    }
}
  • 前端-提交端(JSP)
<body>
    <div align="center">
        <form action="/AC/methodThree.do" method="post">
            <table align="center">
                <tr>
                    <td>名字:<input type="text" name="name"/></td>
                </tr>
                <tr>
                    <td>年龄:<input type="text" name="age"/></td>
                </tr>
                <tr>
                    <td>生日:<input type="text" name="birth"/></td>
                </tr>
                <tr>
                    <td><input type="submit" value="提交"></td>
                </tr>
            </table>
        </form>
    </div>
</body>
  • 前端-显示端(JSP)
<body>
    姓名: ${user.name}<br/>
    年龄: ${user.age}<br/>
    生日: ${user.birth}<br/>
</body>
3.包装实体类来收集数据

具体做法是通过创建包装类,里面的成员是各个实体类,实现多个实体类的封装,然后将这个包装类作为函数的参数,前端在传参时候可以添加包装类中对应实体类的成员名称作为前缀来进行区分,这样SpringMVC会非常机智的自动封装数据实现数据传递

  • 包装类
public class Beans {
    private User user;
    private Admin admin;
    ......
}
  • 控制层
@Controller
@RequestMapping("/AC")
public class AnnotationController {
    /**日期转换处理*/
    @InitBinder
    public void initBinder(ServletRequestDataBinder binder){
        binder.registerCustomEditor(Date.class,
                new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
    }
    @RequestMapping("/methodFore")
    public String methodFore(Model model, Beans beans) {
        model.addAttribute("user",beans.getUser());
        return "userView";
    }
}
  • 前端-提交端(JSP)
<body>
    <div align="center">
        <h1>包装多个实体类接收参数</h1>
        <form action="/AC/method.Foredo" method="post">
            <table align="center">
                <tr>
                    <td>名字:<input type="text" name="user.name"/></td>
                </tr>
                <tr>
                    <td>年龄:<input type="text" name="user.age"/></td>
                </tr>
                <tr>
                    <td>生日:<input type="text" name="user.birth"/></td>
                </tr>
                <tr>
                    <td><input type="submit" value="提交"></td>
                </tr>
            </table>
        </form>
    </div>
</body>
  • 前端-展示端(JSP)
<body>
    姓名: ${user.name}<br/>
    年龄: ${user.age}<br/>
    生日: ${user.birth}<br/>
</body>
4.批量数据收集
(1).数组的收集

没啥好说的写了简略代码

public String getList(int[] ids, Model model){
     ......
 }
(2).对象集合收集批量对象数据
  • 包装类
public class Beans {
    private List<User> users = new ArrayList<>();
    ......
}
  • 控制层
@Controller
@RequestMapping("/AC")
public class AnnotationController {
    /**日期转换处理*/
    @InitBinder
    public void initBinder(ServletRequestDataBinder binder){
        binder.registerCustomEditor(Date.class,
                new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
    }
    @RequestMapping("/methodFive")
    public String methodFive(Model model, Beans beans) {
        model.addAttribute("user", beans.getUsers().get(0));
        return "userView";
    }
}
  • 前端-提交端(JSP)
<body>
    <div align="center">
        <h1>批量参数的接收</h1>
        <form action="/AC/methodFive.do" method="post">
            <table align="center">
                <tr>
                    <td>名字:<input type="text" name="users[0].name"/></td>
                </tr>
                <tr>
                    <td>年龄:<input type="text" name="users[0].age"/></td>
                </tr>
                <tr>
                    <td>生日:<input type="text" name="users[0].birth"/></td>
                </tr>
                <tr>
                    <td>名字:<input type="text" name="users[1].name"/></td>
                </tr>
                <tr>
                    <td>年龄:<input type="text" name="users[1].age"/></td>
                </tr>
                <tr>
                    <td>生日:<input type="text" name="users[1].birth"/></td>
                </tr>
                <tr>
                    <td><input type="submit" value="提交"></td>
                </tr>
            </table>
        </form>
    </div>
</body>
  • 前端-展示端(JSP)
<body>
    姓名: ${user.name}<br/>
    年龄: ${user.age}<br/>
    生日: ${user.birth}<br/>
</body>

五、控制层的跳转

1.页面跳转
  • 返回ModelAndVie

当业务方法返回ModelAndView类型时,视图解析器会根据ModelAndView对象中的viewName属性去进行视图匹配找到对应的视图页面进行渲染后跳转

  • 返回String

当业务方法直接返回字符串类型时,视图解析器会根据返回的字符串进行视图匹配找到对应的视图页面进行渲染后跳转,需要注意的是,如果返回的字符串是逻辑路径,需要在配置文件中进行视图解析器的配置来进行前后缀的设定。

2.转发和重定向
  • 转发

转发可以将requestmodel从一个业务方法传递到另一个业务方法中,在SpringMVC中只要通过返回字符串forward:/AC/methodOne.do即可,切记带上后缀。

然后再对应的业务方法中设置需要接收的参数即可收到传递的数据。

  • 重定向

与转发不同,重定向不会传递任何数据,在SpringMVC中可以通过返回字符串redirect:/user/methodOne.do即可,切记带上后缀。

需要带上点数据时候可以通过在url后面添加参数redirect:/user/methodOne.do?id=1或者放进session中,重定向后再从session中拿出来。

六、接收与返回JSON

1.接收JSON

当前端通过AJAX发送JSON到后台时,SpringMVC会自动将JSON中的数据封装到对应的业务方法参数中。

2.返回JSON

SpringMVC支持通过注解@ResponseBody的形式将返回的对象直接转为JSON格式的字符串返回给前端,但在使用前需要导入JSON相关依赖包

<dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.9.6</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.6</version>
    </dependency>
3.代码示例
  • 后台
@Controller
@RequestMapping("/AC")
public class AnnotationController {

    /**日期转换处理*/
    @InitBinder
    public void initBinder(ServletRequestDataBinder binder){
        binder.registerCustomEditor(Date.class,
                new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
    }
    
    @RequestMapping("/methodJson")
    @ResponseBody
    public User methodJson(User user) {
        return user;
    }
}
  • 前端
<head>
    <title>AJAX</title>
    <script src="/js/jquery-3.3.1.min.js"></script>
</head>
<body>
    <script>
        function requestMethod (){
            const user = {
                name: 'Schuyler',
                age: 23
            };
            $.ajax({
                type: "POST",
                url: "/AC/methodJson.do",
                data: user,
                success: function (msg) {
                    alert("name: " + msg.name + ",age: " + msg.age);
                }
            });
        }
    </script>
    <input type="button" value="我按" onclick="requestMethod()"/>
</body>