springboot学习10——springmvc(上)

Spring MVC定位于一个较为松散的组合,展示给用户的视图(View)、控制器返回的数据模型(Model)、定位视图的视图解析器(ViewResolver)和处理适配器(HandlerAdapter)等内容都是独立的。
换句话说,通过Spring MVC很容易把后台的数据转换为各种类型的数据。

例如,Spring MVC可以十分方便地转换为目前最常用的JSON数据集,也可以转换为PDF、Excel和XML等。加之Spring MVC是基于Spring基础框架派生出来的Web框架,所以它天然就可以十分方便地整合到Spring框架中。

基于这些趋势,Spring MVC已经成为当前最主流的Web开发框架。学习Spring MVC,首先是学习其基于MVC的分层的思想

一、Spring MVC框架的设计

MVC的巨大成功在于它的理念,所以有必要学习一下MVC框架。
先看下Spring MVC的示意图:
springboot学习10——springmvc(上)_第1张图片

MVC框架运行的流程:
1.处理请求先到达控制器(Controller),控制器的作用是进行请求分发,这样它会根据请求的内容去访问模型层(Model);
2.在现今互联网系统中,数据主要从数据库和NoSQL中来,而且对于数据库而言往往还存在事务的机制,为了适应这样的变化,设计者会把模型层再细分为两层,即服务层(Service)和数据访问层(DAO);
3.当控制器获取到由模型层返回的数据后,就将数据渲染到视图中,这样就能够展现给用户了。

二、Spring MVC流程

Spring MVC的流程是围绕DispatcherServlet而工作的,所以在Spring MVC中DispatcherServlet就是其最重要的内容。
在DispatcherServlet的基础上,还存在其他的组件,掌握流程和组件就是Spring MVC开发的基础。

上面是Spring MVC运行的全流程,但是严格地说,Spring MVC处理请求并非一定需要经过全流程,有时候一些流程并不存在。例如,在我们加入@ResponseBody时,是没有经过视图解析器和视图渲染的。
springboot学习10——springmvc(上)_第2张图片

1.首先,在Web服务器启动的过程中,如果在Spring Boot机制下启用Spring MVC,它就开始初始化一些重要的组件,如DispactherServlet、HandlerAdapter的实现类RequestMappingHandlerAdapter等组件对象。
关于这些组件的初始化,我们可以看到spring-webmvc-xxx.jar包的属性文件DispatcherServlet.properties,
它定义的对象都是在Spring MVC开始时就初始化,并且存放在Spring IoC容器中。

DispatcherServlet.properties
# Default implementation classes


for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

# 国际化解析器
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

# 主题解析器
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

# HandlerMapping实例
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.
BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

# 处理器适配器
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.
HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

# 处理器异常解析器
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.
servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

# 策略视图名称转换器,当你没有返回视图逻辑名称的时候,通过它可以生成默认的视图名称
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.
servlet.view.DefaultRequestToViewNameTranslator

# 视图解析器
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.
InternalResourceViewResolver

# FlashMap管理器。不常用,不再讨论
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.
support.SessionFlashMapManager

上述组件会在Spring MVC得到初始化,尤其是在Spring Boot中,更是如此,我们可以通过Spring Boot的配置来定制这些组件的初始化。

2.其次是开发控制器(Controller)

package com.springboot.chapter9.controller;
/**** imports ****/
@Controller
@RequestMapping("/user")
public class UserController {

    // 注入用户服务类
    @Autowired
    private UserService userService = null;

    // 展示用户详情
    @RequestMapping("details")
    public ModelAndView details(Long id) {
        // 访问模型层得到数据
        User user = userService.getUser(id);
        // 模型和视图
        ModelAndView mv = new ModelAndView();
        // 定义模型视图
        mv.setViewName("user/details");
        // 加入数据模型
        mv.addObject("user", user);
        // 返回模型和视图
        return mv;
    }
}

