SpringMVC 源码解析 - Controller 的扫描注册及匹配过程

一、SpringMVC Handler 扫描注册过程

在本专栏前面文章中对 SpringMVC 请求执行过程进行了分析,文章地址如下:

SpringMVC 源码解析 - 请求执行的过程

其中有个重要的组件 HandlerMapping 在源码中起到了非常重要的位置,这里的 Handler 也就是我们常见的 Controller,那 Controller 是如何被识别注册以及查找,本篇文章带领大家一起从源码的角度分析下。

二、Handler 扫描注册过程

HandlerMapping 子类中有个RequestMappingHandlerMapping ,其作用是在容器启动后将系统中所有控制器方法的请求条件和控制器方法的对应关系注册到 RequestMappingHandlerMapping 子类 MappingRegistry 的容器中。当有请求进来时,RequestMappingHandlerMapping 会根据请求条件和容器中存储的系统接口信息比对,再执行对应的控制器方法,从而帮助 DispatcherServlet 分发请求。

下面是 RequestMappingHandlerMapping 继承树关系:

SpringMVC 源码解析 - Controller 的扫描注册及匹配过程_第1张图片

从树中可以看到 RequestMappingHandlerMapping 有实现 InitializingBean 因此在bean加载完成后会触发afterPropertiesSet方法:

SpringMVC 源码解析 - Controller 的扫描注册及匹配过程_第2张图片
在该方法下做了 config 配置的初始化,主要逻辑则交由父类的 afterPropertiesSet() 方法进行实现。

下面进到 AbstractHandlerMethodMapping 下的 afterPropertiesSet() 方法中:

SpringMVC 源码解析 - Controller 的扫描注册及匹配过程_第3张图片
这里触发了 initHandlerMethods() 方法,从这里开始核心的逻辑,下面是该方法的源码:

protected void initHandlerMethods() {
    //获取候选的bean名称数组
    for (String beanName : getCandidateBeanNames()) {
        //用于过滤掉以"scopedTarget."开头的bean名称的。
        //这些bean名称是由Spring创建的代理对象,用于支持不同作用域的bean
        //例如session或request。这些代理对象不是真正的handler,所以要排除掉。
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            //处理候选的bean,判断候选的bean是否是一个handler,也就是是否有@Controller注解或者@RequestMapping注解
            //如果是的话,就调用detectHandlerMethods方法,用于检测和注册handler方法
            processCandidateBean(beanName);
        }
    }
    //初始化handler方法, 对所有handler方法进行排序和日志输出
    handlerMethodsInitialized(getHandlerMethods());
}

initHandlerMethods() 中首先排除掉以 scopedTarget开头的类,因为这些是 Spring 创建的代理对象,下面主要逻辑会交由 processCandidateBean(String beanName) 方法:

protected void processCandidateBean(String beanName) {
      Class<?> beanType = null;
      try {
          // 从IOC工厂中获取当前 beanName 的 Class
          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);
          }
      }
      // 如果 beanType 不为空,并且是 handler
      if (beanType != null && isHandler(beanType)) {
          //检测和注册handler方法
          detectHandlerMethods(beanName);
      }
  }

这里会根据 beanNameIOC工厂中获取到 Class 类型,下面使用 isHandler(Class beanType) 方法判断是否为 handler ,其实就是判断类上是否带有 Controller 注解 或者 RequestMapping 注解,源码如下:

protected boolean isHandler(Class<?> beanType) {
	// 类上是否带有 Controller 注解 或者 RequestMapping 注解
	return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
			AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

如果是 handler 则触发 detectHandlerMethods(Object handler) 方法,进行检测和注册 handler

protected void detectHandlerMethods(Object handler) {
    //如果handler是一个字符串,那就从IOC工厂中获取到对应的Class,否则就直接使用 handler.getClass()方法来获取类类型。
    Class<?> handlerType = (handler instanceof String ?
            obtainApplicationContext().getType((String) handler) : handler.getClass());
    // 如果handler类型不为空
    if (handlerType != null) {
        // 去除代理和增强后的原始类类型,主要为了避免AOP对方法检测造成干扰。
        Class<?> userType = ClassUtils.getUserClass(handlerType);
        // 存储userType中所有带有映射注解(比如@RequestMapping) 的方法及其对应的映射信息
        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));
        }
        //遍历methods中,每个方法及其映射信息)
        methods.forEach((method, mapping) -> {
            //存储经过 AopUtils.selectInvocableMethod() 方法处理后可以被调用(即没有被final修饰)
            //且与 userType 匹配(即没有被覆盖) 的原始或桥接(即泛型擦除后生成) 方法
            Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
            //注册到处理器映射
            registerHandlerMethod(handler, invocableMethod, mapping);
        });
    }
}

