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
错误,如下图: