Spring MVC 工作机制

本文主要讲解Spring MVC 开发时应该了解的一些基本原理。利用Spring MVC我们一般最常见的有两种使用方式,一种是由view层的比如利用JSP / Velocity/Freemaker来做显示层渲染数据,另一种没有view层,直接返回类似Json/Xml格式的数据的Restful使用方式。下面先具体讲解下一下Spring MVC原理,然后在讲解下使用RestController注解实现Restful接口的原理。

普通Spring MVC模式

Spring MVC 流程

DispatcherServlet

可以说是一个servlet支撑了Spring MVC,这里有必要细讲一下DispatcherServlet。 输入图片说明

从上面的类图中可以看出DispatcherServlet本质上是一个普通的Servlet类,因此我们可以把需要使用Spring Mvc的请求的类全部映射到这个Servlet中。我们学习Servlet时都知道,Servlet通过doService来处理请求,这个doService参数是HttpServletRequest request/HttpServletResponse response这两个,开发web项目一定很熟悉了。Servlet容器(Tomcat/Jetty)帮我们实例化了这个两个参数,request负责封装请求的参数,response负责由服务器往客户端写数据。那么Spring MVC 无非也是利用这个两个参数来实现数据到服务端的读,然后在把数据通过response写到客户端。比如我们搭建Spring MVC项目时都会在web.xml中配置如下内容,其实和配置一个普通的servlet没有区别,除了多了和Spring Context交互的部分,从上面的类继承图中FrameworkServlet能拿到Spring Context来做IoC相关的工作:

<servlet>
   <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--指明配置文件的文件名,不使用默认配置文件名-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/dispatcher-servlet.xml</param-value>
           <!-- 这个主要是由其父类 FrameworkServlet 来处理,为servlet管理Spring Context相关的工作都是由它完成的-->
        </init-param>
        <load-on-startup>1</load-on-startup><!--启动顺序-->
</servlet>
<servlet-mapping>
       <servlet-name>DispatcherServlet</servlet-name>
       <url-pattern>/</url-pattern> <!--拦截URL带“/”的请求。-->
</servlet-mapping>

DispatcherServlet主要方法。

initStrategies(ApplicationContext context)

这个方法主要是初始化DispatcherServlet类需要的几块大内容,包括最主要的handlerMapping和viewResolver。

   //该方法主要是来初始化DispatcherServlet相关的内容的。
	protected void initStrategies(ApplicationContext context) {
        //这主要是填写上传文件时用哪个MultipartResolver,通常我们会配个CommonsMultipartResolver
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
    //在容器中拿到所有的Spring Context中的Controller ,放倒handlerMapping list中。
		initHandlerMappings(context);
		initHandlerAdapters(context);
    //异常处理配置
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
    // 配置显示层,比如我们是用jsp 还是velocity等等,默认用InternalResourceViewResolver,这个我们都都可以在配置文件里灵活的更改。
		initViewResolvers(context);
		initFlashMapManager(context);
	}
void doService(HttpServletRequest request, HttpServletResponse response)

该方法主要是给request添加一些属性,比如把applicationContext添加到其属性上,使其在后面处理过程,更容易拿到applicationContext, 也会添加LOCALE_RESOLVER_ATTRIBUTE,THEME_RESOLVER_ATTRIBUTE等等。这个方法最重要的一句代码是 doDispatch(request, response),起到具体分发请求,处理请求的作用,下个方法具体分析。

 //.....省落.......
// 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());
// .....省落.......
doDispatch(request, response);
// .....省落.......
doDispatch(request, response)

这个方法是Dispatcher最主要的方法:1 负责查找请求和handler(就是我们写的Controller)的映射,具体到该请求所对应的具体的Controller方法,handler对应的Spring beanFactory,该请求对应的拦截器等; 2 处理文件上传,及加入管理异步请求的逻辑。

doDispatch关键代码,下面是一个基本的请求处理流程

// Determine handler for the current request. 拿到对应的handler
mappedHandler = getHandler(processedRequest);

