首页 头条正文

沧州驾校:萌新学习SpringMVC

欧博网址 头条 2020-05-20 15:05:32 23 0

前言

只有秃顶才气变强。

文本已收录至我的GitHub精选文章,迎接Star:https://github.com/ZhongFuCheng3y/3y

这篇SpringMVC被催了很久了,这阵子由于做整合系统的事,以是异常异常地忙。这周末早早就回了公司肝这篇文章了。

若是关注三歪的同砚会发现,三歪最近写的许多文章都是连系了现有的系统去写的。这些问题都是真实开发场景会遇到的、用的上的,这些案例对未事情的同砚辅助应该照样蛮大的。

不多BB了,照样进入今天的正题吧「SpringMVC

先简朴聊聊SpringMVC

若是你们玩知乎,很可能会看到我的身影。我经常会去知乎水回覆。在知乎有许多初学者都市问的一个问题:「我学习SpringMVC需要什么样的基础

我一定会让他们先学Servlet,再学SpringMVC的。虽然说我们在现实开发中几乎不会写原生Servlet的代码了,但我始终认为学完Servlet再学SpringMVC,对明白SpringMVC是有利益的。

三歪题外话:我那时在学SpringMVC之前实在已经接触过另外一个web框架(固然了Servlet也是学了的),那就是「赫赫有名」的Struts2。只要是Struts2有的功效,SpringMVC都市有。

那时初学Struts2的时刻用的是XML设置的方式去开发的,再转到SpringMVC注解的时刻,以为SpringMVC真香。

Struts2在2020年已经不用学了,学SpringMVC的基础是Servlet,只要Servlet基础还行,上手SpringMVC应该不成问题。

从Servlet到SpringMVC,你会发现SpringMVC帮我们做了许多的器械,我们的代码一定是没以前多了。

Servlet:

我们以前可能需要将通报进来的参数手动封装成一个Bean,然后继续往下传:

SpringMVC:

现在SpringMVC自动帮我们将参数封装成一个Bean

Servlet:

以前我们要导入其他的jar包去手动处置文件上传的细节:

SpringMVC:

现在SpringMVC上传文件用一个MultipartFile工具都给我们封装好了

........

说白了,在Servlet时期我们这些活都醒目,只不过SpringMVC把许多器械都给屏障了,于是我们用起来就加倍舒心了。

在学习SpringMVC的时刻现实上也是学习这些功效是怎么用的而已,并不会太难。这次整理的SpringMVC电子书实在也是在讲SpringMVC是若何使用的

  • 比如说通报一个日期字符串来,SpringMVC默认是不能转成日期的,那我们可以怎么做来实现。
  • SpringMVC的文件上传是怎么使用的
  • SpringMVC的阻挡器是怎么使用的
  • SpringMVC是怎么对参数绑定的
  • ......

现在「电子书」已经放出来了,然则别急,重头戏在后面。显然,通过上面的电子书是可以知道SpringMVC是怎么用的

然则这在面试的时刻人家是不会问你SpringMVC的一些用法的,而SpringMVC面试问得最多的就是:SpringMVC请求处置的流程是怎么样的

实在也很简朴,流程就是下面这张图:

再简化一点,可以发现流程不庞大

在面试的时刻甚至能一句话就讲完了,但这够吗,这是面试官想要的吗?那一定不是。那我们想知道SpringMVC是做了什么吗?想的吧(不管你们想不想,横竖三歪想看)。

由于想要主流程加倍清晰一点,我会在源码添加部门注释以及删减部门的代码

以@ResponseBody和@RequestBody的Controller代码解说为主,这是线上环境用得最多的

DispatcherServlet源码

首先我们看看DispatcherServlet的类结构,可以清楚地发现现实DispatcherServlet就是Servlet接口的一个子类(这也就是为什么网上这么多人说DispatcherServlet的原理现实上就是Servlet)

我们在DispatcherServlet类上可以看到许多熟悉的成员变量(组件),以是看下来,我们要的器械,DispatcherServlet可全都有

// 文件处置器
private MultipartResolver multipartResolver;

// 映射器
private List<HandlerMapping> handlerMappings;

// 适配器
private List<HandlerAdapter> handlerAdapters;

// 异常处置器
private List<HandlerExceptionResolver> handlerExceptionResolvers;

// 视图解析器
private List<ViewResolver> viewResolvers;

然后我们会发现它们在initStrategies()上初始化:

protected void initStrategies(ApplicationContext context) {
  initMultipartResolver(context);
  initLocaleResolver(context);
  initThemeResolver(context);
  initHandlerMappings(context);
  initHandlerAdapters(context);
  initHandlerExceptionResolvers(context);
  initRequestToViewNameTranslator(context);
  initViewResolvers(context);
  initFlashMapManager(context);
}

