springMVC源码解析

从17年接触java,到现在,算起来已经差不多4年多的时间了。之前主要做C语言相关的开发,对于Java可以说是从入门开始学习的,具体的辛酸历程不表。

关于springmvc的内容、讨论已经相当多了,本文想站在最直接、最接近源码的角度,进行分析,既是对自我知识的一个梳理,也是想着将学习源码的技巧、经验分享给大家,让更多的人少走弯路,在代码的能力上更上一个台阶。
工程很简单,结构如下图:
工程结构
启动类
文件

具体的代码会放到github上。
运行效果:
运行效果

controller是如何被一步步调用到的?

调用路线图
通过在controller方法代码上加个断点,调试启动,就可以看到调用的路线图。大体可以看到:DispatcherServlet是入口,然后通过RequestMappingHandlerAdapter#handle()处理,ServletInvocableHandlerMethod#invokeAndHandle(),最后就是Method.invoke()反射调用了。就从这几步进行分析:
1.RequestMappingHandlerAdapter是个HandlerAdapter接口的实现类,HandlerAdapter的实现类有如下几个:①RequestMappingHandlerAdapter 使用RequestMapping标记controller方法的方式 ,他支持的handler是HandlerMethod(在扫描controller的时候创建的,后面会讲到)②AnnotationMethodHandlerAdapter 已被弃用,同1功能相同 ③HttpRequestHandlerAdapter 他负责调起HttpRequestHandler这样的handler,一般用在基于HTTP的rpc调用上,可以看到HttpRequestHandler的实现类诸如:HessianServiceExporter、HttpInvokerServiceExporter,还包括静态资源访问的ResourceHttpRequestHandler;比如使用HessianServiceExporter,就可以定义个BeanNameUrlHandlerMapping,添加一个bean,要求beanName为/斜线开头(做url用),他自己就是个 handler。④SimpleControllerHandlerAdapter 当handler实现的是Controller接口(不是注解) ⑤SimpleServletHandlerAdapter 当handler自身是个Servlet。
这么多实现,并不都是说让开发者直接使用的,因为自动构建的RequestMappingHandlerAdapter已经足够使用了。有很多实现类,是框架为了支持更多的情况而设计的。
2.看一下RequestMappingHandlerAdapter到底干了啥?
AbstractHandlerMethodAdapter是他的父类,父类中实现了HandlerAdapter的接口方法:handle(HttpServletRequest, HttpServletResponse, Object); RequestMappingHandlerAdapter重写了其handleInternal()方法(抽象模板设计模式),经历以下几个步骤:checkRequest() 是session会话的检查,invokeHandlerMethod()调用目标方法,prepareResponse()准备返回。
3.重要逻辑可以看出都在invokeHandlerMethod()方法中,经历以下几个步骤:包装请求响应为ServletWebRequest对象,获取binderFactory,modelFactory,createInvocableHandlerMethod()创建可反射调用的对象,创建mavContainer对象,调用刚构建出来的invocableMethod对象,最后getModelAndView()返回(对于ResponseBody来说view是null,返回的内容是直接写入HttpResponse的,具体下面会说到)。
4.ServletInvocableHandlerMethod#invokeAndHandle()方法,经历以下几个步骤:invokeForRequest()调用目标方法,setResponseStatus()设置http状态(是针对@ResponseStatus注解的处理),this.returnValueHandlers.handleReturnValue()对返回结果的处理。除了setResponseStatus()方法外,其他两个方法都很重要,里面有更多的处理逻辑。
5.ServletInvocableHandlerMethod#invokeForRequest()方法,首先通过getMethodArgumentValues()获取对应参数,具体是通过HandlerMethodArgumentResolver#resolveArgument()实现的(包括@RequestParam @PathVariable @RequestBody等等,有一系列解析类,暂不展开讨论了)。doInvoke()完成反射调用。
6.ServletInvocableHandlerMethod.returnValueHandlers.handleReturnValue()对响应结果的处理,是由HandlerMethodReturnValueHandlerComposite组合handler实现的,这里又引入了一个接口HandlerMethodReturnValueHandler,对于不同的返回值class类型,有不同的处理方式,例如HttpEntity、@RequestBody、Map、HttpHeaders、DeferredResult、ModelAndView、@ModelAttribute、Model、View等。对于方法上加了@ResponseBody注解的,会选择RequestResponseBodyMethodProcessor这个返回值处理类。处理的方法如下
7.调用writeWithMessageConverters()方法,这个方法是父类中AbstractMessageConverterMethodProcessor中定义的,这里面调用了合适的HttpMessageConverter来将返回结果写入HttpResponse。具体有哪些http消息转换器,其实是和http返回的media-type对应的,比如text/plain =>StringHttpMessageConverter,application/octet-stream => ByteArrayHttpMessageConverter,/ => ResourceHttpMessageConverter,ByteArrayHttpMessageConverter,application/x-www-form-urlencoded=>AllEncompassingFormHttpMessageConverter,application/json+=>MappingJackson2HttpMessageConverter。每一种可能还对返回值有限制,如ByteArrayHttpMessageConverter只能处理返回值为byte[],StringHttpMessageConverter只能处理返回值类型为String的。选定合适的Converter后,先调用getAdvice().beforeBodyWrite(),再调用genericConverter.write()执行真正的返回对象写入httpResponse逻辑。
8.getAdvice().beforeBodyWrite(),调用的就是我们的加了@ControllerAdvice注解的ResponseBodyAdvice拦截器实现类,让我们在即将写入http消息之前可以做一些感兴趣的修改。
9.如这个helloworld示例工程而言,值是个String,所以匹配的是text/html类型,converter是StringHttpMessageConverter,关键方法为writeInternal(),方法体为StreamUtils.copy()通过流拷贝的方式将字符流转为字节流写入HttpResponse。
10.主要执行逻辑如上所述,有很多地方都可以再次展开讨论的,而且篇幅很大。如第5点getMethodArgumentValues()获取controller方法的入参,这块也有相当多的相关处理类。可以根据需要断点跟踪查看。

