Spring MVC 执行流程:HandlerMapping
Spring MVC 执行流程:https://www.cnblogs.com/jhxxb/p/10437384.html
@RequestMapping
@Target({ElementType.TYPE, ElementType.METHOD}) // 能够用到类上和方法上 @Retention(RetentionPolicy.RUNTIME) @Documented @Mapping // @Mapping 这个注解是 @since 3.0 但它目前还只有这个地方使用到了 public @interface RequestMapping { /** * 给这个 Mapping 取一个名字。若不填写,就用 HandlerMethodMappingNamingStrategy 去按规则生成 */ String name() default ""; /** * 路径,数组形式,可以写多个。一般都是按照 Ant 风格进行书写 */ @AliasFor("path") String[] value() default {}; @AliasFor("value") String[] path() default {}; /** * 请求方法:GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE * 可以指定多个方法。如果不指定,表示适配所有方法类型 * 同时还有类似的枚举类:org.springframework.http.HttpMethod */ RequestMethod[] method() default {}; /** * 指定 request 中必须包含某些参数值时,才让该方法处理 * 使用 params 元素,可以让多个处理方法处理到同一个 URL 的请求, 而这些请求的参数是不一样的 * 如:@RequestMapping(value = "/fetch", params = {"personId=10"} 和 @RequestMapping(value = "/fetch", params = {"personId=20"} * 这两个方法都处理请求`/fetch`,但是参数不一样,进入的方法也不一样 * 支持 !myParam 和 myParam!=myValue 这种 */ String[] params() default {}; /** * 指定 request 中必须包含某些指定的 header 值,才能让该方法处理请求 * @RequestMapping(value = "/head", headers = {"content-type=text/plain"} * @see org.springframework.http.MediaType */ String[] headers() default {}; /** * 指定处理请求 request 的提交内容类型(Content-Type),例如 application/json、text/html 等 * 相当于只有指定的这些 Content-Type 的才处理 * @RequestMapping(value = "/cons", consumes = {"application/json", "application/XML"} * 不指定表示处理所有,取值参见枚举类:org.springframework.http.MediaType * 可以使用 !text/plain 这样非的表达方式 */ String[] consumes() default {}; /** * 指定返回的内容类型,返回的内容类型必须是 request 请求头(Accept)中所包含的类型 * 仅当 request 请求头中的(Accept)类型中包含该指定类型才返回 * 可以使用 !text/plain 这样非的表达方式 * @see org.springframework.http.MediaType */ String[] produces() default {}; }
consumes、produces、params、headers 四个属性都是用来缩小请求范围。consumes 只能指定 content-Type 的内容类型,但是 headers 可以指定所有。所以可以认为 headers 包含 consumes 和 produces 的功能
Spring 提供了一个类:org.springframework.http.HttpHeaders,它里面有常量:包含几乎所有的请求头的 key
组合注解
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
源码
在 SpringMVC 中会有很多请求,每个请求都需要一个 Handler 处理,具体接收到一个请求之后使用哪个 Handler 进行处理呢?这就是 HandlerMapping 需要做的事。
HandlerMapping:负责映射用户的 URL 和对应的处理类 Handler,HandlerMapping 并没有规定这个 URL 与应用的处理类如何映射。所以在 HandlerMapping 接口中仅仅定义了根据一个 URL 必须返回一个由 HandlerExecutionChain 代表的处理链,我们可以在这个处理链中添加任意的 HandlerAdapter 实例来处理这个 URL 对应的请求(这样保证了最大的灵活性映射关系)。
public interface HandlerMapping { String BEST_MATCHING_HANDLER_ATTRIBUTE = org.springframework.web.servlet.HandlerMapping.class.getName() + ".bestMatchingHandler"; String LOOKUP_PATH = org.springframework.web.servlet.HandlerMapping.class.getName() + ".lookupPath"; String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = org.springframework.web.servlet.HandlerMapping.class.getName() + ".pathWithinHandlerMapping"; String BEST_MATCHING_PATTERN_ATTRIBUTE = org.springframework.web.servlet.HandlerMapping.class.getName() + ".bestMatchingPattern"; String INTROSPECT_TYPE_LEVEL_MAPPING = org.springframework.web.servlet.HandlerMapping.class.getName() + ".introspectTypeLevelMapping"; String URI_TEMPLATE_VARIABLES_ATTRIBUTE = org.springframework.web.servlet.HandlerMapping.class.getName() + ".uriTemplateVariables"; String MATRIX_VARIABLES_ATTRIBUTE = org.springframework.web.servlet.HandlerMapping.class.getName() + ".matrixVariables"; String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = org.springframework.web.servlet.HandlerMapping.class.getName() + ".producibleMediaTypes"; @Nullable HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; }
HandlerMapping 有两大继承主线:MatchableHandlerMapping 和 AbstractHandlerMapping
WebApplicationObjectSupport 和 ApplicationObjectSupport 有点像是 ApplicationContextAware 和 ServletContextAware 的适配器,在允许继承的情况下,只需要继承此类就能自动拥有上面两个接口的功能,可以直接获取上下文等
@Service public class HelloServiceImpl extends WebApplicationObjectSupport implements HelloService { @Override public Object hello() { // 继承自 ApplicationObjectSupport System.out.println(super.getApplicationContext()); System.out.println(super.obtainApplicationContext()); // @since 5.0 // MessageSourceAccessor 参考:MessageSourceAware,它是对 MessageSource 的一个包装,处理国际化 System.out.println(super.getMessageSourceAccessor()); // 继承自 WebApplicationObjectSupport System.out.println(super.getWebApplicationContext()); System.out.println(super.getServletContext()); System.out.println(super.getTempDir()); // Tomcat9_demowar\work\Catalina\localhost\demo_war_war return "service hello"; } @Override protected void initApplicationContext() throws BeansException { // 父类为空实现,子类可以自行实现逻辑 // 比如子类 AbstractDetectingUrlHandlerMapping 就复写了此方法去 detectHandlers(); super.initApplicationContext(); } }
AbstractHandlerMapping 的子类都没有重写 getHandler() 这个方法
相当于 AbstractHandlerMapping 实现了对 getHandler() 方法的模版实现,它主要是对 HandlerInterceptor 进行了一个通用处理,最终会把它们放进 HandlerExecutionChain 里面去
/** * 自己额外实现了 BeanNameAware 和 Ordered 排序接口 */ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered, BeanNameAware { @Nullable private Object defaultHandler; // 默认的 Handler,使用的 Obejct,子类实现的时候,使用 HandlerMethod、HandlerExecutionChain 等 private UrlPathHelper urlPathHelper = new UrlPathHelper(); // url 路径计算的辅助工具类 private PathMatcher pathMatcher = new AntPathMatcher(); // Ant 风格的 Path 匹配模式,解决如 /books/{id} 场景 private final List<Object> interceptors = new ArrayList<>(); // 保存着拦截器们 private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>(); // 从 interceptors 中解析得到,直接添加给全部 handler @Nullable private CorsConfigurationSource corsConfigurationSource; // 跨域相关的配置 private CorsProcessor corsProcessor = new DefaultCorsProcessor(); private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered 最低的顺序 @Nullable private String beanName; public void setPathMatcher(PathMatcher pathMatcher) { // UrlPathHelper 我们可以自己指定 Assert.notNull(pathMatcher, "PathMatcher must not be null"); this.pathMatcher = pathMatcher; if (this.corsConfigurationSource instanceof UrlBasedCorsConfigurationSource) { ((UrlBasedCorsConfigurationSource) this.corsConfigurationSource).setPathMatcher(pathMatcher); } } public PathMatcher getPathMatcher() { // PathMatcher 我们可以自己指定 return this.pathMatcher; } public void setInterceptors(Object... interceptors) { // 可变参数:可以一次性添加多个拦截器,这里使用的 Object this.interceptors.addAll(Arrays.asList(interceptors)); } public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) { // 跨域设置 Assert.notNull(corsConfigurations, "corsConfigurations must not be null"); if (!corsConfigurations.isEmpty()) { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.setCorsConfigurations(corsConfigurations); source.setPathMatcher(this.pathMatcher); source.setUrlPathHelper(this.urlPathHelper); source.setLookupPathAttributeName(LOOKUP_PATH); this.corsConfigurationSource = source; } else { this.corsConfigurationSource = null; } } public void setCorsConfigurationSource(CorsConfigurationSource corsConfigurationSource) { // 重载方法 Assert.notNull(corsConfigurationSource, "corsConfigurationSource must not be null"); this.corsConfigurationSource = corsConfigurationSource; } /** * 这步骤是最重要的。相当于父类 setApplicationContext 完成了之后,就会执行到这里 * 这步可以看出,这里主要处理的都是拦截器相关的内容 */ @Override protected void initApplicationContext() throws BeansException { extendInterceptors(this.interceptors); // 给子类扩展:增加拦截器,默认为空实现 detectMappedInterceptors(this.adaptedInterceptors); // 找到所有 MappedInterceptor 类型的 bean 添加到 adaptedInterceptors 中 // 将 interceptors 中的拦截器取出放入 adaptedInterceptors // 如果是 WebRequestInterceptor 类型的拦截器,需要用 WebRequestHandlerInterceptorAdapter 进行包装适配 initInterceptors(); } /** * 去容器(含祖孙容器)内找到所有的 MappedInterceptor 类型的拦截器,添加进去,非单例的 Bean 也包含 * 备注:MappedInterceptor 为 SpringMVC 拦截器接口`HandlerInterceptor`的实现类,并且是个 final 类,Spring3.0 后出来的 */ protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) { mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), MappedInterceptor.class, true, false).values()); } /** * 它就是把调用者放进来的 interceptors 们,适配成 HandlerInterceptor,然后统一放在`adaptedInterceptors`里面装着 */ protected void initInterceptors() { if (!this.interceptors.isEmpty()) { for (int i = 0; i < this.interceptors.size(); i++) { Object interceptor = this.interceptors.get(i); if (interceptor == null) { throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null"); } this.adaptedInterceptors.add(adaptInterceptor(interceptor)); } } } /** * 适配也很简单,就是支持源生的 HandlerInterceptor,以及 WebRequestInterceptor 两种情况 */ protected HandlerInterceptor adaptInterceptor(Object interceptor) { if (interceptor instanceof HandlerInterceptor) { return (HandlerInterceptor) interceptor; } else if (interceptor instanceof WebRequestInterceptor) { return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor); } else { throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName()); } } @Nullable protected final HandlerInterceptor[] getAdaptedInterceptors() { return (!this.adaptedInterceptors.isEmpty() ? this.adaptedInterceptors.toArray(new HandlerInterceptor[0]) : null); } /** * 它只会返回 MappedInterceptor 这种类型的,上面是返回 adaptedInterceptors 所有 */ @Nullable protected final MappedInterceptor[] getMappedInterceptors() { List<MappedInterceptor> mappedInterceptors = new ArrayList<>(this.adaptedInterceptors.size()); for (HandlerInterceptor interceptor : this.adaptedInterceptors) { if (interceptor instanceof MappedInterceptor) { mappedInterceptors.add((MappedInterceptor) interceptor); } } return (!mappedInterceptors.isEmpty() ? mappedInterceptors.toArray(new MappedInterceptor[0]) : null); } /** * 这个方法是该抽象类提供的一个非常重要的模版方法:根据 request 获取到一个 HandlerExecutionChain * 也是抽象类实现接口 HandlerMapping 的方法 */ @Override @Nullable public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { Object handler = getHandlerInternal(request); // 根据 request 获取对应的 handler,抽象方法,由具体的子类去实现 if (handler == null) { // 若没有匹配上处理器,那就走默认的处理器,默认的处理器也是需要由子类给赋值,否则也会 null 的 handler = getDefaultHandler(); } if (handler == null) { // 若默认的处理器都没有,那就直接返回 null return null; } if (handler instanceof String) { // 如果是个 String 类型的名称,那就去容器内找这个 Bean,当作一个 Handler String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } // 关键步骤:根据 handler 和 request 构造一个请求处理链 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)) { // Spring4.2 版本提供了对 CORS 跨域资源共享的支持 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; } /** * 已经找到 handler,那就根据此 handler 构造一个请求链 * 这里主要是把拦截器们给添加进来,构成对指定请求的一个拦截器链 */ protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { // 小细节:因为 handler 本身也许就是个 Chain,所以此处需要判断一下 HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); // 此处就用到了 urlPathHelper 来解析 request // 请求地址为:`http://localhost:8080/demo_war_war/api/v1/hello`,那么 lookupPath = /api/v1/hello String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH); for (HandlerInterceptor interceptor : this.adaptedInterceptors) { if (interceptor instanceof MappedInterceptor) { // 这里其实就能体现出 MappedInterceptor 的些许优势了:也就是它只有路径匹配上了才会拦截,没有匹配上的就不会拦截了 // 备注:MappedInterceptor 可以设置 includePatterns 和 excludePatterns 等 MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor; if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); } } else { chain.addInterceptor(interceptor); } } return chain; }
MappedInterceptor
/** * @since 3.0 * 是个 final 类,不允许直接使用继承的方式来扩展 */ public final class MappedInterceptor implements HandlerInterceptor { @Nullable private final String[] includePatterns; // 可以为 null @Nullable private final String[] excludePatterns; private final HandlerInterceptor interceptor; // 持有一个 interceptor 的引用,类似于目标类 // 注意:该类允许你自己指定路径的匹配规则。但是 Spring 里,不管哪个上层服务,默认使用的都是 Ant 风格的匹配 // 并不是正则的匹配,所以效率上还是很高的 @Nullable private PathMatcher pathMatcher; /** * 构造函数:不仅兼容 HandlerInterceptor,还可以转换 WebRequestInterceptor */ public MappedInterceptor(@Nullable String[] includePatterns, WebRequestInterceptor interceptor) { this(includePatterns, null, interceptor); } public MappedInterceptor(@Nullable String[] includePatterns, @Nullable String[] excludePatterns, WebRequestInterceptor interceptor) { // 此处使用 WebRequestHandlerInterceptorAdapter 这个适配器 this(includePatterns, excludePatterns, new WebRequestHandlerInterceptorAdapter(interceptor)); } public boolean matches(String lookupPath, PathMatcher pathMatcher) { // 原则:excludePatterns 先执行,includePatterns 后执行 PathMatcher pathMatcherToUse = (this.pathMatcher != null ? this.pathMatcher : pathMatcher); if (!ObjectUtils.isEmpty(this.excludePatterns)) { for (String pattern : this.excludePatterns) { if (pathMatcherToUse.match(pattern, lookupPath)) { return false; } } } // 如果 excludePatterns 执行完都没有匹配的,并且 includePatterns 是空的,那就返回 true(这是个处理方式技巧,对这种互斥的情况,这一步判断很关键) if (ObjectUtils.isEmpty(this.includePatterns)) { return true; } for (String pattern : this.includePatterns) { if (pathMatcherToUse.match(pattern, lookupPath)) { return true; } } return false; }
使用
@Configuration @EnableWebMvc @ComponentScan(value = "com.mvc", useDefaultFilters = false, includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class, ControllerAdvice.class, RestControllerAdvice.class})} ) public class WebMvcConfig extends WebMvcConfigurerAdapter { /** * 方式一:最源生的使用方式:直接注册进去即可 * 支持 includePatterns 和 excludePatterns * 底层原理是依赖于一个`InterceptorRegistration`类,它是个普通类,协助创建一个`MappedInterceptor` * 由此可见最终底层还是使用的`MappedInterceptor` */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new HelloInterceptor()) .addPathPatterns() // 就是includePatterns .excludePathPatterns(); } /** * 方式二:如果说上述方式是交给 Spring 去帮我们自动处理,这种方式相当于自己手动来处理 * 请务必注意:此处的返回值必须是 MappedInterceptor,而不能是 HandlerInterceptor,否则不生效 * 因为 BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), MappedInterceptor.class, true, false) * 这个方法它只去找 MappedInterceptor 类型,如果你是父类型,那就匹配不上了,这和工厂方法的 Bean 定义信息有关 */ @Bean public MappedInterceptor myHandlerInterceptor() { String[] includePatterns = {"/api/v1/hello"}; MappedInterceptor handlerInterceptor = new MappedInterceptor(includePatterns, new HelloInterceptor()); return handlerInterceptor; } }
MatchableHandlerMapping
/** * @since 4.3.1 */ public interface MatchableHandlerMapping extends HandlerMapping { @Nullable RequestMatchResult match(HttpServletRequest request, String pattern); // 确定给定的请求是否符合请求条件,pattern:模版 }
目前有两个类实现了此方法 RequestMappingHandlerMapping 和 AbstractUrlHandlerMapping,但是 Spring 内部还没有调用过此接口方法,此处跳过这部分
看 AbstractHandlerMapping 的真正实现类,主要分为两大主线:AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping
AbstractUrlHandlerMapping
属于 Spring 最早期的控制器实现。完全是基于类级别的:一个类就是一个 Handler,模式和源生的 Servlet 没太大差异。SpringMVC 默认给容器注入了该方式的实现类,由此可见 Spring 还是保持了充分的向下兼容的
和 URL 有关。它的大致思路为:将 url 对应的 Handler 保存在一个 Map 中,在 getHandlerInternal 方法中使用 url 从 Map 中获取 Handler
该抽象类提供了一个 Map,缓存着 URL 和它对应的 Handler,这是个非常重要的缓存。它提供了 registerHandler() 允许子类调用,向缓存里注册 url 和 handler 的对应关系
注意:此处不能把 Map 放出去让子类直接 put,因为程序必须要高内聚,才能保证更好的隔离性以及稳定性
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping { @Nullable private Object rootHandler; // 根路径 / 的处理器 private boolean useTrailingSlashMatch = false; // 是否使用斜线 / 匹配,如果为 true,那么`/users`它也会匹配上`/users/`,默认是 false 的 private boolean lazyInitHandlers = false; // 是否延迟初始化 handler。仅适用于单实例 handler,默认是 false,表示立即实例化 private final Map<String, Object> handlerMap = new LinkedHashMap<>(); // 这个 Map 就是缓存下 URL 对应的 Handler(注意这里只是 handler,而不是 chain) /** * 这个就是父类留给子类实现的抽象方法,此抽象类相当于进行了进一步的模版实现 */ @Override @Nullable protected Object getHandlerInternal(HttpServletRequest request) throws Exception { // 找到 URL 的后半段:如`/api/v1/hello`,由此可见 SpringMVC 处理 URL 路径匹配都是从工程名后面开始匹配的 String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); // 根据 url 查找 handler // 1、先去 handlerMap 里找,若找到了那就实例化它,并且并且给 chain 里加入一个拦截器:`PathExposingHandlerInterceptor`,它是个 private 私有类的 HandlerInterceptor // 该拦截器的作用:request.setAttribute() 请求域里面放置四个属性,key 见 HandlerMapping 的常量 // 2、否则就使用 PathMatcher 去匹配 URL,这里面光匹配其实是比较简单的。但是这里面还解决了一个问题:那就是匹配上多个路径的问题 // 因此:若匹配上多个路径了,就按照 PathMatcher 的排序规则排序,取值 get(0),最后就是同上,加上那个 HandlerInterceptor 即可 // 需要注意的是:若存在 uriTemplateVariables,也就是路径里都存在多个最佳的匹配的情况,比如 /book/{id} 和 /book/{name} 这两种 // 还有就是 URI 完全一样,但是一个是 get 方法,一个是 post 方法之类的,那就再加一个拦截器`UriTemplateVariablesHandlerInterceptor`,它 request.setAttribute() 了一个属性:key为 xxx.uriTemplateVariables // 这些 Attribute 后续都是有用的,请注意:这里默认的两个拦截器每次都是 new 出来的,和 Handler 可以说是绑定的,所以不会存在线程安全问题 request.setAttribute(LOOKUP_PATH, lookupPath); Object handler = lookupHandler(lookupPath, request); if (handler == null) { // 若没找到 // 处理跟路径 / 和默认的 Handler Object rawHandler = null; if (StringUtils.matchesCharacter(lookupPath, '/')) { rawHandler = getRootHandler(); } if (rawHandler == null) { rawHandler = getDefaultHandler(); } if (rawHandler != null) { if (rawHandler instanceof String) { String handlerName = (String) rawHandler; rawHandler = obtainApplicationContext().getBean(handlerName); } validateHandler(rawHandler, request); // 就是注册上面说的默认的两个拦截器,第四个参数为 null,就只会注册一个拦截器 // 然后把 rawHandler 转换成 chain(这个时候 chain 里面可能已经有两个拦截器了,然后父类还会继续把用户自定义的拦截器放上去) handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null); } } return handler; } /** * 该抽象类提供的这个方法就特别重要了:向 handlerMap 里面 put 值的唯一入口,可以批量 urls */ protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException { Assert.notNull(urlPaths, "URL path array must not be null"); for (String urlPath : urlPaths) { registerHandler(urlPath, beanName); } } protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException { Assert.notNull(urlPath, "URL path must not be null"); Assert.notNull(handler, "Handler object must not be null"); Object resolvedHandler = handler; if (!this.lazyInitHandlers && handler instanceof String) { // 如果是 beanName,并且它是立马加载的 String handlerName = (String) handler; ApplicationContext applicationContext = obtainApplicationContext(); if (applicationContext.isSingleton(handlerName)) { // 并且还是单例的,那就立马实例化吧 resolvedHandler = applicationContext.getBean(handlerName); } } Object mappedHandler = this.handlerMap.get(urlPath); // 先尝试从 Map 中去获取 if (mappedHandler != null) { // 一个 URL 只能映射到一个 Handler 上(但是一个 Handler 是可以处理多个 URL 的,这个需要注意) if (mappedHandler != resolvedHandler) { throw new IllegalStateException("Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath + "]: There is already " + getHandlerDescription(mappedHandler) + " mapped."); } } else { if (urlPath.equals("/")) { // 如果你的 handler 处理的路径是根路径 if (logger.isTraceEnabled()) { logger.trace("Root mapping to " + getHandlerDescription(handler)); } setRootHandler(resolvedHandler); } else if (urlPath.equals("/*")) { // 这个路径相当于处理所有优先级是最低的,所以当作默认的处理器来使用 if (logger.isTraceEnabled()) { logger.trace("Default mapping to " + getHandlerDescription(handler)); } setDefaultHandler(resolvedHandler); } else { // 正常路径的 this.handlerMap.put(urlPath, resolvedHandler); if (logger.isTraceEnabled()) { // trace 级别日志 logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler)); } } } } public final Map<String, Object> getHandlerMap() { // 该缓存也提供了一个只读视图给调用者访问 return Collections.unmodifiableMap(this.handlerMap); }
AbstractDetectingUrlHandlerMapping
是个抽象类,继承自 AbstractUrlHandlerMapping。它就越来越具有功能化了:Detecting 表明它是有检测 URL 的功能的
AbstractDetectingUrlHandlerMapping 是通过扫描方式注册 Handler,收到请求时由 AbstractUrlHandlerMapping 的 getHandlerInternal 进行分发,看看到底是交给哪个 Handler 进行处理
/** * @since 2.5 */ public abstract class AbstractDetectingUrlHandlerMapping extends AbstractUrlHandlerMapping { // 是否要去祖先容器里面检测所有的 Handlers,默认是 false,表示只在自己的容器里面找 // 若设置为 true,相当于在父容器里的 Controller 也会被挖出来,一般不建议这么去做 private boolean detectHandlersInAncestorContexts = false; @Override public void initApplicationContext() throws ApplicationContextException { super.initApplicationContext(); detectHandlers(); // 这里是检测的入口 } protected void detectHandlers() throws BeansException { ApplicationContext applicationContext = obtainApplicationContext(); // 默认只会在当前容器里面去查找检测,这里使用的 Object.class,说明是把本容器内所有类型的 Bean 定义都拿出来了 String[] beanNames = (this.detectHandlersInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) : applicationContext.getBeanNamesForType(Object.class)); for (String beanName : beanNames) { // 这是个抽象方法由子类去实现。它的作用就是看看 url 和 bean 怎么才算是匹配,也就是说这个 handler 到底能够处理哪些 URL // 注意:此处还是类级别(Bean),相当于一个类就是一个 Handler String[] urls = determineUrlsForHandler(beanName); if (!ObjectUtils.isEmpty(urls)) { registerHandler(urls, beanName); // 注册进去,缓存起来 } } if ((logger.isDebugEnabled() && !getHandlerMap().isEmpty()) || logger.isTraceEnabled()) { logger.debug("Detected " + getHandlerMap().size() + " mappings in " + formatMappingName()); } }
BeanNameUrlHandlerMapping
它是 AbstractDetectingUrlHandlerMapping 的唯一实现类(Spring5 以上)
public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping { @Override protected String[] determineUrlsForHandler(String beanName) { List<String> urls = new ArrayList<>(); // 意思就是必须以 / 开头才行,这算是一种约定吧 // 这种方式和 @WebServlet 方式一模一样 if (beanName.startsWith("/")) { urls.add(beanName); } // 别名也可以 String[] aliases = obtainApplicationContext().getAliases(beanName); for (String alias : aliases) { if (alias.startsWith("/")) { urls.add(alias); } } return StringUtils.toStringArray(urls); } }
SimpleUrlHandlerMapping
它是 AbstractUrlHandlerMapping 的直接实现类,也是一个基于Map的简单实现。就是把开发者指定的一个 Map,在容器启动的时候把它注册进去,当然我们自己已经知道了 URL 和 Handler 的映射关系了,然后需要进一步构造出一个 HandlerMapping 的时候,或许它是一个较快解决问题的选择,它最重要的是 urlMap 这个参数
public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping { private final Map<String, Object> urlMap = new LinkedHashMap<>();public SimpleUrlHandlerMapping(Map<String, ?> urlMap, int order) { setUrlMap(urlMap); setOrder(order); } public void setMappings(Properties mappings) { CollectionUtils.mergePropertiesIntoMap(mappings, this.urlMap); } public void setUrlMap(Map<String, ?> urlMap) { this.urlMap.putAll(urlMap); } @Override public void initApplicationContext() throws BeansException { super.initApplicationContext(); registerHandlers(this.urlMap); } protected void registerHandlers(Map<String, Object> urlMap) throws BeansException { if (urlMap.isEmpty()) { logger.trace("No patterns in " + formatMappingName()); } else { urlMap.forEach((url, handler) -> { // Prepend with slash if not already present. if (!url.startsWith("/")) { url = "/" + url; } // Remove whitespace from handler bean name. if (handler instanceof String) { handler = ((String) handler).trim(); } registerHandler(url, handler); }); if (logger.isDebugEnabled()) { List<String> patterns = new ArrayList<>(); if (getRootHandler() != null) { // 如果还没有 /,在前面加上 / patterns.add("/"); } if (getDefaultHandler() != null) { patterns.add("/**"); } patterns.addAll(getHandlerMap().keySet()); logger.debug("Patterns " + patterns + " in " + formatMappingName()); } } }
SimpleUrlHandlerMapping 使用
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property> <map> <entry key="/hello.do" value="myController"/> <entry key="/my.do" value="myController"/> </map> </property> </bean>
对应的 Controller
// 我们的 Controller 类必须(直接|间接)实现 org.springframework.web.servlet.mvc.Controller 接口,才被认为是一个控制器,否则无法判断具体处理方法是谁 @Controller("/hello") // 此处 BeanName 必须是 / 开头,否则是不会作为 handler 的 public class HelloController extends AbstractController { @Override protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("this is my demo"); return null; } }
AbstractHandlerMethodMapping
将 method 作为 handler 来使用的,比如 @RequestMapping 所注释的方法就是这种 handler(当然它并不强制你一定得使用 @RequestMapping 这样的注解)
在前面我们已经知道了 AbstractHandlerMethodMapping 的父类 AbstractHandlerMapping,其定义了抽象方法 getHandlerInternal(HttpServletRequest request),那么这里主要看看它对此抽象方法的实现
/** * @since 3.1 * 实现了 initializingBean 接口,其实主要的注册操作则是通过 afterPropertiesSet() 接口方法来调用的 * 它是带有泛型 T 的,T:包含 HandlerMethod 与传入请求匹配所需条件的 handlerMethod 的映射 */ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean { private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget."; // SCOPED_TARGET 的 BeanName 的前缀 private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH = new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle")); private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration(); // 跨域相关 static { ALLOW_CORS_CONFIG.addAllowedOrigin("*"); ALLOW_CORS_CONFIG.addAllowedMethod("*"); ALLOW_CORS_CONFIG.addAllowedHeader("*"); ALLOW_CORS_CONFIG.setAllowCredentials(true); } private boolean detectHandlerMethodsInAncestorContexts = false; // 默认不会去祖先容器里面找 Handlers // @since 4.1 提供的新接口 // 为 HandlerMetho 的映射分配名称的策略接口,只有一个方法 getName() // 唯一实现为:RequestMappingInfoHandlerMethodMappingNamingStrategy // 策略为:@RequestMapping 指定了 name 属性,那就以指定的为准,否则策略为:取出 Controller 所有的`大写字母` + # + method.getName() // 如:AppoloController#match方法,最终的 name 为:AC#match // 当然这个你也可以自己实现这个接口,然后 set 进来即可(只是一般没啥必要这么去干) @Nullable private HandlerMethodMappingNamingStrategy<T> namingStrategy; private final MappingRegistry mappingRegistry = new MappingRegistry(); // 内部类,负责注册 /** * 此处细节:使用的是读写锁,比如此处使用的是读锁,获得所有注册进去的 Handler 的 Map */ public Map<T, HandlerMethod> getHandlerMethods() { this.mappingRegistry.acquireReadLock(); try { return Collections.unmodifiableMap(this.mappingRegistry.getMappings()); } finally { this.mappingRegistry.releaseReadLock(); } } /** * 此处是根据 mappingName 来获取一个 Handler */ @Nullable public List<HandlerMethod> getHandlerMethodsForMappingName(String mappingName) { return this.mappingRegistry.getHandlerMethodsByMappingName(mappingName); } /** * 最终都是委托给 mappingRegistry 去做了注册的工作,此处日志级别为 trace */ public void registerMapping(T mapping, Object handler, Method method) { if (logger.isTraceEnabled()) { logger.trace("Register \"" + mapping + "\" to " + method.toGenericString()); } this.mappingRegistry.register(mapping, handler, method); } public void unregisterMapping(T mapping) { if (logger.isTraceEnabled()) { logger.trace("Unregister mapping \"" + mapping + "\""); } this.mappingRegistry.unregister(mapping); } @Override public void afterPropertiesSet() { initHandlerMethods(); // 这个很重要,是初始化 HandlerMethods 的入口 } /** * 看 initHandlerMethods(),观察是如何实现加载 HandlerMethod */ protected void initHandlerMethods() { // getCandidateBeanNames:Object.class 相当于拿到当前容器(一般都是当前容器)内所有的 Bean 定义信息 // 如果容器隔离好的话,这里一般只会拿到 @Controller 标注的 web 组件,以及其它相关 web 组件的,不会非常多的 for (String beanName : getCandidateBeanNames()) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { // BeanName 不是以这个打头的,这里才会 process 这个 BeanName processCandidateBean(beanName); // 会在每个Bean里面找处理方法,HandlerMethod,然后注册进去 } } handlerMethodsInitialized(getHandlerMethods()); // 就是输出一句日志 } /** * 确定指定的候选 bean 的类型,如果标识为 Handler 类型,则调用 detectHandlerMethods 方法 * isHandler(beanType):判断这个 type 是否为 Handler 类型,它是个抽象方法,由子类去决定到底啥才叫 Handler * `RequestMappingHandlerMapping`的判断依据为:该类上标注了 @Controller 注解或者 @Component 注解,就算作是一个 Handler * 所以此处:@Controller 起到了一个特殊的作用,不能等价于 @Component 的 */ protected void processCandidateBean(String beanName) { Class<?> beanType = null; try { beanType = obtainApplicationContext().getType(beanName); } catch (Throwable ex) { // 即使抛出异常,程序也不会终止 if (logger.isTraceEnabled()) { logger.trace("Could not resolve type for bean '" + beanName + "'", ex); } } if (beanType != null && isHandler(beanType)) { detectHandlerMethods(beanName); } } /** * 在指定的 Handler 的 bean 中查找处理程序方法 Methods,找到就注册进去:mappingRegistry */ protected void detectHandlerMethods(Object handler) { Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) { Class<?> userType = ClassUtils.getUserClass(handlerType); // https://blog.csdn.net/f641385712/article/details/89380067 // 主要用到了 MethodIntrospector.selectMethods 这个内省方法工具类的这个工具方法,去找指定 Class 里面,符合条件的方法们 Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup<T>) method -> { try { // 过滤 Method 的核心逻辑 // getMappingForMethod 属于一个抽象方法,由子类去决定它的寻找规则:什么才算作一个处理器方法 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)); } // 把找到的 Method 一个个遍历,注册进去 methods.forEach((method, mapping) -> { // 找到这个可调用的方法(AopUtils.selectInvocableMethod) Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); registerHandlerMethod(handler, invocableMethod, mapping); }); } }
该抽象类完成了所有的 Handler 以及 handler 里面所有的 HandlerMethod 的模版操作,但是决定哪些 Bean 是 Handler 类,哪些方法才是 HandlerMathod,这些逻辑都是交给子类自己去实现,并没有把 Handler 的实现方式定死,允许不同
这里面有个核心内容:那就是注册 handlerMethod,是交给 AbstractHandlerMethodMapping 的一个内部类 MappingRegistry 去完成的,用来专门维持所有的映射关系,并提供查找方法
AbstractHandlerMethodMapping.MappingRegistry:内部类注册中心
维护几个 Map(键值对),用来存储映射的信息, 还有一个 MappingRegistration 专门保存注册信息
MappingRegistration:就是一个 private 的内部类,维护着 T mapping、HandlerMethod handlerMethod、List<String> directUrls、String mappingName 等信息,提供 get 方法访问。没有任何其它逻辑
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean { class MappingRegistry { private final Map<T, MappingRegistration<T>> registry = new HashMap<>(); // mapping 对应的 MappingRegistration 对象 private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>(); // 保存着 mapping 和 HandlerMethod 的对应关系 // 保存着 URL 与匹配条件(mapping)的对应关系,当然这里的 URL 是 pattern 式的,可以使用通配符 // 这里的 Map 不是普通的 Map,而是 MultiValueMap,它是个多值 Map。其实它的 value 是一个 list 类型的值 // 至于为何是多值?有这么一种情况,URL 都是 /api/v1/hello,但是有的是 get post delete 等方法,所以有可能是会匹配到多个 MappingInfo 的 private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>(); // 这个 Map 是 SpringMVC4.1 新增的(毕竟这个策略接口 HandlerMethodMappingNamingStrategy 在 Spring4.1 后才有,这里的 name 是它生成出来的) // 保存着 name 和 HandlerMethod 的对应关系(一个 name 可以有多个 HandlerMethod) private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>(); private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>(); // 跨域 private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // 读写锁,读写分离,提高启动效率 // 读锁提供给外部访问,写锁自己放在内部即可 public void acquireReadLock() { this.readWriteLock.readLock().lock(); } public void releaseReadLock() { this.readWriteLock.readLock().unlock(); } /** * 注册 Mapping 和 handler,以及 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); // 此处注意:都是 new HandlerMethod() 了一个新的出来 // 同样的:一个 URL Mapping 只能对应一个 Handler,这里可能会出现常见的一个异常信息:Ambiguous mapping. Cannot map XXX validateMethodMapping(handlerMethod, mapping); this.mappingLookup.put(mapping, handlerMethod); // 缓存 Mapping 和 handlerMethod 的关系 // 保存 url 和 RequestMappingInfo(mapping)对应关系 // 这里注意:多个 url 可能对应着同一个 mappingInfo,毕竟 @RequestMapping 的 url 是可以写多个的 List<String> directUrls = getDirectUrls(mapping); for (String url : directUrls) { this.urlLookup.add(url, mapping); } // 保存 name 和 handlerMethod 的关系,同样也是一对多 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); } // 注册 mapping 和 MappingRegistration 的关系 this.registry.put(mapping, MappingRegistration<>(mapping, handlerMethod, directUrls, name)); } finally { this.readWriteLock.writeLock().unlock(); // 释放锁 } } public void unregister(T mapping) { // 相当于进行一次逆向操作 private void removeMappingName(MappingRegistration<T> definition) {
这个注册中心,核心是保存了多个 Map 映射关系,相当于缓存下来。在请求过来时需要查找的时候,可以迅速定位到处理器
再看 AbstractHandlerMethodMapping 对父类抽象方法:getHandlerInternal 的实现
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean { @Override protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); // 要进行匹配的请求的 URI path request.setAttribute(LOOKUP_PATH, lookupPath); this.mappingRegistry.acquireReadLock(); try { HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); //委托给方法 lookupHandlerMethod() 去找到一个 HandlerMethod 去最终处理 return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally { this.mappingRegistry.releaseReadLock(); } } @Nullable protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); // Match 是一个 private class,内部就两个属性:T mapping 和 HandlerMethod handlerMethod // 根据 lookupPath 去注册中心里查找 mappingInfos,因为一个具体的 url 可能匹配上多个 MappingInfo 的 // 至于为何是多值?有这么一种情况,URL 都是 /api/v1/hello,但是有的是 get post delete 等方法,当然还有可能是 headers/consumes 等等不一样,都算多个的,所以有可能是会匹配到多个 MappingInfo 的 // 所有这个里可以匹配出多个出来。比如 /hello 匹配出 GET、POST、PUT 都成,所以 size 可以为 3 List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); if (directPathMatches != null) { // 依赖于子类实现的抽象方法:getMatchingMapping(),看看到底匹不匹配,而不仅仅是 URL 匹配就行 // 比如还有 method、headers、consumes 等等这些不同,都代表着不同的 MappingInfo 的 // 最终匹配上的,会 new Match() 放进 matches 里面去 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()) { // 但凡只要找到了一个匹配,就进来这里了,请注意:因为到这里,匹配上的可能还不止一个,所以才需要继续处理 // 排序后的最佳匹配为get(0) Match bestMatch = matches.get(0); if (matches.size() > 1) { // 如果总的匹配个数大于 1 的话 // getMappingComparator 这个方法也是抽象方法由子类去实现的。 // 比如:`RequestMappingInfoHandlerMapping` 的实现为先比较 Method,patterns、params Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); matches.sort(comparator); bestMatch = matches.get(0); if (logger.isTraceEnabled()) { logger.trace(matches.size() + " matching mappings: " + matches); } if (CorsUtils.isPreFlightRequest(request)) { return PREFLIGHT_AMBIGUOUS_MATCH; } Match secondBestMatch = matches.get(1); // 次最佳匹配 // 如果发现次最佳匹配和最佳匹配比较是相等的,那就报错 // Ambiguous handler methods mapped for // 注意:这个是运行时的检查,在启动的时候是检查不出来的,所以运行期的这个检查也是很有必要的,否则就会出现意想不到的效果 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 的属性里面 request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod); // 它也是做了一件事:request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath) handleMatch(bestMatch.mapping, lookupPath, request); // 最终返回的是 HandlerMethod return bestMatch.handlerMethod; } else { // 一个都没匹配上,handleNoMatch 这个方法虽然不是抽象方法,但子类 RequestMappingInfoHandlerMapping 有复写此方法 return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); } } private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) { // 因为上面说了 mappings 可能会有多个,比如 get post put 的都算,这里就是要进行筛选出所有 match 上的 for (T mapping : mappings) { // 只有 RequestMappingInfoHandlerMapping 实现了一句话:return info.getMatchingCondition(request); // 因此 RequestMappingInfo#getMatchingCondition() 方法里大有文章可为 // 它会对所有的 methods、params、headers... 都进行匹配,但凡匹配不上的就返回 null T match = getMatchingMapping(mapping, request); if (match != null) { matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping))); } } }
RequestMappingInfoHandlerMapping
提供匹配条件 RequestMappingInfo 的解析处理,它主要做的事就是确定了泛型类型为:RequestMappingInfo,然后很多方法都依托它来完成判定逻辑
而 RequestMappingInfo 的构建工作,SpringMVC 理论上是可以允许有多种方案。SpringMVC 给出的唯一实现类为 RequestMappingHandlerMapping,SpringMVC 目前的唯一构造方案:通过 @RequestMapping 来构造一个 RequestMappingInfo
/** * @since 3.1 此处泛型为:RequestMappingInfo 用这个类来表示 mapping 映射关系、参数、条件等 */ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> { private static final Method HTTP_OPTIONS_HANDLE_METHOD; // 专门处理 Http 的 Options 方法的 HandlerMethod static { try { HTTP_OPTIONS_HANDLE_METHOD = HttpOptionsHandler.class.getMethod("handle"); } catch (NoSuchMethodException ex) { // Should never happen throw new IllegalStateException("Failed to retrieve internal handler method for HTTP OPTIONS", ex); } } protected RequestMappingInfoHandlerMapping() { // 构造函数:给 set 了一个 HandlerMethodMappingNamingStrategy setHandlerMethodMappingNamingStrategy(new RequestMappingInfoHandlerMethodMappingNamingStrategy()); } @Override protected Set<String> getMappingPathPatterns(RequestMappingInfo info) { // 复写父类的抽象方法:获取 mappings 里面的 patters 们 return info.getPatternsCondition().getPatterns(); } @Override protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) { return info.getMatchingCondition(request); // 校验看看这个 Mapping 是否能匹配上这个 request,若能匹配上就返回一个 RequestMappingInfo } @Override protected Comparator<RequestMappingInfo> getMappingComparator(final HttpServletRequest request) { return (info1, info2) -> info1.compareTo(info2, request); }
RequestMappingInfo
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> { private static final PatternsRequestCondition EMPTY_PATTERNS = new PatternsRequestCondition(); private static final RequestMethodsRequestCondition EMPTY_REQUEST_METHODS = new RequestMethodsRequestCondition(); private static final ParamsRequestCondition EMPTY_PARAMS = new ParamsRequestCondition(); private static final HeadersRequestCondition EMPTY_HEADERS = new HeadersRequestCondition(); private static final ConsumesRequestCondition EMPTY_CONSUMES = new ConsumesRequestCondition(); private static final ProducesRequestCondition EMPTY_PRODUCES = new ProducesRequestCondition(); private static final RequestConditionHolder EMPTY_CUSTOM = new RequestConditionHolder(null); @Nullable private final String name; // 这些个匹配器都继承自 AbstractRequestCondition,会进行各自的匹配工作 // 它们顶级抽象接口为:RequestCondition @since 3.1 private final PatternsRequestCondition patternsCondition; private final RequestMethodsRequestCondition methodsCondition; private final ParamsRequestCondition paramsCondition; private final HeadersRequestCondition headersCondition; private final ConsumesRequestCondition consumesCondition; private final ProducesRequestCondition producesCondition; private final RequestConditionHolder customConditionHolder; private final int hashCode; public RequestMappingInfo(@Nullable String name, @Nullable PatternsRequestCondition patterns, @Nullable RequestMethodsRequestCondition methods, @Nullable ParamsRequestCondition params, @Nullable HeadersRequestCondition headers, @Nullable ConsumesRequestCondition consumes, @Nullable ProducesRequestCondition produces, @Nullable RequestCondition<?> custom) { this.name = (StringUtils.hasText(name) ? name : null); this.patternsCondition = (patterns != null ? patterns : EMPTY_PATTERNS); this.methodsCondition = (methods != null ? methods : EMPTY_REQUEST_METHODS); this.paramsCondition = (params != null ? params : EMPTY_PARAMS); this.headersCondition = (headers != null ? headers : EMPTY_HEADERS); this.consumesCondition = (consumes != null ? consumes : EMPTY_CONSUMES); this.producesCondition = (produces != null ? produces : EMPTY_PRODUCES); this.customConditionHolder = (custom != null ? new RequestConditionHolder(custom) : EMPTY_CUSTOM); this.hashCode = calculateHashCode( this.patternsCondition, this.methodsCondition, this.paramsCondition, this.headersCondition, this.consumesCondition, this.producesCondition, this.customConditionHolder); } /** * 因为类上和方法上都可能会有 @RequestMapping 注解,所以这里是把语意合并,该方法来自顶层接口 */ @Override public RequestMappingInfo combine(RequestMappingInfo other) { String name = combineNames(other); PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition); RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition); ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition); HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition); ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition); ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition); RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder); return new RequestMappingInfo(name, patterns, methods, params, headers, consumes, produces, custom.getCondition()); } /** * 合并后,就开始发挥作用了,该接口来自于顶层接口 */ @Override @Nullable public RequestMappingInfo getMatchingCondition(HttpServletRequest request) { RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request); if (methods == null) { return null; } ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request); if (params == null) { return null; } HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request); if (headers == null) { return null; } ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request); if (consumes == null) { return null; } ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request); if (produces == null) { return null; } PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request); if (patterns == null) { return null; } RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request); if (custom == null) { return null; } return new RequestMappingInfo(this.name, patterns, methods, params, headers, consumes, produces, custom.getCondition()); }
SpringMVC 请求 URL 带后缀匹配的情况,如 /hello.json 也能匹配 /hello
RequestMappingInfoHandlerMapping 在处理 http 请求的时候, 如果请求 url 有后缀,如果找不到精确匹配的那个 @RequestMapping 方法。那么,就把后缀去掉,然后 .* 去匹配,这样,一般都可以匹配。
比如有一个 @RequestMapping("/rest"),那么精确匹配的情况下,只会匹配 /rest 请求。如果前端发来一个 /rest.abcdef 这样的请求,又没有配置 @RequestMapping("/rest.abcdef") 这样映射的情况下,@RequestMapping("/rest") 就会生效。
究其原因就到了 PatternsRequestCondition 这个类上,具体实现是它的匹配逻辑来决定的。
public class PatternsRequestCondition extends AbstractRequestCondition<PatternsRequestCondition> { private final static Set<String> EMPTY_PATH_PATTERN = Collections.singleton(""); private final Set<String> patterns; private final UrlPathHelper pathHelper; private final PathMatcher pathMatcher; private final boolean useSuffixPatternMatch; private final boolean useTrailingSlashMatch; private final List<String> fileExtensions = new ArrayList<>(); @Override @Nullable public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) { // patterns表示此MappingInfo可以匹配的值们。一般对应@RequestMapping注解上的patters数组的值 // 拿到待匹配的值,比如此处为"/hello.json" String lookupPath = this.pathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH); // 最主要就是这个方法了,它拿着这个lookupPath匹配 List<String> matches = getMatchingPatterns(lookupPath); // 此处如果为empty,就返回null了 return !matches.isEmpty() ? new PatternsRequestCondition(new LinkedHashSet<>(matches), this) : null; } public List<String> getMatchingPatterns(String lookupPath) { List<String> matches = null; for (String pattern : this.patterns) { // 最最最重点就是在 getMatchingPattern() 这个方法里,拿着 lookupPath 和 pattern 看它俩合拍不 String match = getMatchingPattern(pattern, lookupPath); if (match != null) { matches = (matches != null ? matches : new ArrayList<>()); matches.add(match); } } if (matches == null) { return Collections.emptyList(); } if (matches.size() > 1) { // 解释一下为何匹配的可能是多个。因为 url 匹配上了,但是还有可能 @RequestMapping 的其余属性匹配不上,所以此处需要注意,是可能匹配上多个的,最终是唯一匹配就成 matches.sort(this.pathMatcher.getPatternComparator(lookupPath)); } return matches; } /** * url 的真正匹配规则,非常重要 * 注意这个方法的取名,上面是负数,这里是单数 */ @Nullable private String getMatchingPattern(String pattern, String lookupPath) { if (pattern.equals(lookupPath)) { // 完全相等,那就不继续了 return pattern; } // 注意了:useSuffixPatternMatch 这个属性就是我们最终要关闭后缀匹配的关键 // 这个值默外部给传的 true(其实内部默认值是 boolean 类型为 false) if (this.useSuffixPatternMatch) { // 这个意思是若 useSuffixPatternMatch=true,就支持后缀匹配。还可以配置 fileExtensions 让只支持我们自定义的后缀匹配,而不是下面最终的 .* 全部支持 if (!this.fileExtensions.isEmpty() && lookupPath.indexOf('.') != -1) { for (String extension : this.fileExtensions) { if (this.pathMatcher.match(pattern + extension, lookupPath)) { return pattern + extension; } } } else { // 若没有配置指定后缀匹配,并且你的 handler 也没有 .* 这样匹配的,那就默认你的 pattern 就给你添加上后缀 ".*",表示匹配所有请求的 url 的后缀 boolean hasSuffix = pattern.indexOf('.') != -1; if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) { return pattern + ".*"; } } } if (this.pathMatcher.match(pattern, lookupPath)) { // 若匹配上了,直接返回此 patter return pattern; } // 这又是它支持的匹配规则。默认 useTrailingSlashMatch 它也是 true // 这就是为何我们的 /hello/ 也能匹配上 /hello 的原因 if (this.useTrailingSlashMatch) { if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) { return pattern + "/"; } } return null; }
SpringMVC 中配置此功能
@Configuration @EnableWebMvc public class WebMvcConfig extends WebMvcConfigurerAdapter { // 关闭后缀名匹配,关闭最后一个 / 匹配 @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.setUseSuffixPatternMatch(false); configurer.setUseTrailingSlashMatch(false); } }
RequestMappingHandlerMapping
唯一实现类,根据 @RequestMapping 注解生成 RequestMappingInfo,同时提供 isHandler 实现。直到这个具体实现类,才与具体的实现方式 @RequestMapping 做了强绑定,有了三层抽象的实现,留给本类需要实现的功能已经不是非常的多了
/** * @since 3.1 Spring3.1 才提供的这种注解扫描的方式的支持,它也实现了 MatchableHandlerMapping 分支的接口 * EmbeddedValueResolverAware 接口:说明要支持解析 Spring 的表达式 */ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping implements MatchableHandlerMapping, EmbeddedValueResolverAware { private boolean useSuffixPatternMatch = true; private boolean useRegisteredSuffixPatternMatch = false; private boolean useTrailingSlashMatch = true; private Map<String, Predicate<Class<?>>> pathPrefixes = new LinkedHashMap<>(); private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager(); @Nullable private StringValueResolver embeddedValueResolver; private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration(); /** * 配置要应用于控制器方法的路径前缀 * * @since 5.1:Spring5.1 才出来的新特性 * 前缀用于丰富每个 @RequestMapping 方法的映射,至于匹不匹配由 Predicate 来决定,有种前缀分类的效果 * 推荐使用 Spring5.1 提供的类:org.springframework.web.method.HandlerTypePredicate */ public void setPathPrefixes(Map<String, Predicate<Class<?>>> prefixes) { this.pathPrefixes = Collections.unmodifiableMap(new LinkedHashMap<>(prefixes)); } /** * @since 5.1 * 注意 pathPrefixes 是只读的,因为上面 Collections.unmodifiableMap 了,有可能只是个空 Map */ public Map<String, Predicate<Class<?>>> getPathPrefixes() { return this.pathPrefixes; } @Override @SuppressWarnings("deprecation") public void afterPropertiesSet() { this.config = new RequestMappingInfo.BuilderConfiguration(); // 对 RequestMappingInfo 的配置进行初始化赋值 this.config.setUrlPathHelper(getUrlPathHelper()); // 设置 urlPathHelper 默认为 UrlPathHelper.class this.config.setPathMatcher(getPathMatcher()); // 默认为 AntPathMatcher,路径匹配校验器 this.config.setSuffixPatternMatch(useSuffixPatternMatch()); // 是否支持后缀补充,默认为 true this.config.setTrailingSlashMatch(useTrailingSlashMatch()); // 是否添加 / 后缀,默认为 true this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch()); // 是否采用 mediaType 匹配模式,比如 .json/.xml 模式的匹配,默认为 false this.config.setContentNegotiationManager(getContentNegotiationManager()); // mediaType处理类:ContentNegotiationManager super.afterPropertiesSet(); // 此处,必须要调用父类的方法 } /** * 判断该类,是否是一个 handler(此处就体现出 @Controller 注解的特殊性了) * 这也是为何我们的 XXXController 用 @Bean 申明是无效的原因(前提是类上没有 @RequestMapping 注解,否则也是可以的) * 因此个人建议:为了普适性,类上的 @RequestMapping 也统一要求加上,即使你不写 @Value 也没关系,这样是最好的 */ @Override protected boolean isHandler(Class<?> beanType) { return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)); } /** * 父类:AbstractHandlerMethodMapping#detectHandlerMethods 的时候,会去该类里面找所有的指定的方法 * 而什么叫指定的?就是靠这个来判定方法来确定是否符合条件 */ @Override @Nullable protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { RequestMappingInfo info = createRequestMappingInfo(method); // 第一步:先拿到方法上的 info if (info != null) { // 方法上有。再第二步:拿到类上的 info RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType); if (typeInfo != null) { // 倘若类上面也有,那就 combine 把两者结合 // combile 的逻辑基如下: // names:name1+#+name2 // path:路径拼接起来作为全路径(容错了方法里没有 / 的情况) // method、params、headers:取并集 // consumes、produces:以方法的为准,没有指定再取类上的 // custom:谁有取谁的。若都有:那就看 custom 具体实现的 .combine 方法去决定,简单的说就是交给调用者了 info = typeInfo.combine(info); } // 在 Spring5.1 之后还要处理这个前缀匹配 // 根据这个类,去找看有没有前缀 getPathPrefix():entry.getValue().test(handlerType) = true 算是匹配上了 // 备注:也支持 ${os.name} 这样的语法拿值,可以把前缀也写在专门的配置文件里 String prefix = getPathPrefix(handlerType); if (prefix != null) { // RequestMappingInfo.paths(prefix) 相当于统一在前面加上这个前缀 info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info); } } return info; } @Nullable private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) { // 此处使用的是 findMergedAnnotation,这也就是为什么虽然 @RequestMapping 它并不具有继承的特性,但是你子类仍然有继承的效果的原因 RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); // 这里进行了区分处理,如果是 Class 的话、如果是 Method 的话 // 这里返回的是一个 condition,也就是看看要不要处理这个请求的条件 RequestCondition<?> condition = (element instanceof Class ? getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element)); // 这个 createRequestMappingInfo 就是根据一个 @RequestMapping 以及一个 condition 创建一个 // 显然如果没有找到此注解,这里就返回 null 了,表明这个方法啥的就不是一个 info return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null); } /** * 它俩都是返回的 null。protected 方法留给子类复写,子类可以据此自己定义一套自己的规则来限制匹配 * Provide a custom method-level request condition. * 它相当于在 Spring MVC 默认规则的基础上,用户还可以自定义条件进行处理 */ @Nullable protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) { return null; } @Nullable protected RequestCondition<?> getCustomMethodCondition(Method method) { return null; } /** * 根据 @RequestMapping 创建一个 RequestMappingInfo */ protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) { RequestMappingInfo.Builder builder = RequestMappingInfo // 强大的地方在此处:path 支持 /api/v1/${os.name}/hello 这样形式动态的获取值 // 也就是说 URL 还可以从配置文件里面读取 // @GetMapping("/${os.name}/hello") // 支持从配置文件里读取此值 .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); } // 注意此处:把当前的 config 设置进去了 return builder.options(this.config).build(); } @Override protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) { // 支持了 @CrossOrigin 注解,Spring4.2 提供的注解
全局统一配置 Controller 前缀,如 /api/v1
@Configuration @EnableWebMvc public class WebMvcConfig extends WebMvcConfigurerAdapter { @Override public void configurePathMatch(PathMatchConfigurer configurer) { // configurer.setUseSuffixPatternMatch(false); // 关闭后缀名匹配 // configurer.setUseTrailingSlashMatch(false); // 关闭最后一个 / 匹配 // 这样 HelloController 上的方法自动就会有此前缀了,而别的 controller 上是不会有的 // 注意:这是 Spring5.1 后才支持的新特性 configurer.addPathPrefix("/api/v1", clazz -> clazz.isAssignableFrom(HelloController.class)); // 使用 Spring 提供的 HandlerTypePredicate,更加的强大 // 若添加了两prefix都可以作用在某个Controller上,那么会按照放入的顺序(因为它是LinkedHashMap)以先匹配上的为准 HandlerTypePredicate predicate = HandlerTypePredicate.forBasePackage("com"); // HandlerTypePredicate predicate = HandlerTypePredicate.forBasePackageClass(HelloController.class); // HandlerTypePredicate predicate = HandlerTypePredicate.forAssignableType(...); // HandlerTypePredicate predicate = HandlerTypePredicate.forAnnotation(...); // HandlerTypePredicate predicate = HandlerTypePredicate.builder() // .basePackage() // .basePackageClass() // .build(); configurer.addPathPrefix("/api/v2", predicate); } }
RequestMappingHandlerMapping 向容器中注册的时候,检测到实现了 InitializingBean 接口,容器去执行 afterPropertiesSet(),在 afterPropertiesSet() 中完成 Controller 中方法的映射
总结
Spring MVC 在启动时会扫描所有的 @RequestMapping 并封装成对应的 RequestMapingInfo。
一个请求过来会与 RequestMapingInfo 进行逐个比较,找到最适合的那个 RequestMapingInfo。
Spring MVC 通过 HandlerMapping 建立起了 Url Pattern 和 Handler 的对应关系,这样任何一个 URL 请求过来时,就可以快速定位一个唯一的 Handler,然后交给其进行处理了