RequestMapping注解方式的handler

1:@XXXMapping

为了是实现通过方法定义handler,springmvc定义了如下的一些注解:

org.springframework.web.servlet.bind.annotation.@RequestMapping
org.springframework.web.servlet.bind.annotation.@GetMapping
org.springframework.web.servlet.bind.annotation.@PostMapping
org.springframework.web.servlet.bind.annotation.@PutMapping
org.springframework.web.servlet.bind.annotation.@DeleteMapping
org.springframework.web.servlet.bind.annotation.@PatchMapping

我们知道每种handler都需要有对应的HandlerMapping来管理requestPath->Handler的对应关系,对于这类注解这个HandlerMapping就是RequestMappingHandlerMapping,其UML图如下:
在这里插入图片描述
作为例子,看下以下定义的handler在程序中最终获取的Handler是什么样子的

@Controller
@RequestMapping("/testinterceptor")
public class TestinterceptorController {

	@RequestMapping("/hi")
	@ResponseBody
	public String hi() {
		String msg = "testinterceptor hi";
		System.out.println(msg);
		return msg;
	}
}

我们可以通过org.springframework.web.servlet.handler.AbstractHandlerMapping#getHanlder方法中调用模板方法的获取的Handler的信息:
在这里插入图片描述
可以看到该handler的类型是org.springframework.web.method.HandlerMethod类型的,该类型是为了专门用来在封装作为handler方法,当然,本例就属于这种情况。

2:初始化入口

获取的工作是由org.springframework.web.mvc.annotation.RequstMappingHandlerMapping类完成的,其中方法的入口是在其父类org.springframework.web.servlet.handler.AbstractHandlerMethodMapping中,该类因为实现了org.springframework.beans.factory,InitializingBean接口,因此spring容器在初始化时会在属性设置完毕后调用其afterPropertiesSet()方法,但是最开始的入口并不是这里,而是在org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#afterPropertiesSet源码如下:

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#afterPropertiesSet
@Override
public void afterPropertiesSet() {
	this.config = new RequestMappingInfo.BuilderConfiguration();
	this.config.setUrlPathHelper(getUrlPathHelper());
	this.config.setPathMatcher(getPathMatcher());
	this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
	this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
	this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
	this.config.setContentNegotiationManager(getContentNegotiationManager());

	super.afterPropertiesSet();
}

注意代码super.afterPropertiesSet();就会调用org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#afterPropertiesSet完成相关HandlerMethod的初始化。

3:AbstractHandlerMethodMapping#initHandlerMethods

3.1:测试使用的controller

我们以如下的controller来进行说明:

@Controller
@RequestMapping("/testinterceptor")
public class TestinterceptorController {

	@RequestMapping("/hi")
	@ResponseBody
	public String hi() {
		String msg = "testinterceptor hi";
		System.out.println(msg);
		return msg;
	}

	@GetMapping("/hey")
	@ResponseBody
	public String hey() {
		String msg = "testinterceptor hey";
		System.out.println(msg);
		return msg;
	}
}

3.2:initHandlerMethods

方法:

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

这里循环所有的bean名称来进行处理,为了方便我们的调试,增加"testinterceptorController".equalsIgnoreCase(beanName)的条件变量:
在这里插入图片描述

循环内部是通过方法processCandidateBean来处理的,接下来看这个方法。

3.3:processCandidateBean

源码:

protected void processCandidateBean(String beanName) {
		Class<?> beanType = null;
		try {
			// <1>
			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);
			}
		}
		// <2>
		if (beanType != null && isHandler(beanType)) {
			// <3>
			detectHandlerMethods(beanName);
		}
}

<1>处代码是获取bean的类型,这里debug如下:
在这里插入图片描述
<2>处代码是判断当前的bean是否是一个handler,在AbstractHandlerMethodMapping中定义的是抽象方法protected abstract boolean isHandler(Class<?> beanType);其具体实现是在RequestMappingHandlerMapping中,源码如下:

@Override
protected boolean isHandler(Class<?> beanType) {
	return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
			AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

就是判断是否有@Controller注解或者是@RequestMapping注解,很明显,我们这里是满足要求的。因此会继续执行<3>处代码。
3.4:detectHandlerMethods(beanName)
源码:

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

	if (handlerType != null) {
		Class<?> userType = ClassUtils.getUserClass(handlerType);
		// <2>
		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));
		}
		// <3>
		methods.forEach((method, mapping) -> {
			Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
			registerHandlerMethod(handler, invocableMethod, mapping);
		});
	}
}

