RequestMappingHandlerMapping 初始化搜集所有控制器方法的过程分析

背景介绍

RequestMappingHandlerMappingSpring MVC HandlerMapping的一个实现,主要用于针对控制器类(带有注解@Controller)中类级别或者方法级别的注解@RequestMapping创建RequestMappingInfo并管理。

RequestMappingHandlerMapping工作原理大致是这样的 :

  • 应用启动阶段
    • RequestMappingHandlerMapping实现了接口InitializingBean,它的初始化方法会设置各种工作参数,并检测容器中所有的控制器方法并将它们登记管理起来。针对每个使用注解@RequestMapping的控制器方法所生成的请求匹配条件是一个RequestMappingInfo对象,最终所被管理的是一个HandlerMethod对象。
  • 运行时
    • 当一个请求到达时,RequestMappingHandlerMapping会被DispatcherServlet用于匹配一个HandlerMethod并最终调用它处理该请求。匹配的依据是当前请求中的路径信息和RequestMappingHandlerMapping所管理的每个控制器方法的RequestMappingInfo请求匹配条件。

关于 RequestMappingHandlerMapping 的源代码分析,可以参考:Spring MVC HandlerMapping : RequestMappingHandlerMapping 源代码解析

本文将着重介绍应用启动阶段搜集所有控制器方法并对其进行管理的过程。

1. RequestMappingHandlerMapping组件引入

Spring MVC配置过程中定义了bean组件RequestMappingHandlerMapping requestMappingHandlerMapping,定义代码如下所示 :

// Spring MVC 配置类 WebMvcConfigurationSupport 代码片段
	/**
	 * Return a RequestMappingHandlerMapping ordered at 0 for mapping
	 * requests to annotated controllers.
	 */
	@Bean
	public RequestMappingHandlerMapping requestMappingHandlerMapping() {
		RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
		mapping.setOrder(0);
       // 设置所配置的 HandlerInterceptor 到该 RequestMappingHandlerMapping 组件 
		mapping.setInterceptors(getInterceptors());
		mapping.setContentNegotiationManager(mvcContentNegotiationManager());
		mapping.setCorsConfigurations(getCorsConfigurations());

		PathMatchConfigurer configurer = getPathMatchConfigurer();

		Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();
		if (useSuffixPatternMatch != null) {
			mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
		}
		Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();
		if (useRegisteredSuffixPatternMatch != null) {
			mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
		}
		Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
		if (useTrailingSlashMatch != null) {
			mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
		}

		UrlPathHelper pathHelper = configurer.getUrlPathHelper();
		if (pathHelper != null) {
			mapping.setUrlPathHelper(pathHelper);
		}
		PathMatcher pathMatcher = configurer.getPathMatcher();
		if (pathMatcher != null) {
			mapping.setPathMatcher(pathMatcher);
		}
		Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();
		if (pathPrefixes != null) {
			mapping.setPathPrefixes(pathPrefixes);
		}

		return mapping;
	}

	/**
	 * Protected method for plugging in a custom subclass of
	 * {@link RequestMappingHandlerMapping}.
	 * @since 4.0
	 */
	protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
		return new RequestMappingHandlerMapping();
	}

	/**
	 * Provide access to the shared handler interceptors used to configure
	 * HandlerMapping instances with.
	 * This method cannot be overridden; use #addInterceptors instead.
	 */
	protected final Object[] getInterceptors() {
		if (this.interceptors == null) {
			InterceptorRegistry registry = new InterceptorRegistry();
          // 添加开发人员提供的  HandlerInterceptor,该方法缺省实现为空,开发人员可以提供自己的实现
          // 以添加自己的 HandlerInterceptor
			addInterceptors(registry);
          // 添加 Spring MVC 内置 HandlerInterceptor
			registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService()));
			registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider()));
			this.interceptors = registry.getInterceptors();
		}
		return this.interceptors.toArray();
	}

在容器启动时,该配置被应用,bean组件RequestMappingHandlerMapping requestMappingHandlerMapping被注册到Spring IoC容器。

2.RequestMappingHandlerMapping组件初始化

