Spring MVC(一)框架设计及流程
SpringMVC框架设计
框架设计图如下
上图是SpringMVC框架运行的流程。处理请求先到达控制器(Controller),控制器的作用是进行请求转发,这样它会根据请求的内容去访问模型层(Model);在现今互联网体系中,数据主要从数据库和NoSQL中来,而且对于数据库而言往往还存在事务的机制,为了适应这样的变化,设计者会把模型层再细分为两层,即服务层(Service)和数据访问层(DAO);当控制器获取到由模型层返回的数据后,将数据渲染到视图中,这样就能够展示给用户了。
SpringMVC流程
流程图
流程和组件是SpringMVC的核心。
SpringMVC的流程都是围绕着DispatcherServlet
而工作的。严格来说,springMVC处理请求并非一定需要经过全流程,有时候一些流程并不存在,比如我们加入@ResponseBody
注解时,是没有经过视图解析器和视图渲染的,关于这个后面会讨论到。
首先,在Web服务器启动的过程中,如果在Spring Boot的机制下启用SpirngMVC,它就开始初始化一些重要组件,如DispactherServlert
、HandlerAdatper
的实现类RequestMappingHandlerAdatper
等对象组件。关于组件的初始化,我们在spring-webmvc-xxx.jar
包的属性文件DispatcherServler.properties
,它定义的对象都是在SpringMVC开始时就初始化,并且存放在SpringIoC容器中。其源码如下
DispatcherServler.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
这些组件会在SpringMVC得到初始化,所以我们并不需要太多的配置就能够开发SpringMVC程序。
普通视图
定义控制器
@RequestMapping(value = "/person")
@Controller
public class PersonController {
//注入用户服务类
@Autowired
PersonService personService;
//展示人员详情
@RequestMapping("/details")
public ModelAndView details(Long id) {
//访问模型层得到数据
Person perosn = personService.getPerson(id);
//模型和视图
ModelAndView mv = new ModelAndView();
//定义模型视图
mv.setViewName("person/details");
//加入数据模型
mv.addObject("person", perosn);
//返回视图
return mv;
}
}
@Controller
:表明这是一个控制器
@RequestMapping
:代表请求路径和控制器(或方法)的映射关系,它会在Web服务启动SpringMVC的时,就被扫描到HandlerMapping的机制中存储,之后再用户发起请求被DispatcherServlert
拦截后,通过URI和其他的条件,通过HandlerMapper
机制就能够找到对应的控制器或其方法进行响应。只是通过HandlerMapping返回的是一个HandlerExecutionChain
对象,源码如下
HandlerExecutionChain
源码
public class HandlerExecutionChain {
//日志
private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);
//处理器
private final Object handler;
//拦截器数组
@Nullable
private HandlerInterceptor[] interceptors;
//拦截器列表
@Nullable
private List<HandlerInterceptor> interceptorList;
//拦截器当前下标
private int interceptorIndex;
//....
}
HandlerExecutionChain
对象包含一个处理器(handler)。这里的处理器就是对控制器(controller)的包装。因为我们的控制器方法可能存在参数,那么处理器就可以读入http和上下文的相关参数,然后再传递给控制器方法。而在控制器执行完成返回后,处理器又可以通过配置信息和对控制器的返回结果进行处理。可以看出,处理器包含了控制器方法的逻辑,此外还有处理器的拦截器(interceptor),这样就能够通过拦截处理器进一步增强处理器的功能。
得到了处理器(handler)。还需要去运行,但是我们有普通的http请求,也有按BeanName的请求,设置是WebSocket的请求,所以它还需要一个适配器去运行HandlerExecutionChain对象包含的处理器,这就是HandlerAdapter
接口定义的实现类。从源码中可以看到SpringMVC中最常用的HandlerAdapter
的实现类,便是HttpRequestHandlerAdapter
。通过请求的类型,DispatcherServlet
就会找到它来执行Web请求的HandlerExecutionChain
对象包含的内容,这样就能够执行我们的处理器(handler)了。
在处理器调用控制器时,它首先通过模型层得到数据,再放入数据模型中,最后将返回模型和视图对象(ModelAndView),这里控制器设置的视图名称设置为"person/details"
这样就走到了视图解析器(ViewResolver),去解析视图逻辑名称了。
SpringMVC的ViewResolver
的实现类是InternalResourceViewResolver
,为了定制,我们可以在application.properties文件中进行配置
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.html
通过这样的配置,就能能在Spring Boot机制下定织InternalResouceViwResolver
这个视图解析器的初始化,也就是在返回视图名称之后,它会以前缀(prefix)和后缀(suffix)以及视图的名称组成全路径定位视图。例如,在控制器返回的"person/details"
,这里前缀为/
,等于spring boot默认路径"/src/main/resources/templates"
,那么它就会找到"/src/main/resources/templates/person.html"
作为视图。
视图解析器定位到视图后,视图的作用是将数据模型(Model)渲染,这样就能够响应数据的请求。这一步就是视图将数据模型渲染(View)出来,用来展示给用户看。
/person/details.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta charset="UTF-8">
<title>用户详情</title>
</head>
<body>
<center>
<table border="1">
<tr>
<td>标签</td>
<td>值</td>
</tr>
<tr>
<td>用户编号</td>
<td th:text="${person.id}"></td>
</tr>
<tr>
<td>用户名称</td>
<td th:text="${person.personName}"></td>
</tr>
<tr>
<td>用户备注</td>
<td th:text="${person.note}"></td>
</tr>
</table>
</center>
</body>
</html>
这里因为我们的控制器里绑定数据模型的时候,属性名称为person
,而属性为person
对象,所以就有了${person.id}
代表perosn对象的id属性,其余属性是一样的。
流程如下
JSON视图
有时候我们需要的只是JSON数据集,因为目前前后台分离的趋势,使用JSON已经是主流的方式。正如@ResponseBody
注解表明方法一样,springMVC会把数据转换为JSON数据集,但是这里不谈@ResponseBody
,因为它会采用处理器内部的机制。这里先用MappingJackson2JsonView
转换出JSON。
//使用json视图
@RequestMapping("/detailsForJson")
public ModelAndView detailsForJson(Long id) {
Person person = personService.getPerson(id);
ModelAndView mv = new ModelAndView();
MappingJackson2JsonView jsonView = new MappingJackson2JsonView();
mv.setView(jsonView);
mv.addObject("person", person);
return mv;
}
其流程如下
流程中我们可以看到并没有视图解析器,是因为MappingJackson2JsonView
是一个非逻辑视图。它不需要视图解析器进行定位,它的作用只是将数据模型渲染为JSON数据集来响应请求。