3.注解@Controller表明这是一个控制器,然后@RequestMapping代表请求路径和控制器(或其方法)的映射关系,它会在Web服务器启动Spring MVC时,就被扫描到HandlerMapping的机制中存储,
之后在用户发起请求被DispatcherServlet拦截后,通过URI和其他的条件,通过HandlerMapper机制就能找到对应的控制器(或其方法)进行响应。
只是通过HandlerMapping返回的是一个HandlerExecutionChain对象。

HandlerExecutionChain源码

package org.springframework.web.servlet;
/**** imports ****/
public class HandlerExecutionChain {
    // 日志
    private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);
    // 处理器
    private final Object handler;
    // 拦截器数组
    private HandlerInterceptor[] interceptors;
    // 拦截器列表
    private List interceptorList;
    // 拦截器当前下标
    private int interceptorIndex= -1;
    ......
}

4.HandlerExecutionChain对象包含一个处理器(handler)
这里的处理器是对控制器(controller)的包装,因为我们的控制器方法可能存在参数,那么处理器就可以读入HTTP和上下文的相关参数,然后再传递给控制器方法。而在控制器执行完成返回后,处理器又可以通过配置信息对控制器的返回结果进行处理。

从这段描述中可以看出,处理器包含了控制器方法的逻辑,此外还有处理器的拦截器(interceptor),这样就能够通过拦截处理器进一步地增强处理器的功能。

5.得到了处理器(handler),还需要去运行,但是我们有普通HTTP请求,也有按BeanName的请求,甚至是WebSocket的请求,所以它还需要一个适配器去运行HandlerExecutionChain对象包含的处理器,
这就是HandlerAdapter接口定义的实现类。Spring MVC中最常用的HandlerAdapter的实现类,这是HttpRequestHandlerAdapter。

6.通过请求的类型,DispatcherServlet就会找到它来执行Web请求的HandlerExecutionChain对象包含的内容,这样就能够执行我们的处理器(handler)了

7.在处理器调用控制器时,它首先通过模型层得到数据,再放入数据模型中,最后将返回模型和视图(ModelAndView)对象
这里控制器设置的视图名称设置为“user/details”,这样就走到了视图解析器(ViewResolver),去解析视图逻辑名称了。

8.视图解析器(ViewResolver)的自动初始化是可定制的
为了定制InternalResourceViewResolver初始化,可以在配置文件application.properties中进行配置。

spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

通过修改这样的配置,就能在Spring Boot的机制下定制InternalResourceViewResolver这个视图解析器的初始化,
也就是在返回视图名称之后,它会以前缀(prefix)和后缀(suffix)以及视图名称组成全路径定位视图。例如,在控制器中返回的是“user/details”,那么它就会找到/WEB-INF/jsp/user/details.jsp作为视图(View)。

严格地说,这一步也不是必需的,因为有些视图并不需要逻辑名称,在不需要的时候,就不再需要视图解析器工作了。

9.视图解析器定位到视图后,视图的作用是将数据模型(Model)渲染,这样就能够响应用户的请求。
这一步就是视图将数据模型渲染(View)出来,用来展示给用户查看。
按照我们控制器的返回,就是/WEB-INF/jsp/user/details.jsp作为我们的视图。

Spring Boot启动文件

// 定制扫描路径
@SpringBootApplication(scanBasePackages = "com.springboot.chapter9")
// 扫描MyBatis的DAO接口
@MapperScan(basePackages = "com.springboot.chapter9",
   annotationClass = Repository.class)
public class Chapter9Application {
    public static void main(String[] args) {
        SpringApplication.run(Chapter9Application.class, args);
    }
}

运行它得到如下日志:

Mapped "{[/user/details]}" onto p
ublic org.springframework.web.servlet.ModelAndView 
com.springboot.chapter9.controller.UserController.details(java.lang.Long)
Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity> org.springframework.boot.autoconfigure.web.
BasicErrorController.error(javax.servlet.http.HttpServletRequest)
Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.
ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml
(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.
servlet.resource.ResourceHttpRequestHandler]
Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.
resource.ResourceHttpRequestHandler]
Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.
web.servlet.resource.ResourceHttpRequestHandler]
Registering beans for JMX exposure on startup
Tomcat started on port(s): 8080 (http)
Started Chapter9Application in 3.275 seconds (JVM running for 3.608)

注意开头的部分,证明我们配置的@RequestMapping的请求映射已经在服务器启动时被扫描到了Spring的上下文中,所以当请求来到时就可以匹配找到对应的控制器去提供服务。

然后我们通过请求http://localhost:8080/user/details?id=1以及HandlerMapping的匹配机制就可以找到处理器提供服务。而这个处理器则包含我们开发的控制器,那么进入这个控制器后,它就执行控制器的逻辑,通过模型和视图(ModelAndView)绑定了数据模型,而且把视图名称修改为了“user/details”,随后返回。

模型和视图(ModelAndView)返回后,视图名称为“user/details”,而我们定义的视图解析器(InternalResourceViewResolver)的前缀为/WEB-INF/jsp/,且后缀为.jsp,这样它便能够映射为/WEB- INF/jsp/user/details.jsp,进而找到JSP文件作为视图,这便是视图解析器的作用。然后将数据模型渲染到视图中。

有时候,我们可能需要的只是JSON数据集,因为目前前后台分离的趋势,使用JSON已经是主流的方式,正如我们之前使用的@ResponseBody标明方法一样,
在后面Spring MVC会把数据转换为JSON数据集,但是这里暂时不谈@ResponseBody,因为它会采用处理器内部的机制。本节暂时不讨论处理器的内部机制,
而是先用MappingJackson2JsonView转换出JSON
springboot学习10——springmvc(上)_第3张图片

实例在Spring MVC下的流程图

使用JSON视图

@RequestMapping("/detailsForJson")
public ModelAndView detailsForJson(Long id) {
    // 访问模型层得到数据
    User user = userService.getUser(id);
    // 模型和视图
    ModelAndView mv = new ModelAndView();
    // 生成JSON视图
    MappingJackson2JsonView jsonView = new MappingJackson2JsonView();
    mv.setView(jsonView);
    // 加入模型
    mv.addObject("user", user);
    return mv;
}

可以看到,在控制器的方法中模型和视图(ModelAndView)中捆绑了JSON视图(Mapping- Jackson2JsonView)和数据模型(User对象),然后返回,其结果也会转变为JSON,
只是需要注意的是这步与我们使用JSP作为视图是不一样的。

之前我们给视图设置了名称,它会根据视图解析器(InternalResourceViewResolver)的解析找到JSP,然后渲染数据到视图中,从而展示最后的结果,而这里的JSON视图是没有视图解析器的定位视图的,因为它不是一个逻辑视图,只是需要将数据模型(这里是User对象)转换为JSON而已。

从流程图中我们可以看到并没有视图解析器,那是因为MappingJackson2JsonView是一个非逻辑视图。它并不需要视图解析器进行定位,它的作用只是将数据模型渲染为JSON数据集来响应请求。

可见Spring MVC中,不是每一个步骤都是必需的,而是根据特别的需要会有不同的流程。
springboot学习10——springmvc(上)_第4张图片

三、定制Spring MVC的初始化

在Servlet 3.0规范中,web.xml再也不是一个必需的配置文件。为了适应这个规范,Spring MVC从3.1版本开始也进行了支持,也就是我们已经不再需要通过任何的XML去配置Spring MVC的运行环境。
为了支持对于Spring MVC的配置,Spring提供了接口WebMvcConfigurer,这是一个基于Java 8的接口,所以其大部分方法都是default类型的,但是它们都是空实现,这样开发者只需要实现这个接口,重写需要自定义的方法即可,这样就很方便进行开发了。

