SpringMVC-详解HandlerMapping的前世今生之RequestMappingHandlerMapping

本文基于spring 5.5.2.release

上一篇文章《SpringMVC-详解HandlerMapping的前世今生之诞生》简单介绍了HandlerMapping接口,了解了它的实现类可以根据http请求查找Handler,但是没有介绍如何查找,以及Handler是怎么实现的,本文将通过HandlerMapping接口的实现类之一RequestMappingHandlerMapping,来回答上面这些问题。

一、创建RequestMappingHandlerMapping对象

先来看一下该类的定义:

/**
 * Creates {@link RequestMappingInfo} instances from type and method-level
 * {@link RequestMapping @RequestMapping} annotations in
 * {@link Controller @Controller} classes.
 */
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
		implements MatchableHandlerMapping, EmbeddedValueResolverAware {}

从注释中可以知道该类会创建实例RequestMappingInfo对象,而且与注解@RequestMapping有很大的关系,该类还实现了接口InitializingBean(上面代码没有体现出来),因此springmvc实例化该对象后,会执行afterPropertiesSet()方法做初始化。
下面看一下该类被实例化为对象的过程。
从上一篇文章《SpringMVC-详解HandlerMapping的前世今生之诞生》可以知道RequestMappingHandlerMapping对象是在WebMvcConfigurationSupport类中通过requestMappingHandlerMapping()方法创建的。下面来看一下这个方法:

	@Bean
	public RequestMappingHandlerMapping requestMappingHandlerMapping(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
		//通过new创建对象
		RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
		//设置order,可以看到RequestMappingHandlerMapping是排序第一的
		//也就是DispatcherServlet收到请求遍历HandlerMapping,RequestMappingHandlerMapping是第一个被遍历的
		mapping.setOrder(0);
		//设置拦截器,这里的拦截器无法通过配置干预
		mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
		//contentNegotiationManager用于控制请求中的media type(媒体类型)
		mapping.setContentNegotiationManager(contentNegotiationManager);
		//CorsConfiguration用于处理http请求头
		mapping.setCorsConfigurations(getCorsConfigurations());
		//PathMatchConfigurer是路径匹配的配置对象,
		//路径匹配指的是http请求路径与Handler可以处理的路径之间的匹配关系
		PathMatchConfigurer configurer = getPathMatchConfigurer();

		Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();
		if (useSuffixPatternMatch != null) {
			mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
		}
		Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();
		if (useRegisteredSuffixPatternMatch != null) {
			mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
		}
		Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
		if (useTrailingSlashMatch != null) {
			mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
		}

		UrlPathHelper pathHelper = configurer.getUrlPathHelper();
		if (pathHelper != null) {
			mapping.setUrlPathHelper(pathHelper);
		}
		PathMatcher pathMatcher = configurer.getPathMatcher();
		if (pathMatcher != null) {
			mapping.setPathMatcher(pathMatcher);
		}
		Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();
		if (pathPrefixes != null) {
			mapping.setPathPrefixes(pathPrefixes);
		}
		//返回创建完毕的对象,springmvc将该对象注册到容器中
		return mapping;
	}
	protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
		return new RequestMappingHandlerMapping();
	}

