Spring Mvc 源码解析(三)— HandlerMapping初始化

1,761 阅读6分钟

image

写在最前面

上一篇文章我们简单介绍了,servlet容器以及Spring Mvc 应用容器的初始化过程。并了解如何通过java代码,来进行容器的初始化配置。在源码解析(一)中我们提到当servlet container接收到一个请求时,servlet container 会根据servlet的mapping 配置选取一个 servlet 进行请求处理,并返回 response了解更多servlet container 容器。我们在实际开发的时候经常会写很多 controller ,本文将详细介绍handerMapping的初始化过程,以及DispatcherServlet 是如何根据Request 获取对应的handlerMapping的。

一、Spring Mvc初始化过程

首先我们先来明确几个概念:

  • Handler 通常指用于处理request请求的实际对象,可以类比 XxxController。在Spring Mvc中并没有具体的类叫 Handler。
  • HandlerMethod 处理request请求的具体方法对应 Controller 层中的某一个注解了@RequestMappping的方法。org.springframework.web.method.HandlerMethod 该类中保存了执行某一个 request 请求的方法,以及相应的类信息。
  • RequestMappingInfo 当Spring Mvc处理请求的时候通过 Request的 method、header、参数等来匹配对应的RequestMappingInfoRequestMappingInfo通常保存的是@RequestMaping注解的信息、用来确定每一个请求具体由哪一个Controller方法处理。

1.1 初始化调用链

Spring Mvc 的初始化调用过程如下图:

image

1.2、RequestMappingHandlerMapping、AbstractHandlerMethodMapping

RequestMappingHandlerMapping 实现了InitalizingBean接口熟悉Spring 容器的同学应该知道Spring容器在启动的时候会执行InitalizingBean.afterPropertiesSet()方法。RequestMappingHandlerMapping实现了这个方法,这里也是Spring Mvc初始化的入口。

1.3 正真的初始化方法initHandlerMethods()

直接看源码,方法的逻辑很简单配合注释很容易理解

/**
	 * Scan beans in the ApplicationContext, detect and register handler methods.
	 * @see #isHandler(Class)
	 * @see #getMappingForMethod(Method, Class)
	 * @see #handlerMethodsInitialized(Map)
	 */
	protected void initHandlerMethods() {
		if (logger.isDebugEnabled()) {
			logger.debug("Looking for request mappings in application context: " + getApplicationContext());
		}
		//获取所有容器托管的 beanName
		String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
				BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
				getApplicationContext().getBeanNamesForType(Object.class));

		for (String beanName : beanNames) {
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
				Class<?> beanType = null;
				try {
				    //获取 Class 信息
					beanType = getApplicationContext().getType(beanName);
				}
				catch (Throwable ex) {
					// An unresolvable bean type, probably from a lazy bean - let's ignore it.
					if (logger.isDebugEnabled()) {
						logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
					}
				}
				// isHandler()方法判断这个类是否有 @RequestMapping 或 @Controller
				if (beanType != null && isHandler(beanType)) {
				    //发现并注册 Controller @RequestMapping方法
					detectHandlerMethods(beanName);
				}
			}
		}
		//Spring Mvc框架没有实现、可以用于功能扩展。
		handlerMethodsInitialized(getHandlerMethods());
	}

1.4从handler中获取HandlerMethod

/**
	 * Look for handler methods in a handler.
	 * @param handler the bean name of a handler or a handler instance
	 */
	protected void detectHandlerMethods(final Object handler) {
		Class<?> handlerType = (handler instanceof String ?
				getApplicationContext().getType((String) handler) : handler.getClass());
		final Class<?> userType = ClassUtils.getUserClass(handlerType);

		Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
				new MethodIntrospector.MetadataLookup<T>() {
					@Override
					public T inspect(Method method) {
						try {
						    //获取 RequestMappingInfo
							return getMappingForMethod(method, userType);
						}
						catch (Throwable ex) {
							throw new IllegalStateException("Invalid mapping on handler class [" +
									userType.getName() + "]: " + method, ex);
						}
					}
				});

		if (logger.isDebugEnabled()) {
			logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
		}
		for (Map.Entry<Method, T> entry : methods.entrySet()) {
			Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
			T mapping = entry.getValue();
			//注册RequestMappingInfo
			registerHandlerMethod(handler, invocableMethod, mapping);
		}
	}

1.5、创建RequestMappingInfo

这一步会获取方法和类上的@RequestMapping注解并通过 @RequestMaping注解配置的参数生成相应的RequestMappingInfoRequestMappingInfo中保存了很多Request需要匹配的参数。

1、匹配请求Url PatternsRequestCondition patternsCondition;

2、匹配请求方法 GET等 RequestMethodsRequestCondition methodsCondition;

3、匹配参数例如@Requestmaping(Params="action=DoXxx") ParamsRequestCondition paramsCondition;

4、匹配请求头信息 HeadersRequestCondition headersCondition;

5、指定处理请求的提交内容类型(Content-Type),例如application/json, text/html; ConsumesRequestCondition consumesCondition;

6、指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回ProducesRequestCondition producesCondition;

7、用于用户定制请求条件 RequestConditionHolder customConditionHolder; 举个例子,当我们有一个需求只要请求中包含某一个参数时都可以掉这个方法处理,就可以定制这个匹配条件。