<1>处代码是获取类型,变量handler一般是String类型的,其实就是bean名称,所以会通过容器获取类型,<2>处是获取定义的所有方法,例如如下的定义,

@Controller
@RequestMapping("/testinterceptor")
public class TestinterceptorController {

	@RequestMapping("/hi")
	@ResponseBody
	public String hi(String hiName, String hiAge) {
		String msg = "testinterceptor hi";
		System.out.println(msg);
		return msg;
	}

	@GetMapping("/hey")
	@ResponseBody
	public String hey(String heyName, String heyAge) {
		String msg = "testinterceptor hey";
		System.out.println(msg);
		return msg;
	}
}

<1><2>debug信息:
在这里插入图片描述
<3>处的循环所有方法并进行注册,接下来看下方法registerHandlerMethod是如何完成方法注册的。

3.4:registerHandlerMethod

源码:

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
	this.mappingRegistry.register(mapping, handler, method);
}

直接调用org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register方法,源码:

public void register(T mapping, Object handler, Method method) {
	this.readWriteLock.writeLock().lock();
	try {
	    // <1>
		HandlerMethod handlerMethod = createHandlerMethod(handler, method);
		// <2>
		assertUniqueMethodMapping(handlerMethod, mapping);
		// <3>
		this.mappingLookup.put(mapping, handlerMethod);
		// <4>
		List<String> directUrls = getDirectUrls(mapping);
		// <5>
		for (String url : directUrls) {
			this.urlLookup.add(url, mapping);
		}
		// <6>
		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);
		}
		// <7>
		this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
	}
	finally {
		this.readWriteLock.writeLock().unlock();
	}
}

<1>处代码生成HandlerMethod对象,单起一部分讲解,<2>是保证完全相同的@RequestMapping只存在一个,源码如下:

private void assertUniqueMethodMapping(HandlerMethod newHandlerMethod, T mapping) {
	HandlerMethod handlerMethod = this.mappingLookup.get(mapping);
	if (handlerMethod != null && !handlerMethod.equals(newHandlerMethod)) {
		throw new IllegalStateException(
				"Ambiguous mapping. Cannot map '" +	newHandlerMethod.getBean() + "' method \n" +
				newHandlerMethod + "\nto " + mapping + ": There is already '" +
				handlerMethod.getBean() + "' bean method\n" + handlerMethod + " mapped.");
	}
}

其中异常中的信息在开发过程中应该是经常见到的(毕竟ctrlc,ctrlv比较多!!!),另外这里需要注意这里冲突的mapping其实并不是一个对象,因为每次都是重新生成的新对象,但是RequestMappingInfo重写了hashCode和equals方法,这样在get的时候信息完全相同的就认为是相同的key,其中源码如下:

org.springframework.web.servlet.mvc.method.RequestMappingInfo#hashCode
@Override
public int hashCode() {
	return (this.patternsCondition.hashCode() * 31 +  // primary differentiation
			this.methodsCondition.hashCode() + this.paramsCondition.hashCode() +
			this.headersCondition.hashCode() + this.consumesCondition.hashCode() +
			this.producesCondition.hashCode() + this.customConditionHolder.hashCode());
}

org.springframework.web.servlet.mvc.method.RequestMappingInfo#equals
@Override
public boolean equals(Object other) {
	if (this == other) {
		return true;
	}
	if (!(other instanceof RequestMappingInfo)) {
		return false;
	}
	RequestMappingInfo otherInfo = (RequestMappingInfo) other;
	return (this.patternsCondition.equals(otherInfo.patternsCondition) &&
			this.methodsCondition.equals(otherInfo.methodsCondition) &&
			this.paramsCondition.equals(otherInfo.paramsCondition) &&
			this.headersCondition.equals(otherInfo.headersCondition) &&
			this.consumesCondition.equals(otherInfo.consumesCondition) &&
			this.producesCondition.equals(otherInfo.producesCondition) &&
			this.customConditionHolder.equals(otherInfo.customConditionHolder));
}