在Spring Boot中,自定义是通过配置类WebMvcAutoConfiguration定义的,它有一个静态的内部类WebMvcAutoConfigurationAdapter,通过它Spring Boot就自动配置了Spring MVC的初始化。
springboot学习10——springmvc(上)_第5张图片

在WebMvcAutoConfigurationAdapter类中,它会读入Spring配置Spring MVC的属性来初始化对应组件,这样便能够在一定程度上实现自定义。

Spring MVC可配置项
# SPRING MVC (WebMvcProperties)
spring.mvc.async.request-timeout=             # 异步请求超时时间(单位为毫秒)
spring.mvc.contentnegotiation.favor-parameter=false 
                         #  是否使用请求参数(默认参数为"format")来确定请求的媒体类型
spring.mvc.contentnegotiation.favor-path-extension=false 
                         # 是否使用URL中的路径扩展来确定请求的媒体类型
spring.mvc.contentnegotiation.media-types.*= 
                         # 设置内容协商向媒体类型映射文件扩展名。例如,YML文本/YAML
spring.mvc.contentnegotiation.parameter-name= # 当启用favor-parameter参数是,自定义参数名
spring.mvc.date-format=                 # 日期格式配置,如yyyy-MM-dd
spring.mvc.dispatch-trace-request=false # 是否让FrameworkServlet doService方法支持TRACE请求
spring.mvc.dispatch-options-request=true 
                         # 是否启用 FrameworkServlet doService 方法支持OPTIONS请求
spring.mvc.favicon.enabled=true         # spring MVC的图标是否启用
spring.mvc.formcontent.putfilter.enabled=true 
    # Servlet规范要求表格数据可用于HTTP POST而不是HTTP PUT或PATCH请求,这个选项将使得过滤器拦截
HTTP PUT和PATCH,且内容类型是application/x-www-form-urlencoded的请求,并且将其转换为POST请求
spring.mvc.ignore-default-model-on-redirect=true 
                         # 如果配置为default,那么它将忽略模型重定向的场景
spring.mvc.locale=                         # 默认国际化选项,默认取Accept-Language
spring.mvc.locale-resolver=accept-header   # 国际化解析器,如果需要固定可以使用fixed
spring.mvc.log-resolved-exception=false    # 是否启用警告日志异常解决
spring.mvc.message-codes-resolver-format=  # 消息代码的格式化策略。例如,' prefix_error_code '
spring.mvc.pathmatch.use-registered-suffix-pattern=false 
    # 是否对spring.mvc.contentnegotiation.media-types.*注册的扩展采用后缀模式匹配