url如何映射到Controller方法的?

由前文可知,由url得到对应的Handler,是通过HandlerMapping得到的。具体的逻辑就需要查看HandlerMapping的初始化过程了。这里我们重点关注RequestMappingHandlerMapping。因为这一些都发生在DispatcherServlet的doDispatch()方法中,我们自然要看他里面的handlerMappings是如何初始化的,经过简单查找,可以看到有个方法#initHandlerMappings()做了初始化,题外话:这个方法是重载了FrameworkServlet的onRefresh()方法,可以断点看一下调用栈:
DispatcherServlet初始化
初始化方法调用的根源在javax.servlet.Servlet#init()方法,这个是servlet规范,当servlet第一次调用的时候会自动init()初始化。init()方法是由HttpServletBean实现的,HttpServlet(tomcat) -> HttpServletBean(spring) ->FrameworkServlet->DispatcherServlet,这就是DispatcherServlet的继承关系。
1.initHandlerMappings()方法,通过BeanFactoryUtils#BeanFactoryUtils.beansOfTypeIncludingAncestors()获取获取所有实现了HandlerMapping的bean。(所以如果有自定义的HandlerMapping也会被DispatcherServlet找到并使用),可以看到一共找到了5个,分别是RequestMappingHandlerMapping,BeanNameUrlHandlerMapping,RouterFunctionMapping,SimpleUrlHandlerMapping,WelcomePageHandlerMapping,具体这些HandlerMapping是哪来的,其实就在WebMvcConfigurationSupport中,WebMvcConfigurationSupport
最后一个WelcomePageHandlerMapping是springboot中定义的EnableWebMvcConfiguration引入的,不细究。
获取到后,有一个很关键的操作,就是排序,RequestMappingHandlerMapping的order是0,BeanNameUrlHandlerMapping的order是2,RouterFunctionMapping的order是3,SimpleUrlHandlerMapping的order是Ordered.LOWEST_PRECEDENCE - 1,也就是2147483646,就是很小了。排序的意义在于,每次根据url找Handler是依次找的,假如两个HandlerMapping有同一个url,或者一个HandlerMapping有个Fallback默认的handler,那就永远进不到我们的controller方法了。
2.WebMvcConfigurationSupport中的RequestMappingHandlerMapping初始化细节讨论。
RequestMappingHandlerMapping
其实就是创建了一个RequestMappingHandlerMapping对象,他有一个非常重要的方法,就是afterPropertiesSet(),直接看一下,他又调用了super.afterPropertiesSet(),也就是父类AbstractHandlerMethodMapping#afterPropertiesSet()方法,

    protected void initHandlerMethods() {
		for (String beanName : getCandidateBeanNames()) {
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
				processCandidateBean(beanName);
			}
		}
		handlerMethodsInitialized(getHandlerMethods());
	}

