Spring MVC 简学

什么是spring MVC?

Spring Web MVC 是一种基于 Java 的实现了 Web MVC 设计模式的请求驱动类型的轻量级 Web 框架,即使用了 MVC 架 构模式的思想,将 web 层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,目的是帮助我们简化web应用开发。

M:modle 就是模型层,负责数据存储和数据处理,其中模型指的是Javabean,JavaBean现在分为两类:专门存储数据的是实体类bean(entity)或者是数据beans(@data),另一类为数据处理bean,指的是service和dao对象,其中service用于处理业务逻辑,dao用于底层数据库访问数据。(持久化)


V: 视图层,指的是工程中的html或者是jsp界面,不混杂业务处理操作,纯净的web界面,将数据展示与数据处理进行解耦。


C:控制层,就是springmvc的控制器,控制什么呢,控制请求该分发给哪个处理器处理,接收请求,返回响应,起到一个调度的作用,不涉及业务逻辑的操作。


MVC的工作流程:用户通过view层,发送请求到服务器,请求被control层(web访问的入口)接收,调用对应的业务处理方法(service),业务处理器通过dao层(数据访问)操作数据,处理完毕后,将结果返回给control层,control层根据结果找到相应的view视图,渲染数据后最终响应给浏览器。

注解介绍:

@RequestMapping注解
见名知意,就是请求路径和请求方法的映射,联系。路径-->方法。
在类上面标注:设置映射请求的请求路径的初始信息,例如对于一个测试类而言,可能包括多个测试方法,那么初始路径就是/test-->test.class 
在方法上标注:设置映射请求请求路径的具体信息。例如/test/method1-->test.method1,/test/method2-->test.method2 
注意:控制层上面除了@RequestMapping注解,还需要添加@Controller注解
此外@RequestMapping注解支持路径占位符,这是rest风格的一种体现,与原始方式相比:

原始:/deleteUsers?id=1

rest:/deleteUsers/1

当请求路径中将某些数据通过路径的方式传输到服 务器中,就可以在相应的@RequestMapping注解的value属性中通过占位符{xxx}表示传输的数据,再通过@PathVariable注解,将占位符所表示的数据赋值给控制器方法的形参。

@RequestMapping注解有三个属性:
@value属性:是一个字符串类型的数组,表示该方法或者该类能匹配多个请求,例如/test,/mytest等
@method属性:可接受的请求方法,目前浏览器仅支持post和get,同样也是一个字符串类型的数组。
@param属性 和@header属性,对请求添加一些限定条件,如必须携带什么参数,或者请求带不带请求头等

请求参数的获取

1. 通过servlet API获取:将HttpServletRequest作为控制器方法的形参,该参数封装了当前请求的请求报文对象。

@RequestMapping("/testParam")
public String testParam(HttpServletRequest request){
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username:"+username+",password:"+password);
return "success";
}

2.直接通过控制器的形参获取请求参数:设置和请求参数同名的形参,前端控制器会将请求参数赋值给相应的形参。

@RequestMapping("/testParam")
public String testParam(String username, String password){
System.out.println("username:"+username+",password:"+password);
return "success";
}

注:对于请求参数中存在多个同名请求参数的情况下,有两种情况。

        1.在控制器将形参设置为字符串数组,数组包含每一个请求参数。

        2.在控制器将形参设置为字符串类型,值为每个数据中间使用逗号拼接的结果。

3.通过@RequestParam注解,创建请求参数和控制器方法形参的映射关系。

@RequestParam注解:一共有三个属性 其中最重要的是value属性
value:指定为形参赋值的请求参数的参数名,一般参数名与形参名对应。
required:设置是否必须传输此请求参数,默认值为true
若设置为true时,则当前请求必须传输value所指定的请求参数,若没有传输该请求参数,且没有设置defaultValue属性,报错;若设置为false,为非必传参数;若没有传输,则注解所标识的形参的值为null。
defaultValue:不管required属性值为true或false,当value所指定的请求参数没有传输或传输的值
为""时,则使用默认值为形参赋值

4.通过POJO获取请求参数:

在控制器方法设置一个实体类型的形参,如果请求参数的参数名,与实体对象里的属性名匹配,那么会将该请求参数的值赋给此属性。

<form th:action="@{/testpojo}" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
性别:<input type="radio" name="sex" value="男">男<input type="radio"
name="sex" value="女">女<br>
年龄:<input type="text" name="age"><br>
邮箱:<input type="text" name="email"><br>
<input type="submit">
</form>
@RequestMapping("/testpojo")
public String testPOJO(User user){
System.out.println(user);
return "success";
}
//最终结果-->User{id=null, username='张三', password='123', age=23, sex='男',
email='123@qq.com'}

HttpMessageConverter