<3>处存储Mapping和HandlerMethod的对应关系,其实就是@RequestMapping信息和所在方法的对应关系<4>获取Mapping中的url,需要注意,这里的URL必须是直接URL,即直接确定的URL,如hi/{id}hey/*就不是直接URL,如hi/hey就是直接URL,如下的定义:

@RequestMapping(value = { "multi_url_1", "multi_url_2", "multi_url_3" }, method = RequestMethod.GET)
@ResponseBody
public String multiValue(){}

该值debug如下:
在这里插入图片描述
<5>处代码存储url->mapping的对应关系,urlLookUp的定义是MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();因为相同的url可能存在不通过的mapping,所以这里使用的是MultiValueMap,比如如下就是这种情况:

@RequestMapping(value = "/url_1", method = RequestMethod.GET)
@ResponseBody
public String url_1(){}
@RequestMapping(value = "/url_1", method = RequestMethod.POST)
@ResponseBody
public String url_2(){}

@RequestMapping中只有method不一样,url都是url_1,但也是不同的RequestMappingInfo。<6>处代码生成mapping的名称,然后保存其对应的HandlerMethod,其中nameLookUp定义是Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();,不同的handlermethod对应的Maping的名字可能是一样的,所以这样存储,这和mapping名称的生成策略有关系,策略为类简单名称大写字符拼接+#+方法名,源码:

org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMethodMappingNamingStrategy#getName
public String getName(HandlerMethod handlerMethod, RequestMappingInfo mapping) {
	if (mapping.getName() != null) {
		return mapping.getName();
	}
	StringBuilder sb = new StringBuilder();
	String simpleTypeName = handlerMethod.getBeanType().getSimpleName();
	for (int i = 0; i < simpleTypeName.length(); i++) {
		if (Character.isUpperCase(simpleTypeName.charAt(i))) {
			sb.append(simpleTypeName.charAt(i));
		}
	}
	sb.append(SEPARATOR).append(handlerMethod.getMethod().getName());
	return sb.toString();
}

比如某个controller叫做MotherFuckerController,方法名称hi,另一个controller叫做是MoonFineController,同样有方法hi,则生成的名字就是重复的了,结果都是MFC#hi<7>处用mapping+handlermethod+directUrls+name定义MappingRegistration并以mapping为key存放。接下来继续看生成HandlerMethod的createHandlerMethod方法。

3.5:createHandlerMethod

protected HandlerMethod createHandlerMethod(Object handler, Method method) {
		HandlerMethod handlerMethod;
	if (handler instanceof String) {
		String beanName = (String) handler;
		// <1>
		handlerMethod = new HandlerMethod(beanName,
				obtainApplicationContext().getAutowireCapableBeanFactory(), method);
	}
	else {
		handlerMethod = new HandlerMethod(handler, method);
	}
	return handlerMethod;
}

主要看<1>处代码,源码如下:

public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
	Assert.hasText(beanName, "Bean name is required");
	Assert.notNull(beanFactory, "BeanFactory is required");
	Assert.notNull(method, "Method is required");
	this.bean = beanName;
	this.beanFactory = beanFactory;
	Class<?> beanType = beanFactory.getType(beanName);
	if (beanType == null) {
		throw new IllegalStateException("Cannot resolve bean type for bean with name '" + beanName + "'");
	}
	// <HandlerMethod_1>
	this.beanType = ClassUtils.getUserClass(beanType);
	this.method = method;
	// <HandlerMethod_2>
	this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
	this.parameters = initMethodParameters();
	// <HandlerMethod_3>
	evaluateResponseStatus();
}

<HandlerMethod_1>处代码处理如果是代理类时获取其原始类型。<HandlerMethod_2>处代码是如果该方法是桥接方法则获取其原始方法。<HandlerMethod_3>处代码是处理使用了@ResponseStatus的情况。

4:请求时获取HandlerMethod

请求的入口自然是在org.springframework.web.servlet.DispatcherServlet#doDispatch中的如下代码调用,对这里有疑问的朋友可以参考一次GET请求在springmvc中是的处理流程:

mappedHandler = getHandler(processedRequest);

getHandler源码:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	if (this.handlerMappings != null) {
		for (HandlerMapping mapping : this.handlerMappings) {
			HandlerExecutionChain handler = mapping.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
	}
	return null;
}

其中我们这里因为使用的是@XXXMapping的方式,所以这里的mapping就是RequestHandlerMapping,而getHandler方法是在AbstractHandlerMapping中定义的,源码如下:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	// 获取request对应的handler
	Object handler = getHandlerInternal(request);
	if (handler == null) {
		handler = getDefaultHandler();
	}
	if (handler == null) {
		return null;
	}
	// Bean name or resolved handler?
	// 如果是查询到的handler是String,则从容器中获取对应名称的handler对象
	if (handler instanceof String) {
		String handlerName = (String) handler;
		handler = obtainApplicationContext().getBean(handlerName);
	}
	// 获取符合请求的HandlerInterceptor并设置到
	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 (CorsUtils.isCorsRequest(request)) {
		CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
		CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
		CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
	}

	return executionChain;
}

主要看方法Object handler = getHandlerInternal(request);该模板方法实现是在org.springframework.web.servlet.handler.AbstractHandlerMethodMapping中,那么就让我们从这里开始吧!

4.1:AbstractHandlerMethodMapping#getHandlerInternal

源码:

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // <1>
	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
	this.mappingRegistry.acquireReadLock();
	try {
		// <2>
		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
		// <3>
		return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
	}
	finally {
		this.mappingRegistry.releaseReadLock();
	}
}

<1>处代码获取请求路径,如http://localhost:8080/testinterceptor/consume_and_produce?myParam=myValue,lookupPaht为/testinterceptor/consume_and_produce,<2>处代码为获取HandlerMethod的核心方法,单独分析,<3>处代码如果HandlerMethod所属的bean还没有获取,则获取,并重新创建HandlerMethod对象,源码如下:

public HandlerMethod createWithResolvedBean() {
	Object handler = this.bean;
	if (this.bean instanceof String) {
		Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory");
		String beanName = (String) this.bean;
		handler = this.beanFactory.getBean(beanName);
	}
	return new HandlerMethod(this, handler);
}

4.2:AbstractHandlerMethodMapping#lookupHandlerMethod

源码:

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	// <1>
	List<Match> matches = new ArrayList<>();
	// <2>
	List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
	if (directPathMatches != null) {
		// <3>
		addMatchingMappings(directPathMatches, matches, request);
	}
	// <4>
	if (matches.isEmpty()) {
		// No choice but to go through all mappings...
		addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
	}
	
	if (!matches.isEmpty()) {
		// <5>
		Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
		// <6>
		matches.sort(comparator);
		// <7>
		Match bestMatch = matches.get(0);
		// <8>
		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);
			// <9>
			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 + "}");
			}
		}
		// <10>
		request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
		// <11>
		handleMatch(bestMatch.mapping, lookupPath, request);
		// <12>
		return bestMatch.handlerMethod;
	}
	else {
	    // <13>
		return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
	}
}

<1>处的Match是Mpping+HanlderMethod的封装对象,源码如下:

private class Match {
    // Mappping, 如果是RequestMappingInfoHandlerMapping,则是RequestMappingInfo
    // public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo>
	private final T mapping;
	// HanlderMethod, 如@RequestMapping注解修饰方法+bean信息组合
	private final HandlerMethod handlerMethod;
	...snip...
}

该集合就是用于封装匹配的mapping即其对应的方法信息的,<2>处代码是根据请求路径获取Mapping信息(注意此时的mapping仅仅只是满足路径匹配要去的,其它要求不一定满足,如method,consumes,produces,params,heanders等)<3>处通过请求方法,头信息,参数等进一步过滤mapping。<4>处如果是没有匹配的,则通过遍历的方式再筛一遍(无奈之举,其实我觉得意义不大,所以老外写的注释是:No choice but to go through all mappings...)<5>处是创建MatchComparator,其实现了java.util.Comparator接口,然后通过<6>按照Mapping的匹配程度进行排序,匹配程度从高到低进行排序,<7>处是获取第一个元素,其就是最匹配元素,<8>处是处理最匹配和次匹配Mapping完全相同的情况(当@RequestMapping信息完全相同的时候),例如下面这种就会出现这种问题:

@RequestMapping(value = {"/common_requestmapping"})
@RequestMapping(value = {"/common_requestmapping", "xxx"})

使用请求http://localhost:8080/testinterceptor/common_requestmapping,访问如下图:
在这里插入图片描述
以上的异常其实就是通过<9>处的代码产生的,虽然第二个提供了多个value,但是这种是不影响mapping的优先级的,还有如果是虽然存在相同优先级的,但是不是最匹配和次匹配重复,也是可以的,如下映射:

@RequestMapping(value = {"/common_requestmapping"},method = RequestMethod.GET)--最匹配
@RequestMapping(value = {"/common_requestmapping", "xxx"})
@RequestMapping(value = {"/common_requestmapping", "yyy"})

访问http://localhost:8080/testinterceptor/common_requestmapping
在这里插入图片描述
<9>就是比较最高优先级和次高优先级是否相同,如果是相同的话,则无法确定使用哪一个,直接抛出异常java.lang.IllegalStateException的运行时异常。<10>,<11>,都是往requst中设置一些属性。<12>返回最匹配的HandlerMethod。<13>代码是处理无法正常访问的原因的,比如其它都是匹配的,但是请求方法不匹配,如@RequestMapping(value = {"/error_method"}, method = RequestMethod.GET)如果如下使用post方式访问,则会返回405 Method Not Allowed错误,如下图:
在这里插入图片描述

最后:都让开,我要喝瑞幸

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值