3.initHandlerMethods就完成了RequestMappingHandlerMapping的url注册。getCandidateBeanNames()方法获取的,

    protected String[] getCandidateBeanNames() {
		return (this.detectHandlerMethodsInAncestorContexts ?
				BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
				obtainApplicationContext().getBeanNamesForType(Object.class));
	}

获取到bean名字列表后,对每个bean进行处理,通过调用processCandidateBean()方法。
4.processCandidateBean()就是判断这个bean上面有没有关心的方法,可以看到是通过isHandler()方法做了一下判断。

    protected void processCandidateBean(String beanName) {
		Class<?> beanType = null;
		try {
			beanType = obtainApplicationContext().getType(beanName);
		}
		catch (Throwable ex) {
			// An unresolvable bean type, probably from a lazy bean - let's ignore it.
			if (logger.isTraceEnabled()) {
				logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
			}
		}
		if (beanType != null && isHandler(beanType)) {
			detectHandlerMethods(beanName);
		}
	}
	// 该方法为RequestMappingHandlerMapping重载的
	@Override
	protected boolean isHandler(Class<?> beanType) {
		return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
				AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
	}

可以清晰的看到,他判断这个bean是不是我们想要的bean,就是看这个bean上面有没有标记@Controller注解或@RequestMapping注解。然后进入detectHandlerMethods()方法。
5.detectHandlerMethods()这个方法就是最终获取url与handler映射的地方了。他通过MethodIntrospector.selectMethods()静态方法获取了关心的方法列表,通过getMappingForMethod()方法进行的过滤。

    protected void detectHandlerMethods(Object handler) {
		Class<?> handlerType = (handler instanceof String ?
				obtainApplicationContext().getType((String) handler) : handler.getClass());

		if (handlerType != null) {
			Class<?> userType = ClassUtils.getUserClass(handlerType);
			Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
					(MethodIntrospector.MetadataLookup<T>) method -> {
						try {
							return getMappingForMethod(method, userType);
						}
						catch (Throwable ex) {
							throw new IllegalStateException("Invalid mapping on handler class [" +
									userType.getName() + "]: " + method, ex);
						}
					});
			if (logger.isTraceEnabled()) {
				logger.trace(formatMappings(userType, methods));
			}
			methods.forEach((method, mapping) -> {
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
				registerHandlerMethod(handler, invocableMethod, mapping);
			});
		}
	}
	// 该方法是在RequestMappingHandlerMapping中重载的
    @Override
	@Nullable
	protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
		RequestMappingInfo info = createRequestMappingInfo(method);
		if (info != null) {
			RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
			if (typeInfo != null) {
				info = typeInfo.combine(info);
			}
			String prefix = getPathPrefix(handlerType);
			if (prefix != null) {
				info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
			}
		}
		return info;
	}
	// 该方法也是在RequestMappingHandlerMapping中重载的
	@Nullable
	private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
		RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
		RequestCondition<?> condition = (element instanceof Class ?
				getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
		return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
	}