从上个方法可以看出,该方法接收的 handler 就是 beanName ,因此第一步会再去工厂中获取 Class 类型,下面看下 getMappingForMethod(Method method, Class handlerType) 方法,主要实现了对注解中的参数信息进行提取:

protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
	//获取方法上定义或继承的@RequestMapping注解,并将其转换为一个RequestMappingInfo对象
	RequestMappingInfo info = createRequestMappingInfo(method);
	if (info != null) {
		//获取类上定义或继承的 @RequestMapping 注解,并将其转换为一个RequestMappingInfo对象
		RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
		if (typeInfo != null) {
			//合并两个RequestMappingInfo对象
			info = typeInfo.combine(info);
		}
		//获取处理器类上定义或继承的 @PathPrefix 注解,并将其转换为一个字符串前缀
		String prefix = getPathPrefix(handlerType);
		if (prefix != null) {
			//根据指定的路径前缀和配置选项来构建一个新的请求映射信息,并且与原有信息进行合并
			info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
		}
	}
	return info;
}

这里分别对方法上和类上的注解的参数进行了提取,将参数信息封装为 RequestMappingInfo 进行返回,其中方法上和类上的属性解析都由 createRequestMappingInfo 方法完成:

private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
	// 获取 RequestMapping 注解
	RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
	RequestCondition<?> condition = (element instanceof Class ?
			getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
	return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}

这里获取到 RequestMapping 注解信息后,交由 createRequestMappingInfo 方法封装成一个 RequestMappingInfo 对象:

protected RequestMappingInfo createRequestMappingInfo(
		RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
	// 解析 RequestMapping 注解中的属性,并生成一个 RequestMappingInfo 对象
	RequestMappingInfo.Builder builder = RequestMappingInfo
			.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);
	}
	return builder.options(this.config).build();
}

createRequestMappingInfo 方法中就比较明了了,分别取出 RequestMapping 中的不同属性值,填充至 RequestMappingInfo 对象中。

下面再回到detectHandlerMethods(Object handler) 方法中,会通过 MethodIntrospector.selectMethods 方法将 RequestMappingInfo封装成一个 keyMethodvalueRequestMappingInfo 对象的 Map

SpringMVC 源码解析 - Controller 的扫描注册及匹配过程_第4张图片
detectHandlerMethods(Object handler) 方法中最后会遍历上面生成的 Map ,触发 registerHandlerMethod(Object handler, Method method, T mapping) 方法进行 handler 的注册工作:

SpringMVC 源码解析 - Controller 的扫描注册及匹配过程_第5张图片
RequestMappingHandlerMapping 中的 registerHandlerMethod 方法同样调用了父类的 registerHandlerMethod 方法,这里来到 AbstractHandlerMethodMapping 类下的 registerHandlerMethod 中:

SpringMVC 源码解析 - Controller 的扫描注册及匹配过程_第6张图片

又调用了mappingRegistry.register 方法,这里则是注册 handler 的主要逻辑,源码如下:

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<?>[] types = method.getParameterTypes();
        if ((types.length > 0) && "kotlin.coroutines.Continuation".equals(types[types.length - 1].getName())) {
            throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
        }
    }
    this.readWriteLock.writeLock().lock();
    try {
        // 创建 HandlerMethod 对象
        HandlerMethod handlerMethod = createHandlerMethod(handler, method);
        validateMethodMapping(handlerMethod, mapping);

        //获取匹配条件对应的直接路径,添加到pathLookup中
        Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
        // 更新 pathLookup 查找表
        for (String path : directPaths) {
            this.pathLookup.add(path, mapping);
        }

        // 如果有命名策略,获取 handler 方法的名称,添加到nameLookup中
        String name = null;
        if (getNamingStrategy() != null) {
            name = getNamingStrategy().getName(handlerMethod, mapping);
            // 注册新的 HandlerMethod
            addMappingName(name, handlerMethod);
        }

        CorsConfiguration config = initCorsConfiguration(handler, method, mapping);
        if (config != null) {
            config.validateAllowCredentials();
            this.corsLookup.put(handlerMethod, config);
        }

        // 将匹配条件和MappingRegistration对象(封装了handler方法、直接路径、名称、跨域配置等信息)添加到registry中
        this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directPaths, name));
    } finally {
        this.readWriteLock.writeLock().unlock();
    }
}

这里首先通过 createHandlerMethod 方法创建了一个 HandlerMethod 对象,源码如下:

protected HandlerMethod createHandlerMethod(Object handler, Method method) {
    if (handler instanceof String) {
        return new HandlerMethod((String) handler,
                obtainApplicationContext().getAutowireCapableBeanFactory(), method);
    }
    return new HandlerMethod(handler, method);
}

这里的 handler 参数就是前面方法一直传递过来的,因此还是 beanName ,所以会进入到第一个判断中,将 beanNameIOC工厂、Method 放入 HandlerMethod 中。

其中 beanName 给了 HandlerMethodbean 属性:

SpringMVC 源码解析 - Controller 的扫描注册及匹配过程_第7张图片

再回到 mappingRegistry.register 方法中,创建完 HandlerMethod 后,将 url 的路径和前面生成的 RequestMappingInfo 对象建立映射关系放入 pathLookup 容器中,在最后又将 RequestMappingInfoMappingRegistration 对象实例建立映射关系放入 registry 中缓存:

SpringMVC 源码解析 - Controller 的扫描注册及匹配过程_第8张图片
到这就完成了 Handler 的扫描和注册过程。

三、DispatcherServlet 下获取 Handler

DispatcherServlet 下获取 Handler 在前面 SpringMVC 源码梳理的文章中就分析过,但那次分析的确实不怎么详细,因此这里再继续梳理下,看是如何和上面注册的过程形成呼应的。

DispatcherServlet下的 getHandler 中会循环所有的 HandlerMapping 类,使用 mapping.getHandler 方法尝试获取一个 HandlerExecutionChain 对象,如果有则提前终止,HandlerExecutionChain 对象主要就是对 HandlerMethod 和 拦截器进行再次包装。

SpringMVC 源码解析 - Controller 的扫描注册及匹配过程_第9张图片

这里 mapping.getHandler 方法我们看到 AbstractHandlerMapping 下的 getHandler 方法中:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	// 模板方法,留给子类实现
	Object handler = getHandlerInternal(request);
	if (handler == null) {
		//handler为空获取默认的处理器 默认的 handler 也为 null
		handler = getDefaultHandler();
	}
	if (handler == null) {
		return null;
	}
	// handler 如果是字符串对象,则通过 Spring 容器尝试获取 Handler 实例
	if (handler instanceof String) {
		String handlerName = (String) handler;
		// 获取 bean 实例
		handler = obtainApplicationContext().getBean(handlerName);
	}

	//获取 HandlerExecutionChain 对象 ,包含 handler 和对应的 Interceptor
	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)) {
		// cors 的检查请求
		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;
}

在这里首先使用 getHandlerInternal(HttpServletRequest request) 方法获取一个 handler,其实就是 HandlerMethod 对象,进到该方法中:

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // 获取请求路径
    String lookupPath = initLookupPath(request);
    // 映射存储器,尝试获取读锁
    this.mappingRegistry.acquireReadLock();
    try {
        // 通过路径和请求找到 HandlerMethod
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        // 如果找到则创建一个新的 HandlerMethod , 主要将String类型的Bean更改为容器中实际的bean
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    } finally {
        // 放行锁
        this.mappingRegistry.releaseReadLock();
    }
}

这里首先拿到请求的路径,主要是去掉上下文路径和后缀名的URL路径,接着尝试获取映射存储器的读锁,主要防止有写的情况造成线程安全问题,拿到锁后通过 lookupHandlerMethod 方法匹配一个合适的 HandlerMethod,下面看到 lookupHandlerMethod 方法中:

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<>();
    // 根据 loopuPath 获取对应 RequestInfoMapping 对象集合
    List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
    if (directPathMatches != null) {
        // 将找到的 RequestInfoMapping 添加到容器缓存
        addMatchingMappings(directPathMatches, matches, request);
    }
    if (matches.isEmpty()) {
        // 如果没找到,则将所有匹配条件的添加到容器缓存
        addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
    }
    if (!matches.isEmpty()) {
        // 获取容器中的第一个
        Match bestMatch = matches.get(0);
        if (matches.size() > 1) {
            // 数量大于1的话进行排序后取第一个 HandlerMethod 包装类,请求条件数量多的优先
            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);
            // 前两个最优匹配一致,则会抛出异常
            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 + "}");
            }
        }
        //设置 HandlerMethod 的 request 属性
        request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
        // 路径变量处理
        handleMatch(bestMatch.mapping, lookupPath, request);
        return bestMatch.handlerMethod;
    } else {
        return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
    }
}

这里首先根据 lookupPath 通过 mappingRegistry.getMappingsByDirectPath 获取到一个 List 集合,进到mappingRegistry.getMappingsByDirectPath方法中就会发现就是从上面注册过程中的 pathLookup 容器中获取信息,这里List也就是 RequestInfoMapping 对象集合:

SpringMVC 源码解析 - Controller 的扫描注册及匹配过程_第10张图片

下面获取到 RequestInfoMapping 对象集合后,继续向下使用 addMatchingMappings 方法匹配出 Match 对象放入 matches 集合中,这里Match 对象其实就是 HandlerMethod的一个包装类,进入到 addMatchingMappings 方法中:

private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
    for (T mapping : mappings) {
        //检查当前请求是否匹配该映射条件的方法
        T match = getMatchingMapping(mapping, request);
        if (match != null) {
            matches.add(new Match(match,
                    this.mappingRegistry.getRegistrations().get(mapping).getHandlerMethod()));
        }
    }
}

在这里遍历了所有的 RequestInfoMapping 通过 getMatchingMapping 方法获取一个匹配的 RequestInfoMapping 对象,如果存在就生成一个 Match 包装对象放入前面的 matches 集合中,下面继续看 getMatchingMapping 方法,该类在 RequestMappingInfoHandlerMapping 类中:

SpringMVC 源码解析 - Controller 的扫描注册及匹配过程_第11张图片
这里直接使用的 RequestInfoMapping 类的 getMatchingCondition 方法,在进到 info.getMatchingCondition 方法中:

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;
	}
	//可消费 MIME 匹配条件
	ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
	if (consumes == null) {
		return null;
	}
	//可生成 MIME 匹配条件
	ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
	if (produces == null) {
		return null;
	}
	PathPatternsRequestCondition pathPatterns = null;
	//路径匹配,即 url
	if (this.pathPatternsCondition != null) {
		pathPatterns = this.pathPatternsCondition.getMatchingCondition(request);
		if (pathPatterns == null) {
			return null;
		}
	}
	PatternsRequestCondition patterns = null;
	if (this.patternsCondition != null) {
		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, pathPatterns, patterns, methods, params, headers, consumes, produces, custom);
}

这里经过不同维度的 Condition 进行匹配,如果符合条件的话才认为这个 RequestInfoMapping 匹配成功,最后生成了一个新的 RequestMappingInfo 对象装入了 Condition 后的不同维度的信息。

下面再回到 addMatchingMappings 方法中,上面提到如果匹配到则创建一个 Match 包装对像,源码如下:

SpringMVC 源码解析 - Controller 的扫描注册及匹配过程_第12张图片
前面在注册的过程使用 RequestInfoMappingHandlerMethod 组成映射放入了registry 容器中,这里拿到 RequestInfoMapping 后,就可以通过registry 容器获取到 HandlerMethod 了。

再回到 lookupHandlerMethod 中,addMatchingMappings方法匹配到到 RequestInfoMapping也拿到了 HandlerMethod ,正常情况下我们都是一个 url 对应一个接口,如果这里匹配出多个并且一致的话,则会抛出异常,否则最后返回匹配出来的 HandlerMethod

SpringMVC 源码解析 - Controller 的扫描注册及匹配过程_第13张图片
下面再回到 getHandlerInternal 方法中,这里拿到 HandlerMethod 后,没有直接选择返回,而是使用 handlerMethod.createWithResolvedBean() 生成了一个新的 HandlerMethod

SpringMVC 源码解析 - Controller 的扫描注册及匹配过程_第14张图片
这个原因从上面注册过程就可以看出,缓存下的 HandlerMethod 里的 bean 属性,其实还只是 beanName ,我们要通过反射执行方法的话,是需要目标对象的,因此在 createWithResolvedBean 方法中尝试从 IOC 容器中获取出实例:

SpringMVC 源码解析 - Controller 的扫描注册及匹配过程_第15张图片
下面再回到 AbstractHandlerMapping 类的 getHandler 方法,第一步通过 getHandlerInternal 方法拿到了一个已经有目标实例对象的HandlerMethod 了,在向下则通过 getHandlerExecutionChain 生成 HandlerExecutionChain 类型的包装类,主要再将拦截器加进来,源码如下:

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
	//封装 HandlerExecutionChain 对象
	HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
			(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

	// 将 adaptedInterceptors 中,MappedInterceptor中符合要求的添加进去
	for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
		// Spring内部维护的一个url匹配拦截器
		if (interceptor instanceof MappedInterceptor) {
			MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
			if (mappedInterceptor.matches(request)) {
				chain.addInterceptor(mappedInterceptor.getInterceptor());
			}
		}
		else {
			// 添加拦截器
			chain.addInterceptor(interceptor);
		}
	}
	return chain;
}

最后将 HandlerExecutionChain 对象返回就完成了获取 Handler 的工作。

你可能感兴趣的:(源码分析,java,spring,开发语言)