报文信息转换器,作用是将请求报文转换为Java对象,或者将Java对象转换为响应报文。

如何实现:
@RequestBody
该注解可以获取httpservlet request请求中的请求数据,并以字符串形式返回。

<form th:action="@{/testRequestBody}" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit">
</form>
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String requestBody){
System.out.println("requestBody:"+requestBody);
return "success";
}


//输出结果:requestBody:username=admin&password=123456

RequestEntity

封装请求报文的一种类型,在控制器方法中设置该类型的形参,请求报文就会赋值给该形参,可以通过getHeaders()获取请求头信息,通过getBody()获取请求体信息。

@RequestMapping("/testRequestEntity")
public String testRequestEntity(RequestEntity<String> requestEntity){
System.out.println("requestHeader:"+requestEntity.getHeaders());
System.out.println("requestBody:"+requestEntity.getBody());
return "success";
}

@ResponseBody
用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器。

注意该标识是标注在方法上的。

@RequestMapping("/testResponseBody")
@ResponseBody
public String testResponseBody(){
return "success";
}

ResponseEntity:

用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文。

在Controller中或者用于服务端响应时,作用是和@ResponseStatus与@ResponseBody结合起来的功能一样的。一般而言使用@ResponseBody即可。

@RestController注解
标识在控制器的类上,相当于为类添加@Controller,以及为每个方法添加了@ResponseBody。

完全注解开发(尤其体现在spring boot)
使用配置类和注解代替web.xml和SpringMVC配置文件的功能
配置类放在config包里,类上面添加@configration注解

springmvc中的视图层 

视图的作用是渲染数据,将model层处理后的数据结果展示给用户。视图技术thyme leaf View。

springMVC执行流程

1.核心组件

DispatcherServlet:前端控制器,由框架提供。
作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求
HandlerMapping:处理器映射器,不需要工程师开发,由框架提供。
作用:根据请求的url、method等信息查找Handler,即控制器方法
Handler:处理器,需要工程师开发。
作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理
HandlerAdapter:处理器适配器,由框架提供。
作用:通过HandlerAdapter对处理器(控制器方法)进行执行
ViewResolver:视图解析器,由框架提供。
作用:进行视图解析,得到相应的视图,例:ThymeleafView,InternalResourceView,RedirectView
View:视图
作用:将模型数据通过页面展示给用户

spring MVC处理设配器补充:调用具体的方法对用户发来的请求来进行处理。

当 handlerMapping 获取到执行请求的controller时,DispatcherServlte 会根据对应的 controller 类型来调用相应的 HandlerAdapter 来进行处理。

1.HandleAdapter的注册

DispatcherServlte 会根据配置文件信息注册 HandlerAdapter,如果在配置文件中没有配置,那么 DispatcherServlte 会获取 HandlerAdapter 的默认配置,如果是读取默认配置的话,DispatcherServlte 会读取 DispatcherServlte.properties 文件, 该文件中配置了三种 HandlerAdapter:HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter 和 AnnotationMethodHandlerAdapter。DispatcherServlte 会将这三个 HandlerAdapter 对象存储到它的 handlerAdapters 这个集合属性中,这样就完成了 HandlerAdapter 的注册。

2.HandleAdapter的执行

结合源码来加深理解

public interface HandlerAdapter {
	boolean supports(Object handler);
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
	long getLastModified(HttpServletRequest request, Object handler);
}

可以看到该接口有三个方法。

        1.通过调用supports()方法,根据handleMapping传过来的handle类型与已经注册的适配器HandelAdapter(实现接口的子类)进行适配,返回true时,匹配成功。

        2.匹配成功后,对应的handleAdapter会调用自身的handle()方法, handle 方法运用 java 的反射机制执行处理器具体方法来获得 ModelAndView。

几种常见的适配器:

  1. AnnotationMethodHandlerAdapter 主要是适配注解类处理器,注解类处理器就是我们经常使用的 @Controller 的这类处理器
  2. HttpRequestHandlerAdapter 主要是适配静态资源处理器,静态资源处理器就是实现了 HttpRequestHandler 接口的处理器,这类处理器的作用是处理通过 SpringMVC 来访问的静态资源的请求
  3. SimpleControllerHandlerAdapter 是 Controller 处理适配器,适配实现了 Controller 接口或 Controller 接口子类的处理器,比如我们经常自己写的 Controller 来继承 MultiActionController.

2.DispatcherServlet初始化过程

DispatcherServlet 本质上是一个 Servlet,遵循 Servlet 的生命周期。

2.1:初始化WebApplicationContext

2.2创建WebApplicationContext

2.3DispatcherServlet初始化策略:

FrameworkServlet创建WebApplicationContext后,刷新容器,调用onRefresh(wac),此方法在 DispatcherServlet中进行了重写,调用了initStrategies(context)方法,初始化策略,即初始化 DispatcherServlet的各个组件。