/**
	 * Uses method and type-level @{@link RequestMapping} annotations to create
	 * the RequestMappingInfo.
	 * @return the created RequestMappingInfo, or {@code null} if the method
	 * does not have a {@code @RequestMapping} annotation.
	 * @see #getCustomMethodCondition(Method)
	 * @see #getCustomTypeCondition(Class)
	 */
	@Override
	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);
			}
		}
		return info;
	}
	
	/**
	 * Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)},
	 * supplying the appropriate custom {@link RequestCondition} depending on whether
	 * the supplied {@code annotatedElement} is a class or method.
	 * @see #getCustomTypeCondition(Class)
	 * @see #getCustomMethodCondition(Method)
	 */
	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);
	}

1.6、注册RequestMappingInfo

这一步 Spring Mvc 会保存 Map<RequestmappingInfo, HandlerMethod>Map<url, RequestmappingInfo>的映射关系。这里我们可以和源码分析(一)里的DispatcherServlet.doDispatch()方法联系起来,doDispatch方法通过 url 在Map<url, RequestmappingInfo>中获取对应的 RequestMappingInfo 再根据request的信息和RequestMappingInfo的各个条件比较是否满足处理条件,如果不满足返回 404 如果满足通过RequestMappingInfoMap<RequestmappingInfo, HandlerMethod>获取正真处理该请求的方法HandlerMathod. 最后通过反射执行具体的方法(Controller 方法)。

public void register(T mapping, Object handler, Method method) {
			this.readWriteLock.writeLock().lock();
			try {
				HandlerMethod handlerMethod = createHandlerMethod(handler, method);
				assertUniqueMethodMapping(handlerMethod, mapping);

				if (logger.isInfoEnabled()) {
					logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
				}
				//保存RequestMappingInfo 和 HandlerMathod的映射关系
				this.mappingLookup.put(mapping, handlerMethod);

				List<String> directUrls = getDirectUrls(mapping);
				for (String url : directUrls) {
				    //保存 Request Url 和 RequestMappingInfo 的对应关系
					this.urlLookup.add(url, mapping);
				}

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

				this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
			}
			finally {
				this.readWriteLock.writeLock().unlock();
			}
		}

二、实战

需求:我们需要对外提供一个 OpenApi 其他项目签名通过后既可以调用,而不用频繁每次写签名校验的逻辑,我们如何利用 Spring mvc 的初始化过程来定制我们的需求?

2.1、OpenApi 注解

@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@ResponseBody
public @interface OpenApi {
    //请求 url
    String value() default "";
    
    //需要参与签名的请求参数
    String[] params() default {};
    
    //方法
    RequestMethod method() default RequestMethod.GET;
    
    ····
}

2.2、定制RequestMappingHandlerMapping

当获取到方法上有@OpenApi注解时创建 RequestMappingInfo

/**
 * @author HODO
 */
public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
    @Override
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        //父类的方法调用上文已经分析过
        RequestMappingInfo mappingInfo = super.getMappingForMethod(method, handlerType);
        if (mappingInfo != null) {
            return mappingInfo;
        }
        OpenApi openApi = AnnotatedElementUtils.findMergedAnnotation(method, OpenApi.class);
        return openApi == null ? null : RequestMappingInfo.paths(openApi.value()).methods(openApi.method()).build();
    }
}

2.3 拦截器

拦截器拦截请求,当处理的方法包含 OpenApi 注解的时候进行签名处理,根据业务自身的需求判断签名是否有效。

/**
 * @author HODO
 */
public class OpenApiSupportInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = ((HandlerMethod) handler);
            OpenApi openApi = AnnotatedElementUtils.getMergedAnnotation(handlerMethod.getMethod(), OpenApi.class);
            if (openApi != null && !checkSign(request, openApi.params())) {
                response.getWriter().write("签名失败");
                return false;
            }
        }
        return true;
    }
    

    private boolean checkSign(HttpServletRequest request, String[] params) {
        
        //get signKey check sign
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

2.4、配置增加拦截器,并替换容器默认初始化的RequestMappingHandlerMapping使用我们定制的CustomRequestMappingHandlerMapping

@Configuration
@ComponentScan("demo")
public class WebMvcConfiguration extends WebMvcConfigurationSupport {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new OpenApiSupportInterceptor());
    }

    @Override
    protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
        return new CustomRequestMappingHandlerMapping();
    }
}

2.4、Controller测试代码

/**
 * @author HODO
 */
@Controller
@RequestMapping
public class DemoController {
    @OpenApi(value = "/api")
    public String demo2() {
        return "test2";
    }
}

三、总结

Spring Mvc的初始化过程比较清晰,整个过程Spring 提供的方法很多都是 protected修饰的这样我们可以通过继承灵活的定制我们的需求。 回顾一下整个初始化过程:

  1. 通过Spring容器对 InitalizingBean.afterPropertiesSet()方法的支持开始初始化流程。

  2. 获取容器中的所有 bean 通过 isHandler()方法区分是否需要处理。

  3. 通过方法上的@RequestMapping注解创建 RequestMappingInfo

  4. 注册、保存保存 Map<RequestmappingInfo, HandlerMethod>Map<url, RequestmappingInfo>的映射关系方便后续调用和Request匹配。

项目地址:https://github.com/hoodoly/Spring-Mvc

联系方式:gunriky@163.com 有问题可以直接联系