RequestMappingHandlerMapping隐含地实现了接口InitializingBean(参考 Spring MVC HandlerMapping : RequestMappingHandlerMapping 源代码解析RequestMappingHandlerMapping的继承关系图),所以在它被容器创建时,方法#afterPropertiesSet会被调用。该方法实现如下 :

	// RequestMappingHandlerMapping 代码片段
    @Override
	public void afterPropertiesSet() {
       // 各种配置 
		this.config = new RequestMappingInfo.BuilderConfiguration();
		this.config.setUrlPathHelper(getUrlPathHelper());
		this.config.setPathMatcher(getPathMatcher());
		this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
		this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
		this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
		this.config.setContentNegotiationManager(getContentNegotiationManager());

       // 调用基类的初始化方法,其实现逻辑在基类 AbstractHandlerMethodMapping
		super.afterPropertiesSet();
	}
  
  // 基类 AbstractHandlerMethodMapping 代码片段
	/**
	 * Detects handler methods at initialization.
	 * @see #initHandlerMethods
	 */
	@Override
	public void afterPropertiesSet() {
		initHandlerMethods();
	}  

接下来我们看initHandlerMethods的具体实现 :

	 // 基类 AbstractHandlerMethodMapping 代码片段
    /**
	 * Scan beans in the ApplicationContext, detect and register handler methods.
	 */
	protected void initHandlerMethods() {
       // #getCandidateBeanNames 方法其实是获取容器中所有 bean 的名称
       // 该for循环遍历容器中所有那些名称不以 scopedTarget. 开头的 bean,
       // 然后检测该 bean 组件中可能存在的控制器方法
		for (String beanName : getCandidateBeanNames()) {
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
              // 检测 beanName 对应的 bean 组件中可能存在的控制器方法
				processCandidateBean(beanName);
			}
		}
        
       // 所有控制器方法检测完成之后,调用此方法做调试级别日志输出该信息 
		handlerMethodsInitialized(getHandlerMethods());
	}

processCandidateBean方法实现如下 :

	 // 基类 AbstractHandlerMethodMapping 代码片段
    protected void processCandidateBean(String beanName) {
		Class<?> beanType = null;
		try {
          // 获取该 bean 的类型
			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);
			}
		}
		if (beanType != null && isHandler(beanType)) {
			// #isHandler 方法从 bean 类型判断这是否是一个控制器类,
			// AbstractHandlerMethodMapping 类中 isHandler 是一个抽象方法,需要由实现子类提供实现,
			// 在 RequestMappingHandlerMapping 类中,isHandler 是通过检测该 bean 类上的是否使用
			// 注解 @Controller 或者 @RequestMapping 来判断的。
			detectHandlerMethods(beanName);
		}
	}