public class DispatcherServlet extends FrameworkServlet {
 //实现子类的onRefresh()方法,该方法委托为initStrategies()方法。
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
//初始化默认的Spring Web MVC框架使用的策略(如HandlerMapping)
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
}

3.DispatcherServlet调用组件处理请求

 3.1调用processRequest(request, response)

FrameworkServlet重写HttpServlet中的service()和doXxx(),这些方法中调用了 processRequest(request, response)

//frameworkServlet里的processRequest方法。
protected final void processRequest(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext =
LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes =
RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request,
response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(),
new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
// 执行服务,doService()是一个抽象方法,在DispatcherServlet中进行了重写
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}

3.2调用doService(DispatcherServlet进行了重写

@Override
protected void doService(HttpServletRequest request, HttpServletResponse
response) throws Exception {
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude ||
attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName,
request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE,
getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request,
response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE,
Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
RequestPath requestPath = null;
if (this.parseRequestPath &&
!ServletRequestPathUtils.hasParsedRequestPath(request)) {
requestPath = ServletRequestPathUtils.parseAndCache(request);
}
try {
// 处理请求和响应
doDispatch(request, response);
}
finally {
if
(!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
if (requestPath != null) {
ServletRequestPathUtils.clearParsedRequestPath(request);
}
}

3.3调用doDispatch(所在类:org.springframework.web.servlet.DispatcherServlet)

如果存在拦截器的话,这里执行了拦截器的prehandle和posthandle两个方法。

在进入控制层之前(调用控制器方法之前),执行拦截器的preHandle()方法,随后由处理设配器调用具体的处理器方法,返回一个可渲染的modleandview对象,以供视图层渲染;

在进入视图层之前,调用拦截器的posthandle()方法,进行处理。处理后调用processDispatchResult()方法。如果存在多个拦截器,逆序调用posthandle()

protected void doDispatch(HttpServletRequest request, HttpServletResponse
response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
/*
mappedHandler:调用链
包含handler、interceptorList、interceptorIndex
handler:浏览器发送的请求所匹配的控制器方法
interceptorList:处理控制器方法的所有拦截器集合
interceptorIndex:拦截器索引,控制拦截器afterCompletion()的执行
*/
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 通过控制器方法创建相应的处理器适配器,调用所对应的控制器方法
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request,
mappedHandler.getHandler());
if (new ServletWebRequest(request,
response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 调用拦截器的preHandle()
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 由处理器适配器调用具体的控制器方法,最终获得ModelAndView对象
mv = ha.handle(processedRequest, response,
mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 调用拦截器的postHandle()
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as
well,
// making them available for @ExceptionHandler methods and other
scenarios.
dispatchException = new NestedServletException("Handler dispatch
failed", err);
}
// 后续处理:处理模型数据和渲染视图
processDispatchResult(processedRequest, response, mappedHandler, mv,
dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing
failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}

3.4调用processDispatchResult() :处理模型数据和渲染视图                                         

private void processDispatch(HttpServletRequest request,HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws
Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered",
exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler()
: null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
// 处理模型数据和渲染视图
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
// 调用拦截器的afterCompletion()
mappedHandler.triggerAfterCompletion(request, response, null);
}
}

等所有数据处理和视图渲染完成后,逆向调用拦截器的afterCompletion()方法,返回响应。

3.SpringMvc执行流程

1) 用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获。
2) DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:
        a) 不存在
        i. 再判断是否配置了mvc:default-servlet-handler
        ii. 如果没配置,则控制台报映射查找不到,客户端展示404错误
        iii. 如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML)
        b) 存在则执行下面的流程
3) 根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。
4) DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。
5) 如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法【正向】
6) 提取Request中的模型数据,填充Handler入参,执行Handler(Controller)方法,处理请求。
在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
        a) HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息。
        b) 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
        c) 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
        d) 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
7) Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象。
8) 此时将开始执行拦截器的postHandle(...)方法【逆向】。
9) 根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行
HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。
10) 渲染视图完毕执行拦截器的afterCompletion(…)方法【逆向】。
11) 将渲染结果返回给客户端。 

Spring MVC 与 Spring boot

实质上,spring boot 就是为了简化开发过程,让开发人员更加专注于业务逻辑的实现。

spring mvc 通过Dispatcher Servlet, ModelAndView 和 View Resolver,开发web应用变得很容易。解决的问题领域是网站应用程序或者服务开发——URL路由、Session、模板引擎、静态Web资源等等。

Spring Boot实现了自动配置,例如前端控制器,适配器的配置,在spring boot 都自动装配好了,从而降低了项目搭建的复杂度。但是功能的实现还是依赖于spring mvc。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值