请求进到DispatcherServlet,实在所有都市打到doService()方式上。我们看看这个doService()方式做了啥:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		
		// 设置一些上下文...(省略一大部门)
		request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
		request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

		try {
      // 挪用doDispatch
			doDispatch(request, response);
		}
		finally {
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
		}
	}

以是请求会走到doDispatch(request, response);里边,我们再进去看看:

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);

         // 找到HandlerExecutionChain
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null || mappedHandler.getHandler() == null) {
            noHandlerFound(processedRequest, response);
            return;
         }
         // 获得对应的hanlder适配器
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // 阻挡前置处置
         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }

         // 真实处置请求
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

         // 视图解析器处置
         applyDefaultViewName(processedRequest, mv);
        
         // 阻挡后置处置
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
         dispatchException = ex;
      }
   }
}

这里的流程跟我们上面的图的流程几乎是一致的了。我们从源码可以知道的是,原来SpringMVC的阻挡器是在MappingHandler的时刻一齐返回的,返回的是一个HandlerExecutionChain工具。这个工具也不难,我们看看:

public class HandlerExecutionChain {

	private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);

  // 真实的handler
	private final Object handler;

  // 阻挡器List
	private HandlerInterceptor[] interceptors;
	private List<HandlerInterceptor> interceptorList;

	private int interceptorIndex = -1;
}

OK,整体的流程我们是已经看完了,顺便要不我们去看看它是怎么找到handler的?三歪带着你们冲!我们点进去getHandler()后,发现它就把默认实现的Handler遍历一遍,然后选出合适的:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	// 遍历一遍默认的Handler实例,选出合适的就返回
  for (HandlerMapping hm : this.handlerMappings) {
    HandlerExecutionChain handler = hm.getHandler(request);
    if (handler != null) {
      return handler;
    }
  }
  return null;
}

再进去getHandler里边看看呗,里边又有几层,我们最后可以看到它凭据路径去匹配,走到了lookupHandlerMethod这么一个方式

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		List<Match> matches = new ArrayList<Match>();
  	// 获取路径
		List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);

  	// 对匹配的排序,找到最佳匹配的
		if (!matches.isEmpty()) {
			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
			Collections.sort(matches, comparator);
			if (logger.isTraceEnabled()) {
				logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
						lookupPath + "] : " + matches);
			}
			Match bestMatch = matches.get(0);
			if (matches.size() > 1) {
				if (CorsUtils.isPreFlightRequest(request)) {
					return PREFLIGHT_AMBIGUOUS_MATCH;
				}
				Match secondBestMatch = matches.get(1);
				if (comparator.compare(bestMatch, secondBestMatch) == 0) {
					Method m1 = bestMatch.handlerMethod.getMethod();
					Method m2 = secondBestMatch.handlerMethod.getMethod();
					throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
							request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
				}
			}
			handleMatch(bestMatch.mapping, lookupPath, request);
			return bestMatch.handlerMethod;
		}
		else {
			return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
		}
	}

找阻挡器也许也是上面的一个历程,于是我们就可以顺遂拿到HandlerExecutionChain了,找到HandlerExecutionChain后,我们是先去拿对应的HandlerAdaptor。我们也去看看里边做了什么:

// 遍历HandlerAdapter实例,找到个合适的返回
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		for (HandlerAdapter ha : this.handlerAdapters) {
			if (ha.supports(handler)) {
				return ha;
			}
		}
	}

我们看一个常用HandlerAdapter实例RequestMappingHandlerAdapter,会发现他会初始化许多的参数解析器,实在我们经常用的@ResponseBody解析器就被内置在里边:

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();

		resolvers.add(new MatrixVariableMethodArgumentResolver());
		resolvers.add(new MatrixVariableMapMethodArgumentResolver());
		resolvers.add(new ServletModelAttributeMethodProcessor(false));
  	// ResponseBody Requestbody解析器
		resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), t
		// 等等
		return resolvers;
	}

获得HandlerAdaptor后,随之而行的就是阻挡器的前置处置,然后就是真实的mv = ha.handle(processedRequest, response, mappedHandler.getHandler())

这里边嵌套了好几层,我就不逐一贴代码了,我们会进入ServletInvocableHandlerMethod#invokeAndHandle方式,我们看一下这里边做了什么:

public void invokeAndHandle(ServletWebRequest webRequest,
			ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

  	// 处置请求
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
		setResponseStatus(webRequest);

		if (returnValue == null) {
			if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
				mavContainer.setRequestHandled(true);
				return;
			}
		}
  	//.. 

		mavContainer.setRequestHandled(false);
		try {
      // 处置返回值
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
	}

处置请求的方式我们进去看看invokeForRequest

public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		
  	// 获得参数
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		
  	// 挪用方式
		Object returnValue = doInvoke(args);
		if (logger.isTraceEnabled()) {
			logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
		}
		return returnValue;
	}