spring.mvc.pathmatch.use-suffix-pattern=false  # 当匹配模式到请求时,是否使用后缀模式匹配(.*)
spring.mvc.servlet.load-on-startup=-1          # 启用Spring Web服务Serlvet的优先顺序配置
spring.mvc.static-path-pattern=/**             # 指定静态资源路径
spring.mvc.throw-exception-if-no-handler-found=false 
                         # 如果请求找不到处理器,是否抛出 NoHandlerFoundException异常
spring.mvc.view.prefix=                        # Spring MVC视图前缀
spring.mvc.view.suffix=                        # Spring MVC视图后缀

这些配置项将会被Spring Boot的机制读入,然后使用WebMvcAutoConfigurationAdapter去定制初始化。
对于这些选项,我们还可以实现接口WebMvcConfigurer,然后加入自己定义的方法即可,毕竟这个接口是Java 8的接口,其本身已经提供了default方法,对其定义的方法做了空实现。

四、Spring MVC实例

Spring MVC的开发核心是控制器的开发,控制器的开发又分为这么几个步骤,
首先是定义请求分发,让Spring MVC能够产生HandlerMapping,
其次是接收请求获取参数,
再次是处理业务逻辑获取数据模型,
最后是绑定视图和数据模型。

下面演示一个用户列表查询的界面。假设可以通过用户名称(userName)和备注(note)进行查询,但是一开始进入页面需要载入所有的数据展示给用户查看。
这里分为两种常见的场景,
一种是刚进入页面时,一般来说是不允许存在异步请求的,因为异步请求会造成数据的刷新,对用户不友好;
另一种是进入页面后的查询,这时可以考虑使用Ajax异步请求,只刷新数据而不刷新页面,这才是良好的UI体验设计。

1.用户控制器

@Controller
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService = null;   

    ......

    @RequestMapping("/table")
    public ModelAndView table() {
        // 访问模型层得到数据
        List userList = userService.findUsers(null, null);
        // 模型和视图
        ModelAndView mv = new ModelAndView();
        // 定义模型视图
        mv.setViewName("user/table");
        // 加入数据模型
        mv.addObject("userList", userList);
        // 返回模型和视图
        return mv;
    }

    @RequestMapping("/list")
    @ResponseBody
    public List list(
        @RequestParam(value = "userName", required = false) String userName, 
        @RequestParam(value = "note", required = false) String note) {
        // 访问模型层得到数据
        List userList = userService.findUsers(userName, note);
        return userList;
    }
}

开发控制器首先是指定请求分发,这个任务是交由注解@RequestMapping去完成的,这个注解可以标注类或者方法,当一个类被标注的时候,所有关于它的请求,都需要在@RequestMapping定义的URL下。
当方法被标注后,也可以定义部分URL,这样就能让请求的URL找到对应的路径。配置了扫描路径之后,Spring MVC扫描机制就可以将其扫描,并且装载为HandlerMapping,以备后面使用。

这里的控制器存在两个方法。
table:这个方法的任务是进入页面时,首先查询所有的用户,是一个没有条件的查询,当它查询出所有的用户数据后,创建模型和视图(ModeAndView),接着指定视图名称为“user/table”,
然后将查询到的用户列表捆绑到模型和视图中,最后返回模型和视图。

2.视图和视图渲染

视图/WEB-INF/jsp/user/table.jsp

<%@ page pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>




用户列表








    
用户名称: 备注 查询
编号 用户名称 备注
${user.id} ${user.userName} ${user.note}

这里使用了EasyUI以及它的控件DataGrid(数据网格),通过JSTL的forEach标签进行循环将控制器返回的用户列表渲染到这张JSP中,所以在刚刚进入页面的时候,就可以展示用户列表,
因为这里采用先取数据后渲染的方式,所以刚刚进入页面的时候并不会出现Ajax的异步请求,这样有助于提高UI(用户接口)体验。

页面中还定义了两个文本框,用来输入用户名和备注,然后通过查询按钮进行查询。
这里查询按钮的点击事件定义为onSearch,这样就能够找到onSearch函数来执行查询,在这个函数中,首先定义DataGrid请求的URL,它指向了list方法,然后通过jQuery去获取两个文本框的参数值,再通过DataGrid的load方法,传递参数去后端查询,得到数据后重新载入DataGrid的数据,这样DataGrid就能够得到查询的数据了。

list方法:
首先它标注为了@ResponseBody,这样Spring MVC就知道最终需要把返回的结果转换为JSON。
然后是获取参数,这里使用了注解@RequestParam,通过指定参数名称使得HTTP请求的参数和方法的参数进行绑定,只是这个注解的默认规则是参数不能为空。
为了克服这个问题,代码将其属性required设置为false即可,其意义就是允许参数为空。这样就可以测试这个请求了。

测试用户查询
点击查询按钮后,就会执行Ajax请求,把数据取回来,用来显示在DataGrid控件中。
这样就可以在互联网系统中,第一次进入一个新的页面,就可以无刷新地显示数据,而在查询等操作中使用Ajax,这样就有效地提高了用户的体验。

你可能感兴趣的:(spring,springboot,springmvc)