实例化为对象后,spring容器继续调用该类的afterPropertiesSet()方法:

	public void afterPropertiesSet() {
		//BuilderConfiguration相当于一个配置容器,将配置选项和与配置相关的对象放到容器中
		//这个类里面也没有复杂的逻辑,只是一些简单的get/set方法
		this.config = new RequestMappingInfo.BuilderConfiguration();
		this.config.setUrlPathHelper(getUrlPathHelper());
		//代码省略
		this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
		//调用父类的afterPropertiesSet()
		super.afterPropertiesSet();
	}
	//下面是父类的afterPropertiesSet()
	public void afterPropertiesSet() {
		initHandlerMethods();
	}
	//该方法是该类初始化过程中最关键的方法
	protected void initHandlerMethods() {
		//getCandidateBeanNames()从spring容器中获得所有的对象名字
		//下面这个循环其实是遍历容器中的所有对象
		for (String beanName : getCandidateBeanNames()) {
			//跳过名字以“scopedTarget.”开头的对象
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
				processCandidateBean(beanName);
			}
		}
		//打印日志,不详细介绍该方法代码
		handlerMethodsInitialized(getHandlerMethods());
	}
	protected void processCandidateBean(String beanName) {
		Class<?> beanType = null;
		try {
			//获得bean对象的类,也就是Class对象
			beanType = obtainApplicationContext().getType(beanName);
		}
		catch (Throwable ex) {
			//打印日志,代码省略
			}
		}
		//isHandler()方法是检查该类有没有Controller或者RequestMapping注解
		//如果有,则返回true
		if (beanType != null && isHandler(beanType)) {
			detectHandlerMethods(beanName);
		}
	}
	protected void detectHandlerMethods(Object handler) {
		//获得bean对象的类,也就是Class对象
		Class<?> handlerType = (handler instanceof String ?
				obtainApplicationContext().getType((String) handler) : handler.getClass());
		
		if (handlerType != null) {
			//如果该类被CGLIB代理了,则找到被代理类
			Class<?> userType = ClassUtils.getUserClass(handlerType);
			//下面这一行代码将遍历类中的所有方法,
			//找到该类中所有@RequestMapping注解的方法,并创建对应的RequestMappingInfo对象
			//methods保存了被@RequestMapping注解的方法和RequestMappingInfo对象的对应关系
			Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
					(MethodIntrospector.MetadataLookup<T>) method -> {
						try {
							//下面将介绍该方法
							return getMappingForMethod(method, userType);
						}
						catch (Throwable ex) {
							//抛出异常,代码省略
					});
				//代码省略
			methods.forEach((method, mapping) -> {
				//对方法的权限做检查,比如检查方法是否是private的
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);	
				//将方法和RequestMappingInfo对象注册到一个容器中,下面介绍该方法
				registerHandlerMethod(handler, invocableMethod, mapping);
			});
		}
	}

afterPropertiesSet()方法的调用链比较长,这里做一下总结,上面的代码总体上逻辑是:

  1. 创建配置对象BuilderConfiguration,便于访问RequestMappingHandlerMapping中的配置信息;
  2. 遍历spring容器中每个对象,找到被Controller或者RequestMapping注解的对象;
  3. 遍历上一步对象中的每个方法,找到@RequestMapping注解的方法,并创建对应的RequestMappingInfo对象;
  4. 将所有的RequestMappingInfo对象注册到一个容器中。

最后两步是在方法getMappingForMethod()和registerHandlerMethod()中完成的,上面代码没有对这两个方法展开。简单的说RequestMappingInfo对象中包含了可以处理http请求的方法信息(也就是被RequestMapping注解的方法),以及该方法可以处理哪些请求(也就是RequestMapping注解中指定的路径,还有一些自定义条件),将RequestMappingInfo对象注册到容器中后,当DispatcherServlet查找处理该请求的方法时,可以通过容器直接找到RequestMappingInfo对象,进而根据RequestMappingInfo调用处理该请求的方法。
下面将对方法getMappingForMethod()和registerHandlerMethod()展开介绍,如果对这个不感兴趣的可以跳过看下一个小节。

1、getMappingForMethod()

	protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
		//创建RequestMappingInfo对象
		RequestMappingInfo info = createRequestMappingInfo(method);
		if (info != null) {
			//下面方法是用于找到类上的RequestMapping注解信息,将类上的信息与方法上的注解信息合并
			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;
	}
	private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
		//获得注解RequestMapping配置信息
		RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
		RequestCondition<?> condition = (element instanceof Class ?
				getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
		//createRequestMappingInfo()方法创建RequestMappingInfo对象
		return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
	}
	protected RequestMappingInfo createRequestMappingInfo(
			RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
		//可以看到RequestMappingInfo对象就是根据@RequestMapping的注解信息创建的
		RequestMappingInfo.Builder builder = RequestMappingInfo
				.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
				.methods(requestMapping.method())
				.params(requestMapping.params())
				.headers(requestMapping.headers())
				.consumes(requestMapping.consumes())
				.produces(requestMapping.produces())
				.mappingName(requestMapping.name());
		if (customCondition != null) {
			builder.customCondition(customCondition);
		}
		return builder.options(this.config).build();
	}

getMappingForMethod()总起来说是根据@RequestMapping注解创建RequestMappingInfo对象,如果类上也有该注解,那么将类上的与方法上的做融合。

2、registerHandlerMethod()

	protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
		super.registerHandlerMethod(handler, method, mapping);
		//根据注解RequestBody的属性required更新自定义条件
		//如果设置required=true,那么会将该条件加入到RequestMappingInfo对象的条件集合中
		//这样便可以对请求做校验
		updateConsumesCondition(mapping, method);
	}
	protected void registerHandlerMethod(Object handler, Method method, T mapping) {
		//mappingRegistry是MappingRegistry的实例,该类中有多个Map属性,
		//注册就是将方法信息和注解信息添加到这些Map属性中
		this.mappingRegistry.register(mapping, handler, method);
	}