很明显,就是找每个method方法上有没有标记@RequestMapping注解,如果没有那就返回null,即不加入最后得到的methods集合中。发现完毕后,通过调用registerHandlerMethod()方法注册。
6.

    protected void registerHandlerMethod(Object handler, Method method, T mapping) {
		this.mappingRegistry.register(mapping, handler, method);
	}
	// AbstractHandlerMethodMapping.MappingRegistry.register(T, Object, Method)方法
	public void register(T mapping, Object handler, Method method) {
			// Assert that the handler method is not a suspending one.
			if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
				Class<?>[] parameterTypes = method.getParameterTypes();
				if ((parameterTypes.length > 0) && "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) {
					throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
				}
			}
			this.readWriteLock.writeLock().lock();
			try {
				HandlerMethod handlerMethod = createHandlerMethod(handler, method);
				validateMethodMapping(handlerMethod, mapping);
				this.mappingLookup.put(mapping, handlerMethod);

				List<String> directUrls = getDirectUrls(mapping);
				for (String url : directUrls) {
					this.urlLookup.add(url, mapping);
				}

				String name = null;
				if (getNamingStrategy() != null) {
					name = getNamingStrategy().getName(handlerMethod, mapping);
					addMappingName(name, handlerMethod);
				}

				CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
				if (corsConfig != null) {
					this.corsLookup.put(handlerMethod, corsConfig);
				}

				this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
			}
			finally {
				this.readWriteLock.writeLock().unlock();
			}
		}

这里重点关注这几行代码,HandlerMethod handlerMethod = createHandlerMethod(handler, method);创建HandlerMethod(也就是handler了),this.mappingLookup.put(mapping, handlerMethod);以及this.urlLookup.add(url, mapping);这就是最终注册url映射的地方了。这俩具体用法,就是先根据url找mapping,再根据mapping找handlerMethod。mapping在这里指的是RequestMappingInfo实体类。
7.在dispatcherServlet从HandlerMapping中根据url查找handler,那么RequestMappingHandlerMapping是如何做的?

    // 来自AbstractHandlerMapping抽象类
    @Override
	@Nullable
	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		Object handler = getHandlerInternal(request);
		if (handler == null) {
			handler = getDefaultHandler();
		}
		if (handler == null) {
			return null;
		}
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}

		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

		if (logger.isTraceEnabled()) {
			logger.trace("Mapped to " + handler);
		}
		else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
			logger.debug("Mapped to " + executionChain.getHandler());
		}

		if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
			CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			config = (config != null ? config.combine(handlerConfig) : handlerConfig);
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}

		return executionChain;
	}
	// 来自AbstractHandlerMethodMapping
	@Override
	protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
		request.setAttribute(LOOKUP_PATH, lookupPath);
		this.mappingRegistry.acquireReadLock();
		try {
			HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
			return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
		}
		finally {
			this.mappingRegistry.releaseReadLock();
		}
	}
	// 来自AbstractHandlerMethodMapping
	@Nullable
	protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		List<Match> matches = new ArrayList<>();
		List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
		if (directPathMatches != null) {
			addMatchingMappings(directPathMatches, matches, request);
		}
		if (matches.isEmpty()) {
			// No choice but to go through all mappings...
			addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
		}

		if (!matches.isEmpty()) {
			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
			matches.sort(comparator);
			Match bestMatch = matches.get(0);
			if (matches.size() > 1) {
				if (logger.isTraceEnabled()) {
					logger.trace(matches.size() + " matching mappings: " + matches);
				}
				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();
					String uri = request.getRequestURI();
					throw new IllegalStateException(
							"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
				}
			}
			request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
			handleMatch(bestMatch.mapping, lookupPath, request);
			return bestMatch.handlerMethod;
		}
		else {
			return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
		}
	}

在这里,虽然lookupHandlerMethod()方法看上去很长,这里因为要处理Rest风格的带PathVariable的情况等,所以并不是简单的等值匹配。不过我们就关注this.mappingRegistry.getMappingsByUrl()这一行就好了,这不就是根据url找mapping吗?
本次分析到这吧,具体的细节还有很多。比如RequestMappingHandlerAdapter对handlerMethod的调用适配,也是相当的丰富多彩的,以后再更新一篇吧!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值