前面跟小伙伴们分享了 SpringMVC 一个大致的初始化流程以及请求的大致处理流程,在请求处理过程中,涉及到九大组件,分别是:
成都创新互联从2013年成立,先为大祥等服务建站,大祥等地企业,进行企业商务咨询服务。为大祥企业网站制作PC+手机+微官网三网同步一站式服务解决您的所有建站问题。
这些组件相信小伙伴们在日常开发中多多少少都有涉及到,如果你对这些组件感到陌生,可以在公众号后台回复 ssm,免费获取松哥的入门视频教程。
那么接下来的几篇文章,松哥想和大家深入分析这九大组件,从用法到源码,挨个分析,今天我们就先来看看这九大组件中的第一个 HandlerMapping。
HandlerMapping 叫做处理器映射器,它的作用就是根据当前 request 找到对应的 Handler 和 Interceptor,然后封装成一个 HandlerExecutionChain 对象返回,我们来看下 HandlerMapping 接口:
- public interface HandlerMapping {
- String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
- @Deprecated
- String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
- String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
- String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
- String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
- String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
- String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
- String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
- default boolean usesPathPatterns() {
- return false;
- }
- @Nullable
- HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
- }
可以看到,除了一堆声明的常量外,其实就一个需要实现的方法 getHandler,该方法的返回值就是我们所了解到的 HandlerExecutionChain。
HandlerMapping 的继承关系如下:
这个继承关系虽然看着有点绕,其实仔细观察就两大类:
其他的都是一些辅助接口。
AbstractHandlerMethodMapping 体系下的都是根据方法名进行匹配的,而 AbstractUrlHandlerMapping 体系下的都是根据 URL 路径进行匹配的,这两者有一个共同的父类 AbstractHandlerMapping,接下来我们就对这三个关键类进行详细分析。
AbstractHandlerMapping 实现了 HandlerMapping 接口,无论是通过 URL 进行匹配还是通过方法名进行匹配,都是通过继承 AbstractHandlerMapping 来实现的,所以 AbstractHandlerMapping 所做的事情其实就是一些公共的事情,将以一些需要具体处理的事情则交给子类去处理,这其实就是典型的模版方法模式。
AbstractHandlerMapping 间接继承自 ApplicationObjectSupport,并重写了 initApplicationContext 方法(其实该方法也是一个模版方法),这也是 AbstractHandlerMapping 的初始化入口方法,我们一起来看下:
- @Override
- protected void initApplicationContext() throws BeansException {
- extendInterceptors(this.interceptors);
- detectMappedInterceptors(this.adaptedInterceptors);
- initInterceptors();
- }
三个方法都和拦截器有关。
extendInterceptors
- protected void extendInterceptors(List
- }
extendInterceptors 是一个模版方法,可以在子类中实现,子类实现了该方法之后,可以对拦截器进行添加、删除或者修改,不过在 SpringMVC 的具体实现中,其实这个方法并没有在子类中进行实现。
detectMappedInterceptors
- protected void detectMappedInterceptors(List
mappedInterceptors) { - mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors(
- obtainApplicationContext(), MappedInterceptor.class, true, false).values());
- }
detectMappedInterceptors 方法会从 SpringMVC 容器以及 Spring 容器中查找所有 MappedInterceptor 类型的 Bean,查找到之后添加到 mappedInterceptors 属性中(其实就是全局的 adaptedInterceptors 属性)。一般来说,我们定义好一个拦截器之后,还要在 XML 文件中配置该拦截器,拦截器以及各种配置信息,最终就会被封装成一个 MappedInterceptor 对象。
initInterceptors
- 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));
- }
- }
- }
initInterceptors 方法主要是进行拦截器的初始化操作,具体内容是将 interceptors 集合中的拦截器添加到 adaptedInterceptors 集合中。
至此,我们看到,所有拦截器最终都会被存入 adaptedInterceptors 变量中。
AbstractHandlerMapping 的初始化其实也就是拦截器的初始化过程。
为什么 AbstractHandlerMapping 中对拦截器如此重视呢?其实不是重视,大家想想,AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping 最大的区别在于查找处理器的区别,一旦处理器找到了,再去找拦截器,但是拦截器都是统一的,并没有什么明显区别,所以拦截器就统一在 AbstractHandlerMapping 中进行处理,而不会去 AbstractUrlHandlerMapping 或者 AbstractHandlerMethodMapping 中处理。
接下来我们再来看看 AbstractHandlerMapping#getHandler 方法,看看处理器是如何获取到的:
- @Override
- @Nullable
- public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
- Object handler = getHandlerInternal(request);
- if (handler == null) {
- handler = getDefaultHandler();
- }
- if (handler == null) {
- return null;
- }
- // Bean name or resolved handler?
- if (handler instanceof String) {
- String handlerName = (String) handler;
- handler = obtainApplicationContext().getBean(handlerName);
- }
- // Ensure presence of cached lookupPath for interceptors and others
- if (!ServletRequestPathUtils.hasCachedPath(request)) {
- initLookupPath(request);
- }
- HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
- if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
- CorsConfiguration config = getCorsConfiguration(handler, request);
- if (getCorsConfigurationSource() != null) {
- CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
- config = (globalConfig != null ? globalConfig.combine(config) : config);
- }
- if (config != null) {
- config.validateAllowCredentials();
- }
- executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
- }
- return executionChain;
- }
这个方法的执行流程是这样的:
接下来我们再来看看第五步的 getHandlerExecutionChain 方法的执行逻辑,正是在这个方法里边把 handler 变成了 HandlerExecutionChain:
- protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
- HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
- (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
- for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
- if (interceptor instanceof MappedInterceptor) {
- MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
- if (mappedInterceptor.matches(request)) {
- chain.addInterceptor(mappedInterceptor.getInterceptor());
- }
- }
- else {
- chain.addInterceptor(interceptor);
- }
- }
- return chain;
- }
这里直接根据已有的 handler 创建一个新的 HandlerExecutionChain 对象,然后遍历 adaptedInterceptors 集合,该集合里存放的都是拦截器,如果拦截器的类型是 MappedInterceptor,则调用 matches 方法去匹配一下,看一下是否是拦截当前请求的拦截器,如果是,则调用 chain.addInterceptor 方法加入到 HandlerExecutionChain 对象中;如果就是一个普通拦截器,则直接加入到 HandlerExecutionChain 对象中。
这就是 AbstractHandlerMapping#getHandler 方法的大致逻辑,可以看到,这里留了一个模版方法 getHandlerInternal 在子类中实现,接下来我们就来看看它的子类。
AbstractUrlHandlerMapping,看名字就知道,都是按照 URL 地址来进行匹配的,它的原理就是将 URL 地址与对应的 Handler 保存在同一个 Map 中,当调用 getHandlerInternal 方法时,就根据请求的 URL 去 Map 中找到对应的 Handler 返回就行了。
这里我们就先从他的 getHandlerInternal 方法开始看起:
- @Override
- @Nullable
- protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
- String lookupPath = initLookupPath(request);
- Object handler;
- if (usesPathPatterns()) {
- RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request);
- handler = lookupHandler(path, lookupPath, request);
- }
- else {
- handler = lookupHandler(lookupPath, request);
- }
- if (handler == null) {
- // We need to care for the default handler directly, since we need to
- // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
- Object rawHandler = null;
- if (StringUtils.matchesCharacter(lookupPath, '/')) {
- rawHandler = getRootHandler();
- }
- if (rawHandler == null) {
- rawHandler = getDefaultHandler();
- }
- if (rawHandler != null) {
- // Bean name or resolved handler?
- if (rawHandler instanceof String) {
- String handlerName = (String) rawHandler;
- rawHandler = obtainApplicationContext().getBean(handlerName);
- }
- validateHandler(rawHandler, request);
- handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
- }
- }
- return handler;
- }
这就是整个 getHandlerInternal 方法的逻辑,实际上并不难,里边主要涉及到 lookupHandler 和 buildPathExposingHandler 两个方法,需要和大家详细介绍下,我们分别来看。
lookupHandler
lookupHandler 有两个,我们分别来看。
- @Nullable
- protected Object lookupHandler(String lookupPath, HttpServletRequest request) throws Exception {
- Object handler = getDirectMatch(lookupPath, request);
- if (handler != null) {
- return handler;
- }
- // Pattern match?
- List
matchingPatterns = new ArrayList<>(); - for (String registeredPattern : this.handlerMap.keySet()) {
- if (getPathMatcher().match(registeredPattern, lookupPath)) {
- matchingPatterns.add(registeredPattern);
- }
- else if (useTrailingSlashMatch()) {
- if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", lookupPath)) {
- matchingPatterns.add(registeredPattern + "/");
- }
- }
- }
- String bestMatch = null;
- Comparator
patternComparator = getPathMatcher().getPatternComparator(lookupPath); - if (!matchingPatterns.isEmpty()) {
- matchingPatterns.sort(patternComparator);
- bestMatch = matchingPatterns.get(0);
- }
- if (bestMatch != null) {
- handler = this.handlerMap.get(bestMatch);
- if (handler == null) {
- if (bestMatch.endsWith("/")) {
- handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
- }
- if (handler == null) {
- throw new IllegalStateException(
- "Could not find handler for best pattern match [" + bestMatch + "]");
- }
- }
- // Bean name or resolved handler?
- if (handler instanceof String) {
- String handlerName = (String) handler;
- handler = obtainApplicationContext().getBean(handlerName);
- }
- validateHandler(handler, request);
- String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, lookupPath);
- // There might be multiple 'best patterns', let's make sure we have the correct URI template variables
- // for all of them
- Map
uriTemplateVariables = new LinkedHashMap<>(); - for (String matchingPattern : matchingPatterns) {
- if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
- Map
vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, lookupPath); - Map
decodedVars = getUrlPathHelper().decodePathVariables(request, vars); - uriTemplateVariables.putAll(decodedVars);
- }
- }
- return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
- }
- // No handler found...
- return null;
- }
- @Nullable
- private Object getDirectMatch(String urlPath, HttpServletRequest request) throws Exception {
- Object handler = this.handlerMap.get(urlPath);
- if (handler != null) {
- // Bean name or resolved handler?
- if (handler instanceof String) {
- String handlerName = (String) handler;
- handler = obtainApplicationContext().getBean(handlerName);
- }
- validateHandler(handler, request);
- return buildPathExposingHandler(handler, urlPath, urlPath, null);
- }
- return null;
- }
1.这里首先调用 getDirectMatch 方法直接去 handlerMap 中找对应的处理器,handlerMap 中就保存了请求 URL 和处理器的映射关系,具体的查找过程就是先去 handlerMap 中找,找到了,如果是 String,则去 Spring 容器中找对应的 Bean,然后调用 validateHandler 方法去验证(实际上没有验证,前面已经说了),最后调用 buildPathExposingHandler 方法添加拦截器。
2.如果 getDirectMatch 方法返回值不为 null,则直接将查找到的 handler 返回,方法到此为止。那么什么情况下 getDirectMatch 方法的返回值不为 null 呢?简单来收就是没有使用通配符的情况下,请求地址中没有通配符,一个请求地址对应一个处理器,只有这种情况,getDirectMatch 方法返回值才不为 null,因为 handlerMap 中保存的是代码的定义,比如我们定义代码的时候,某个处理器的访问路径可能带有通配符,但是当我们真正发起请求的时候,请求路径里是没有通配符的,这个时候再去 handlerMap 中就找不对对应的处理器了。如果用到了定义接口时用到了通配符,则需要在下面的代码中继续处理。
3.接下来处理通配符的情况。首先定义 matchingPatterns 集合,将当前请求路径和 handlerMap 集合中保存的请求路径规则进行对比,凡是能匹配上的规则都直接存入 matchingPatterns 集合中。具体处理中,还有一个 useTrailingSlashMatch 的可能,有的小伙伴 SpringMVC 用的不熟练,看到这里可能就懵了,这里是这样的,SpringMVC 中,默认是可以匹配结尾 / 的,举个简单例子,如果你定义的接口是/user,那么请求路径可以是 /user 也可以 /user/,这两种默认都是支持的,所以这里的 useTrailingSlashMatch 分支主要是处理后面这种情况,处理方式很简单,就在 registeredPattern 后面加上 / 然后继续和请求路径进行匹配。
4.由于一个请求 URL 可能会和定义的多个接口匹配上,所以 matchingPatterns 变量是一个数组,接下来就要对 matchingPatterns 进行排序,排序完成后,选择排序后的第一项作为最佳选项赋值给 bestMatch 变量。默认的排序规则是 AntPatternComparator,当然开发者也可以自定义。AntPatternComparator 中定义的优先级如下:
路由配置 | 优先级 |
---|---|
不含任何特殊符号的路径,如:配置路由/a/b/c |
第一优先级 |
带有{} 的路径,如:/a/{b}/c |
第二优先级 |
带有正则的路径,如:/a/{regex:\d{3}}/c |
第三优先级 |
带有* 的路径,如:/a/b/* |
第四优先级 |
带有** 的路径,如:/a/b/** |
第五优先级 |
最模糊的匹配:/** |
最低优先级 |
5.找到 bestMatch 之后,接下来再根据 bestMatch 去 handlerMap 中找到对应的处理器,直接找如果没找到,就去检查 bestMatch 是否以 / 结尾,如果是以 / 结尾,则去掉结尾的 / 再去 handlerMap 中查找,如果还没找到,那就该抛异常出来了。如果找到的 handler 是 String 类型的,则再去 Spring 容器中查找对应的 Bean,接下来再调用 validateHandler 方法进行验证。
6.接下来调用 extractPathWithinPattern 方法提取出映射路径,例如定义的接口规则是 myroot/*.html,请求路径是 myroot/myfile.html,那么最终获取到的就是myfile.html。
7.接下来的 for 循环是为了处理存在多个最佳匹配规则的情况,在第四步中,我们对 matchingPatterns 进行排序,排序完成后,选择第一项作为最佳选项赋值给 bestMatch,但是最佳选项可能会有多个,这里就是处理最佳选项有多个的情况。
8.最后调用 buildPathExposingHandler 方法注册两个内部拦截器,该方法下文我会给大家详细介绍。
lookupHandler 还有一个重载方法,不过只要大家把这个方法的执行流程搞清楚了,重载方法其实很好理解,这里松哥就不再赘述了,唯一要说的就是重载方法用了 PathPattern 去匹配 URL 路径,而这个方法用了 AntPathMatcher 去匹配 URL 路径。
buildPathExposingHandler
- protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
- String pathWithinMapping, @Nullable Map
uriTemplateVariables) { - HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
- chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
- if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
- chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
- }
- return chain;
- }
buildPathExposingHandler 方法向 HandlerExecutionChain 中添加了两个拦截器 PathExposingHandlerInterceptor 和 UriTemplateVariablesHandlerInterceptor,这两个拦截器在各自的 preHandle 中分别向 request 对象添加了一些属性,具体添加的属性小伙伴们可以自行查看,这个比较简单,我就不多说了。
在前面的方法中,涉及到一个重要的变量 handlerMap,我们定义的接口和处理器之间的关系都保存在这个变量中,那么这个变量是怎么初始化的呢?这就涉及到 AbstractUrlHandlerMapping 中的另一个方法 registerHandler:
- protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
- for (String urlPath : urlPaths) {
- registerHandler(urlPath, beanName);
- }
- }
- protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
- Object resolvedHandler = handler;
- if (!this.lazyInitHandlers && handler instanceof String) {
- String handlerName = (String) handler;
- ApplicationContext applicationContext = obtainApplicationContext();
- if (applicationContext.isSingleton(handlerName)) {
- resolvedHandler = applicationContext.getBean(handlerName);
- }
- }
- Object mappedHandler = this.handlerMap.get(urlPath);
- if (mappedHandler != null) {
- if (mappedHandler != resolvedHandler) {
- throw new IllegalStateException(
- "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
- "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
- }
- }
- else {
- if (urlPath.equals("/")) {
- setRootHandler(resolvedHandler);
- }
- else if (urlPath.equals("/*")) {
- setDefaultHandler(resolvedHandler);
- }
- else {
- this.handlerMap.put(urlPath, resolvedHandler);
- if (getPatternParser() != null) {
- this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler);
- }
- }
- }
- }
registerHandler(String[],String) 方法有两个参数,第一个就是定义的请求路径,第二个参数则是处理器 Bean 的名字,第一个参数是一个数组,那是因为同一个处理器可以对应多个不同的请求路径。
在重载方法 registerHandler(String,String) 里边,完成了 handlerMap 的初始化,具体流程如下:
这就是 AbstractUrlHandlerMapping 的主要工作,其中 registerHandler 将在它的子类中调用。
接下来我们来看 AbstractUrlHandlerMapping 的子类。
为了方便处理,SimpleUrlHandlerMapping 中自己定义了一个 urlMap 变量,这样可以在注册之前做一些预处理,例如确保所有的 URL 都是以 / 开始。SimpleUrlHandlerMapping 在定义时重写了父类的 initApplicationContext 方法,并在该方法中调用了 registerHandlers,在 registerHandlers 中又调用了父类的 registerHandler 方法完成了 handlerMap 的初始化操作:
- @Override
- public void initApplicationContext() throws BeansException {
- super.initApplicationContext();
- registerHandlers(this.urlMap);
- }
- protected void registerHandlers(Map
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);
- });
- }
- }
这块代码很简单,实在没啥好说的,如果 URL 不是以 / 开头,则手动给它加上/ 即可。有小伙伴们可能要问了,urlMap 的值从哪里来?当然是从我们的配置文件里边来呀,像下面这样:
AbstractDetectingUrlHandlerMapping 也是 AbstractUrlHandlerMapping 的子类,但是它和 SimpleUrlHandlerMapping 有一些不一样的地方。
不一样的是哪里呢?
AbstractDetectingUrlHandlerMapping 会自动查找到 SpringMVC 容器以及 Spring 容器中的所有 beanName,然后根据 beanName 解析出对应的 URL 地址,再将解析出的 url 地址和对应的 beanName 注册到父类的 handlerMap 变量中。换句话说,如果你用了 AbstractDetectingUrlHandlerMapping,就不用像 SimpleUrlHandlerMapping 那样去挨个配置 URL 地址和处理器的映射关系了。我们来看下 AbstractDetectingUrlHandlerMapping#initApplicationContext 方法:
- @Override
- public void initApplicationContext() throws ApplicationContextException {
- super.initApplicationContext();
- detectHandlers();
- }
- protected void detectHandlers() throws BeansException {
- ApplicationContext applicationContext = obtainApplicationContext();
- String[] beanNames = (this.detectHandlersInAncestorContexts ?
- BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
- applicationContext.getBeanNamesForType(Object.class));
- for (String beanName : beanNames) {
- String[] urls = determineUrlsForHandler(beanName);
- if (!ObjectUtils.isEmpty(urls)) {
- registerHandler(urls, beanName);
- }
- }
- }
AbstractDetectingUrlHandlerMapping 重写了父类的 initApplicationContext 方法,并在该方法中调用了 detectHandlers 方法,在 detectHandlers 中,首先查找到所有的 beanName,然后调用 determineUrlsForHandler 方法分析出 beanName 对应的 URL,不过这里的 determineUrlsForHandler 方法是一个空方法,具体的实现在它的子类中,AbstractDetectingUrlHandlerMapping 只有一个子类 BeanNameUrlHandlerMapping,我们一起来看下:
- public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
- @Override
- protected String[] determineUrlsForHandler(String beanName) {
- List
urls = new ArrayList<>(); - 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);
- }
- }
这个类很简单,里边就一个 determineUrlsForHandler 方法,这个方法的执行逻辑也很简单,就判断 beanName 是不是以 / 开始,如果是,则将之作为 URL。
如果我们想要在项目中使用 BeanNameUrlHandlerMapping,配置方式如下:
注意,Controller 的 name 必须是以 / 开始,否则该 bean 不会被自动作为处理器。
至此,AbstractUrlHandlerMapping 体系下的东西就和大家分享完了。
AbstractHandlerMethodMapping 体系下只有三个类,分别是 AbstractHandlerMethodMapping、RequestMappingInfoHandlerMapping 以及 RequestMappingHandlerMapping,如下图:
在前面第三小节的 AbstractUrlHandlerMapping 体系下,一个 Handler 一般就是一个类,但是在 AbstractHandlerMethodMapping 体系下,一个 Handler 就是一个 Mehtod,这也是我们目前使用 SpringMVC 时最常见的用法,即直接用 @RequestMapping 去标记一个方法,该方法就是一个 Handler。
接下来我们就一起来看看 AbstractHandlerMethodMapping。
AbstractHandlerMethodMapping 类实现了 InitializingBean 接口,所以 Spring 容器会自动调用其 afterPropertiesSet 方法,在这里将完成初始化操作:
- @Override
- public void afterPropertiesSet() {
- initHandlerMethods();
- }
- protected void initHandlerMethods() {
- for (String beanName : getCandidateBeanNames()) {
- if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
- processCandidateBean(beanName);
- }
- }
- handlerMethodsInitialized(getHandlerMethods());
- }
- protected String[] getCandidateBeanNames() {
- return (this.detectHandlerMethodsInAncestorContexts ?
- BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
- obtainApplicationContext().getBeanNamesForType(Object.class));
- }
- protected void processCandidateBean(String beanName) {
- Class> beanType = null;
- try {
- beanType = obtainApplicationContext().getType(beanName);
- }
- catch (Throwable ex) {
- }
- if (beanType != null && isHandler(beanType)) {
- detectHandlerMethods(beanName);
- }
- }
可以看到,具体的初始化又是在 initHandlerMethods 方法中完成的,在该方法中,首先调用 getCandidateBeanNames 方法获取容器中所有的 beanName,然后调用 processCandidateBean 方法对这些候选的 beanName 进行处理,具体的处理思路就是根据 beanName 找到 beanType,然后调用 isHandler 方法判断该 beanType 是不是一个 Handler,isHandler 是一个空方法,在它的子类 RequestMappingHandlerMapping 中被实现了,该方法主要是检查该 beanType 上有没有 @Controller 或者 @RequestMapping 注解,如果有,说明这就是我们想要的 handler,接下来再调用 detectHandlerMethods 方法保存 URL 和 handler 的映射关系:
- protected void detectHandlerMethods(Object handler) {
- Class> handlerType = (handler instanceof String ?
- obtainApplicationContext().getType((String) handler) : handler.getClass());
- if (handlerType != null) {
- Class> userType = ClassUtils.getUserClass(handlerType);
- Map
methods = MethodIntrospector.selectMethods(userType, - (MethodIntrospector.MetadataLookup
) method -> { - try {
- return getMappingForMethod(method, userType);
- }
- catch (Throwable ex) {
- throw new IllegalStateException("Invalid mapping on handler class [" +
- userType.getName() + "]: " + method, ex);
- }
- });
- methods.forEach((method, mapping) -> {
- Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
- registerHandlerMethod(handler, invocableMethod, mapping);
- });
- }
- }
上面这段代码里又涉及到两个方法:
我们分别来看:
getMappingForMethod
getMappingForMethod 是一个模版方法,具体的实现也是在子类 RequestMappingHandlerMapping 里边:
- @Override
- @Nullable
- protected RequestMappingInfo getMappingForMethod(Method method, Class> handlerType) {
- RequestMappingInfo info = createRequestMappingInfo(method);
- if (info != null) {
- RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
- if (typeInfo != null) {
- info = typeInfo.combine(info);
- }
- String prefix = getPathPrefix(handlerType);
- if (prefix != null) {
- info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
- }
- }
- return info;
- }
首先根据 method 对象,调用 createRequestMappingInfo 方法获取一个 RequestMappingInfo,一个 RequestMappingInfo 包含了一个接口定义的详细信息,例如参数、header、produces、consumes、请求方法等等信息都在这里边。接下来再根据 handlerType 也获取一个 RequestMappingInfo,并调用 combine 方法将两个 RequestMappingInfo 进行合并。接下来调用 getPathPrefix 方法查看 handlerType 上有没有 URL 前缀,如果有,就添加到 info 里边去,最后将 info 返回。
这里要说一下 handlerType 里边的这个前缀是那里来的,我们可以在 Controller 上使用 @RequestMapping 注解,配置一个路径前缀,这样 Controller 中的所有方法都加上了该路径前缀,但是这种方式需要一个一个的配置,如果想一次性配置所有的 Controller 呢?我们可以使用 Spring5.1 中新引入的方法 addPathPrefix 来配置,如下:
- @Configuration
- public class WebConfig implements WebMvcConfigurer {
- @Override
- public void configurePathMatch(PathMatchConfigurer configurer) {
- configurer.setPatternParser(new PathPatternParser()).addPathPrefix("/itboyhub", HandlerTypePredicate.forAnnotation(RestController.class));
- }
- }
上面这个配置表示,所有的 @RestController 标记的类都自动加上 itboyhub前缀。有了这个配置之后,上面的 getPathPrefix 方法获取到的就是/itboyhub 了。
registerHandlerMethod
当找齐了 URL 和 handlerMethod 之后,接下来就是将这些信息保存下来,方式如下:
- protected void registerHandlerMethod(Object handler, Method method, T mapping) {
- this.mappingRegistry.register(mapping, handler, method);
- }
- public void register(T mapping, Object handler, Method method) {
- this.readWriteLock.writeLock().lock();
- try {
- HandlerMethod handlerMethod = createHandlerMethod(handler, method);
- validateMethodMapping(handlerMethod, mapping);
- Set
directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping); - for (String path : directPaths) {
- this.pathLookup.add(path, mapping);
- }
- String name = null;
- if (getNamingStrategy() != null) {
- name = getNamingStrategy().getName(handlerMethod, mapping);
- addMappingName(name, handlerMethod);
- }
- CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
- if (corsConfig != null) {
- corsConfig.v
分享文章:SpringMVC九大组件之HandlerMapping深入分析
网址分享:http://www.36103.cn/qtweb/news29/15279.html网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联