// 找到handler 对应的适配器,适配器的作用是起到对handler调用时做一些额外的事情
/*
默认的 SimpleControllerHandlerAdapter 是什么事情都没有做,直接触发handler方法,
这个HandlerAdapter接口设计主要是为了一些定制MVC workflow。This interface is not intended for application developers. It is available to handlers who want to develop their own web workflow.
*/
// 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.触发具体的controller逻辑,生成modelAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//设置view
applyDefaultViewName(processedRequest, mv);
//触发拦截器的 postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);

//处理result,下面单独分析
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

processDispatchResult 方法

这个方法主要是处理handler产生的结果或者异常,如果有异常则调用我们配置好的异常处理的resolver来处理,如果没有则调用具体的View来渲染handler产生的model。

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 (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			// Concurrent handling started during a forward
			return;
		}

		if (mappedHandler != null) {
			mappedHandler.triggerAfterCompletion(request, response, null);
		}

Spring MVC Restful模式

输入图片说明 由流程图我们可以查出Spring MVC Restful结构不需要view来渲染界面。下面我们看其实现原理。

开发Rest接口用到了RestController注解。从RestController这个注解继承中,看出它继承了Controller和ResponseBody两个注解,也就是说 如果我们在一个Controller类上加上@RestController 等价于 @Controller 和@ResponseBody这个两个注解。也就是Spring MVC对有注解 ResonseBody 的方法的返回值做了特殊处理,这里我们还经常用到RequestBody这个注解放到具体的参数bean前,这样会自动把前端提交的内容转换为我们想要的对象。

这里先简单理解下,因为Http消息协议都是字符串,也就是说Spring MVC把请求的字符串帮我们转换为java 对象,处理完逻辑后,在把java 对象转换为Json或者xml字符串写出去。这里有几个类必须提一下:

HttpInputMessage:这个Http消息流向服务端的一个接口抽象

HttpOutputMessage:Http消息写出的抽象接口

HttpMessageConverter :消息转换基本接口类

public interface HttpMessageConverter<T> {


	boolean canRead(Class<?> clazz, MediaType mediaType);


	boolean canWrite(Class<?> clazz, MediaType mediaType);

	List<MediaType> getSupportedMediaTypes();


	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;


	void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

知道大概有这么回事,那Spring MVC的DispatcherServlet是怎么把这些转换串起来的呢,下面我们接着看。

记得我们上面提过DispatcherServlet的initStrategies方法,里面讲到它帮我们做了很多初始化工作,包括初始化handler及handlerAdapter,我们一直没看到handlerAdapter起到什么大作用。其实我们的消息转换就是靠它帮我们织入的。在RequestMappingHandlerAdapter中我们可以看到messageConverters属性,它是一个HttpMessageConverter List,包含了我们需要的各种消息转换器。

我们断点调试时可以看到,在doDispatch根据handler取到对应handlerAdapter,里面包含了各种messageConverter。 输入图片说明

输入图片说明

那准备工作完了以后,这些messageConverts在何时何地被谁调用呢,继续跟下去 输入图片说明 RequestMappingHandlerAdapter中 输入图片说明

先封装一个invocableMethod出来,设置入参(methodArgument)和出参(methodReturnValue)的resolvers,接着看下面具体的触发过程

输入图片说明输入图片说明

上面的returenValueHandlers就是刚刚我们上面set的出参(methodReturnValue)的resolvers,接着下去,找到能处理我们Response注解的resolver类 RequestResponseBodyMethodProcessor

输入图片说明

RequestResponseBodyMethodProcessor 在其父类方法中遍历converts,找到匹配的messageConvert然后进行转换,然后写出。至此处理过程结束,即时走到DispatcherServlet中的processDispatchResult,由于view为空,所以不会做渲染的逻辑。

至此,SpringMVC 两种常见的用法分析完毕,从Spring MVC的dispatcher类的设计中,我们可以看到很多处理逻辑是灵活可配置的,比如当我们用不同的view显示就直接在xml中配对应的ViewResolver,例如常见的用JSP时,我们经常会像下边这样配置

	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
	</bean>

还有如果上传文件时,我们会配置multipartResolver,像下边这样

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />

还有上面我们讲到做消息转换用到的handlerAdapter和messageConvert,都可以灵活的配置的。Spring MVC 这种可以通过配置灵活扩展的设计思想值得我们借鉴。

https://my.oschina.net/robinyao/blog/795723

转载于:https://my.oschina.net/robinyao/blog/795723

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值