RequestMapping注解方式的handler

    科技2025-05-25  37

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

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

    Processed: 0.008, SQL: 8