在执行mappingRegistry的注册方法时,还会创建HandlerMethod对象,之前一直提到HandlerMapping作用是查找Handler,在RequestMappingHandlerMapping中,Handler指的就是HandlerMethod对象。创建的过程也很简单,直接调用构造方法进行创建:

	protected HandlerMethod createHandlerMethod(Object handler, Method method) {
		if (handler instanceof String) {
			return new HandlerMethod((String) handler,
					obtainApplicationContext().getAutowireCapableBeanFactory(), method);
		}
		//一般是执行下面这个构造方法
		//入参handler是Controller对象,method是可以处理http请求的Method对象
		//根据这行代码,可以知道HandlerMethod保存了Controller对象和处理请求的方法信息
		//这样根据http请求找到了HandlerMethod对象,也就找到处理该请求的Controller方法
		//HandlerMethod构造方法逻辑并不复杂,有兴趣大家可以自行查看
		return new HandlerMethod(handler, method);
	}

创建完成后,springmvc将它和RequestMappingInfo之间的对应关系存储到一个Map对象中:

//mapping指的是RequestMappingInfo,handlerMethod指的是HandlerMethod
this.mappingLookup.put(mapping, handlerMethod);

到这里,RequestMappingHandlerMapping对象的创建完成了,该对象里面包含了容器中所有的@RequestMapping注解的方法信息。
下面介绍DispatcherServlet如何查找处理当前请求的Handler。

二、查找Handler

上面已经提到Handler指的是HandlerMethod对象,通过上面的代码介绍,其实已经大概知道查找过程。查找的代码也是挺多的,这里不详细对每个方法展开介绍,只介绍RequestMappingHandlerMapping的getHandler(),DispatcherServlet查找handler时,便是直接调用的该方法。

	//该方法是在RequestMappingHandlerMapping的父类中实现的
	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		//下面介绍getHandlerInternal方法的处理流程
		//handler是HandlerMethod对象
		Object handler = getHandlerInternal(request);
		//如果没有找到,返回默认值,对于RequestMappingHandlerMapping来说,默认值是null
		if (handler == null) {
			handler = getDefaultHandler();
		}
		if (handler == null) {
			return null;
		}
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}
		//使用HandlerExecutionChain做封装,添加一些拦截器
		//DispatcherServlet不直接调用Controller的方法,而是通过HandlerExecutionChain调用,
		//这样可以在调用前和调用后执行拦截器逻辑
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
		//代码删减
		if (hasCorsConfigurationSource(handler)) {
			//下面的代码用于添加拦截器
			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;
	}

getHandler()里面调用了getHandlerInternal()方法,所有的查找逻辑都在该方法里面,getHandlerInternal()方法的处理比较多,这里简答介绍一下处理流程:

  1. 从http请求参数中得到请求路径;
  2. 首先根据请求路径从mappingRegistry中精确查找RequestMappingInfo对象,如果找不到,那么遍历所有的RequestMappingInfo对象,根据http请求参数和RequestMappingInfo对象中记录的条件(也就是注解RequestMapping中的属性值)看看是否有符合要求的,这时可以做模糊匹配;
  3. 如果找到多个符合要求的RequestMappingInfo对象,那么对这些对象筛选,从中找到一个匹配度最高的RequestMappingInfo对象,比如请求路径的匹配,/teacher/s1都可以模糊匹配/teacher/*和/teacher/s*,但是后者匹配的更多,那么后者的匹配度也就越高;
  4. 因为RequestMappingInfo与HandlerMethod存在对应关系,找到了RequestMappingInfo对象,也就找到了HandlerMethod对象,将该对象返回;

三、总结

本文首先介绍了RequestMappingHandlerMapping的创建过程,里面涉及到两个关键类RequestMappingInfo和HandlerMethod,前者保存了@RequestMapping注解的配置信息,后者记录了@RequestMapping注解的方法信息,两者一一对应,前文一直提到的Handler,指的就是HandlerMethod,之后介绍根据请求参数查找HandlerMethod对象的过程,找到了HandlerMethod对象,也就找到了处理当前请求的方法,之后便可以调用该方法处理请求了。
从HandlerMethod的构造方法可以知道springmvc的Controller对象是单例的,不是每次请求都新建对象。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值