我们看看它是怎么处置参数的,getMethodArgumentValues方式进去看看:

private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

  	// 获得参数
		MethodParameter[] parameters = getMethodParameters();
		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
			MethodParameter parameter = parameters[i];
			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
			GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
			args[i] = resolveProvidedArgument(parameter, providedArgs);
			if (args[i] != null) {
				continue;
			}
      // 找到适配的参数解析器
			if (this.argumentResolvers.supportsParameter(parameter)) {
				try {
					args[i] = this.argumentResolvers.resolveArgument(
							parameter, mavContainer, request, this.dataBinderFactory);
					continue;
				}
				//.....
		}
		return args;
	}

这些参数解析器现实上在HandlerAdaptor内置的那些,这里欠好放代码,以是我截个图吧:

针对于RequestResponseBodyMethodProcessor解析器我们看看里边做了什么:

public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

    // 通过Converters对参数转换
		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
		String name = Conventions.getVariableNameForParameter(parameter);

		WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
		// ...
		mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());

		return arg;
	}

再进去readWithMessageConverters里边看看:

protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter param,
			Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

		// ...处置请求头

		try {
			inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage);

      // HttpMessageConverter实例去对参数转换
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
				if (converter instanceof GenericHttpMessageConverter) {
					GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter;
					if (genericConverter.canRead(targetType, contextClass, contentType)) {
						if (logger.isDebugEnabled()) {
							logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
						}
						if (inputMessage.getBody() != null) {
							inputMessage = getAdvice().beforeBodyRead(inputMessage, param, targetType, converterType);
							body = genericConverter.read(targetType, contextClass, inputMessage);
							body = getAdvice().afterBodyRead(body, inputMessage, param, targetType, converterType);
						}
						else {
							body = null;
							body = getAdvice().handleEmptyBody(body, inputMessage, param, targetType, converterType);
						}
						break;
					}
				}
				//...种种判断
		

		return body;
	}

看到这里,有没有看不懂,想要退出的感受了??别慌,三歪带你们看看这份熟悉的设置:

<!-- 启动JSON返回花样 -->
	<bean	class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
		<property name="messageConverters">
			<list>
				<ref bean="jacksonMessageConverter" />
			</list>
		</property>
	</bean>
	<bean id="jacksonMessageConverter"
		class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
		<property name="supportedMediaTypes">
			<list>
				<value>text/html;charset=UTF-8</value>
				<value>application/json;charset=UTF-8</value>
				<value>application/x-www-form-urlencoded;charset=UTF-8</value>
			</list>
		</property>
		<property name="objectMapper" ref="jacksonObjectMapper" />
	</bean>
	<bean id="jacksonObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />

我们在SpringMVC想要使用@ResponseBody返回JSON花样都市在设置文件上设置上面的设置,RequestMappingHandlerAdapter这个适配器就是上面所说的谁人,内置了RequestResponseBodyMethodProcessor解析器,然后MappingJackson2HttpMessageConverter现实上就是HttpMessageConverter接口的实例

然后在返回的时刻也经由HttpMessageConverter去将参数转换后,写给HTTP响应报文。转换的流程大致如图所示:

视图解析器后面就不贴了,也许的流程就如上面的源码,我再画个图来加深一下明白吧:

最后

SpringMVC我们使用的时刻异常简捷,在内部现实上帮我们做了许多(有种种的HandlerAdaptor),SpringMVC的请求流程面试的时刻照样面得许多的,照样可以看看源码它帮我们做了什么,过一遍可能会发现自己能看懂以前的设置了。

参考资料:

  • https://www.cnblogs.com/java-chen-hao/category/1503579.html
  • https://www.jianshu.com/p/1bff57c74037
  • https://stackoverflow.com/questions/18682486/why-does-spring-mvc-need-at-least-two-contexts

各种知识点总结

  • 92页的Mybatis
  • 129页的多线程
  • 141页的Servlet
  • 158页的JSP
  • 76页的聚集
  • 64页的JDBC
  • 105页的数据结构和算法
  • 142页的Spring
  • 58页的过滤器和监听器
  • 30页的HTTP
  • Hibernate
  • AJAX
  • Redis
  • ......

涵盖Java后端所有知识点的开源项目(已有8K+ star):

  • GitHub
  • Gitee接见更快

若是人人想要实时关注我更新的文章以及分享的干货的话,微信搜索Java3y

,

诚信在线手机版

诚信在线(现:阳光在线官网)现已开放诚信在线手机版、诚信在线电脑客户端下载。诚信在线娱乐游戏公平、公开、公正,用实力赢取信誉。

版权声明

本文仅代表作者观点,
不代表本站欧博网址的立场。
本文系作者授权发表,未经许可,不得转载。