processCandidateBean方法主要是检测指定bean类是否使用了注解@Controller或者@RequestMapping,如果使用了,则认为这是目标类,会调用方法detectHandlerMethods从中检测控制器方法。而方法detectHandlerMethods实现如下:

	// 基类 AbstractHandlerMethodMapping 代码片段
    protected void detectHandlerMethods(Object handler) {
      // handler 其实就是控制器类对应的单例bean组件,这里获取其类型到   handlerType
		Class<?> handlerType = (handler instanceof String ?
				obtainApplicationContext().getType((String) handler) : handler.getClass());

		if (handlerType != null) {
			Class<?> userType = ClassUtils.getUserClass(handlerType);
          //  从当前组件bean类上获取所有控制器方法的信息保存在 methods
			Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
					(MethodIntrospector.MetadataLookup<T>) method -> {
						try {
                        // 从方法 method 上尝试构建 映射信息
                        // getMappingForMethod 在 AbstractHandlerMethodMapping 中
                        // 是一个抽象方法,在 RequestMappingHandlerMapping 中有实现,
                        // 该实现尝试从方法 method 上构建 RequestMappingInfo,如果该方法
                        // 不是一个控制器方法,构造的 RequestMappingInfo 为 null,而
                        // 返回 null RequestMappingInfo 的 method 不会被 methods 记录
							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.forEach((method, mapping) -> {
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
              // 注册登记每个控制器方法  
              // 对于 RequestMappingHandlerMapping 来讲,这里 mapping 类型为 RequestMappingInfo              
				registerHandlerMethod(handler, invocableMethod, mapping);
			});
		}
	}

这里RequestMappingHandlerMapping对抽象方法getMappingForMethod的实现指的一提,因为它是从一个控制器类的方法分析控制器方法信息的RequestMappingInfo关键。RequestMappingHandlerMapping类中,此方法实现如下 :

// RequestMappingHandlerMapping 类代码片段
	/**
	 * Uses method and type-level @RequestMapping annotations to create
	 * the RequestMappingInfo.
     * 结合使用方法和类级别的注解 @RequestMapping 创建 RequestMappingInfo
     * 如果综合考虑之后,该方法没有使用注解 @RequestMapping,则该方法返回 null,
     * 否则该方法返回一个 RequestMappingInfo 对象
	 * @return the created RequestMappingInfo, or null if the method
	 * does not have a @RequestMapping annotation.
	 * @see #getCustomMethodCondition(Method)
	 * @see #getCustomTypeCondition(Class)
	 */
	@Override
	@Nullable
	protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
		// 尝试获取方法级别的注解 @RequestMapping 并基于此构建 RequestMappingInfo info
		RequestMappingInfo info = createRequestMappingInfo(method);
		if (info != null) {
			// 尝试获取类级别的注解 @RequestMapping 并基于此构建 RequestMappingInfo typeInfo
			RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
			if (typeInfo != null) {
				// 对类级别和方法级别的 RequestMappingInfo 进行合并,最终都合并到 info 对象
				info = typeInfo.combine(info);
			}
            
			// 根据控制类上的路径前缀信息做 RequestMappingInfo 路径前缀的调整 
			String prefix = getPathPrefix(handlerType);
			if (prefix != null) {
				info = RequestMappingInfo.paths(prefix).build().combine(info);
			}
		}
		return info;
	}
    // 其他辅助方法
	@Nullable
	private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
		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);
	}    
	@Nullable
	protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
		return null;
	}    
	@Nullable
	protected RequestCondition<?> getCustomMethodCondition(Method method) {
		return null;
	}    
	protected RequestMappingInfo createRequestMappingInfo(
			RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {

		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();
	}    

从以上代码分析可见 ,RequestMappingHandlerMapping 初始化搜集所有控制器方法的过程有如下要点 :

  1. 所实现接口InitializingBean约定的方法afterPropertiesSet在该组件bean创建时触发了扫描所有控制器方法的逻辑调用;
  2. AbstractHandlerMethodMapping#processCandidateBean方法扫描所有使用了注解@Controller或者@RequestMapping的目标类:控制器类,并扫描其中的控制器方法;
  3. RequestMappingHandlerMapping#isHandler用于检测一个类是否是控制器类:是否使用了注解@Controller或者@RequestMapping
  4. AbstractHandlerMethodMapping#detectHandlerMethods 检测一个控制器类中的所有控制器方法,获取每个控制器方法上的RequestMappingInfo信息并注册登记;
  5. RequestMappingHandlerMapping#getMappingForMethod用于从控制器类的一个方法上尝试根据类级别和方法级别的@RequestMapping信息构建相应的RequestMappingInfo对象,供#detectHandlerMethods调用;

经过以上这些逻辑,开发人员定义的所有控制器方法都被注册登记到RequestMappingHandlerMapping之中了,这些信息供随后DispatcherServlet在处理用户请求时使用。

参考文章

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在Spring框架中,`RequestMappingHandlerMapping`是一个用于处理请求映射的核心组件。它负责将请求映射到对应的处理方法,并提供了一些附加功能,比如处理拦截器、URL匹配策略等。 下面是`RequestMappingHandlerMapping`的初始化过程: 1. 当Spring Boot应用程序启动时,会创建一个`DispatcherServlet`实例。`DispatcherServlet`是Spring MVC的前端控制器,负责接收和处理所有的HTTP请求。 2. 在`DispatcherServlet`的初始化过程中,会创建`RequestMappingHandlerMapping`实例。这通常是通过在Spring Boot配置中声明一个`@Bean`方法来完成的。 3. 在`RequestMappingHandlerMapping`的构造函数中,会设置一些默认属性和配置。例如,可以通过调用`setOrder(int)`方法为该组件指定一个特定的顺序,以确定其在请求处理链中的位置。 4. 在应用程序上下文中注册了所有带有`@Controller`或`@RestController`注解的类。这些注解用于标识控制器类,并指示Spring将其作为请求处理器进行管理。 5. 通过扫描这些被注解的类,`RequestMappingHandlerMapping`会解析其中的请求映射方法,并根据配置生成相应的`HandlerMethod`对象。 6. 在注册过程中,`RequestMappingHandlerMapping`还会应用其他配置,比如拦截器、URL匹配策略等。这些配置可以通过在Spring Boot配置中声明其他组件或注解来完成。 7. 初始化完成后,`RequestMappingHandlerMapping`会保存所有的请求映射信息,并在收到HTTP请求时,根据请求的URL和其他条件来查找匹配的处理方法。 总之,`RequestMappingHandlerMapping`的初始化过程涉及到创建实例、注册控制器类、解析请求映射方法以及应用其他配置。这样,在应用程序启动时,它就能够准备好处理请求映射任务。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值