spring mvc的RequestMappingHandlerMapping注册HandlerMethod源码分析

Posted 不识君的荒漠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring mvc的RequestMappingHandlerMapping注册HandlerMethod源码分析相关的知识,希望对你有一定的参考价值。

前言

先解释下会用到的一些词语/术语的含义:

Handler : 绑定了注解@RequestMapping和@Controller的类

HandlerMethod:就是Handler下某个绑定@RequestMapping注解的方法(GetMapping、PostMapping...等都绑定的有注解@RequestMapping,spring mvc在做注解解析处理生成代理对象等的时候,会做值的合并等处理,所以最终都是用RequestMapping的注解来计算,所以@Controller和@RestController的处理等同)

为什么要注册HandlerMethod,在spring mvc请求流程图--大纲这篇文章中,大体画了个简单的spring mvc的请求处理流程图,其中有一个环节是匹配HandlerMethod的过程,这个匹配过程后面的文章中说明,本文分析下它的初始化过程:如何根据注解信息和对应方法解析绑定在一起的,如下一段代码:

@RestController
@RequestMapping("/demo")
public class DemoController 
    
    @RequestMapping(path = "/hello", method = RequestMethod.GET)
    public String hello() 
        return "hello, world";
    

spring mvc默认只有一个servlet映射/*请求,但是他可以根据请求信息:比如url:/demo/hello等匹配到hello()这个方法,这个过程是它的匹配HandlerMethod的过程,我们本文重点说的这个根据注解信息注册请求映射信息和对应的HandlerMethod的过程。

接下来开始:

RequestMappingHandlerMapping

RequestMappingHandlerMapping这个类,是负责根据注解方式来处理HandlerMethod,那就来看下它的注册过程。

看下这个类的继承关系:

父类只画到了

AbstractHandlerMethodMapping<RequestMappingInfo>

这一级,其中的泛型信息也很重要。

AbstractHandlerMethodMapping这个类实现了接口InitializingBean(不清楚这个接口作用的同学,可以查下资料了解下),所以HandlerMethod的注册便在这个接口实现方法afterPropertiesSet()方法上了(这是因为spring 初始化bean的时候,如果某个bean实现了接口InitializingBean便会回调它的afterPropertiesSet()。

我们看下这个源码:(后面的代码中我都会加上中文注释)

	/**
	 * 在初始化的时候侦测所有的HandlerMethod并注册
	 */
	@Override
	public void afterPropertiesSet() 
		initHandlerMethods();
	

接下来进入initHandlerMethods(),

    //扫描容器中所有的bean,侦测并注册HandlerMethod
	protected void initHandlerMethods() 
		if (logger.isDebugEnabled()) 
			logger.debug("Looking for request mappings in application context: " + getApplicationContext());
		
        // detectHandlerMethodsInAncestorContexts这个值 默认为true,
        // 这样一来就会获取所有的bean名称,然后遍历进行过滤
		String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
				BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
				getApplicationContext().getBeanNamesForType(Object.class));

		for (String beanName : beanNames) 
            //不能是作用域代理类的bean
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) 
				Class<?> beanType = null;
				try 
					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的判断条件就是类上是否有注解(Controller || RequestMapping)
				if (beanType != null && isHandler(beanType)) 
                    // 这里就是把每个类下所有符合条件的方法作为HandlerMethod注册,用于匹配时快速查找匹配
					detectHandlerMethods(beanName);
				
			
		
        // 其实这个默认是个空实现,如果有自己的业务需要可以定制
		handlerMethodsInitialized(getHandlerMethods());
	

接下来进入重点:detectHandlerMethods()这个方法,看下如何注册,

  
	protected void detectHandlerMethods(final Object handler) 
        // 看上面的代码,明明是传入的字符串,为什么还要判断它的类型,讲真,这里我也没想到用意
		Class<?> handlerType = (handler instanceof String ?
				getApplicationContext().getType((String) handler) : handler.getClass());
		// 获取它的原生类型,比如是个代理类的时候,就获得被代理的类型
        final Class<?> userType = ClassUtils.getUserClass(handlerType);
        
        //这个操作是把所有符合条件的method过滤出来, 这个泛型T:RequestMappingInfo
        // 这个过滤方法的条件就是看方法上是否使用了注解RequestMapping,注意对RequestMapping
        // 注解的解析并不是很简单的直接获取,因为可能使用了GetMapping等,所以会对它继承的注解解析并将属性值拼接起来
		Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
				new MethodIntrospector.MetadataLookup<T>() 
					@Override
					public T inspect(Method method) 
						try 
                            // getMappingForMethod这个方法会根据method创建对应的请求映射信息返回,后面会重点说下这个方法
							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);
		
        // 遍历过滤出来的所有Method和请求映射信息
		for (Map.Entry<Method, T> entry : methods.entrySet()) 
            // 这里返回的是目标类型可调用的方法
			Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
			T mapping = entry.getValue();
            // 开妈注册了
			registerHandlerMethod(handler, invocableMethod, mapping);
		
	

接下来,应该说registerHadnlerMethod方法,但是这里先解释下getMappingForMethod()这个方法,这是根据方法创建RequestMappingInfo信息,这个可是后面请求到来时匹配HandlerMethod的重要信息:

先看下getMappingForMethod()源码(这个代码是RequetMappingHandlerMapping类实现的):

    @Override
    //RequestMappingInfo 可是根据注解RequestMapping创建的,所以方法上必须得有RequestMapping注解
	protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) 
        // 这个创建方法可以看下面两个方法,代码比较直观易懂
		RequestMappingInfo info = createRequestMappingInfo(method);
		if (info != null)     
            // 这个类型信息,是解析类上的RequestMapping注解信息,然后跟方法的进行拼接组合
            // 比如方法上有路径/hello,类上:/demo,就可以拼接出/demo/hello,这里的拼接还包含其它信息的拼接,
            //如果类上有两条路径,方法有两条,就会产生2*2=4条,多条路径类似
			RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
			if (typeInfo != null) 
                // 注意这个方法的使用,是谁组合谁,谁在前
				info = typeInfo.combine(info);
			
		
		return info;
	


// 看下下面两个方法的代码,可以看到创建RequestMappingInfo的具体过程,RequestMappingInfo包含了很多信息,
// 路径、方法、参数...等等,所以匹配是一个复杂的过程,因为条件多,所以匹配也是可能匹配到多个,但是会选择最合适的那个
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);
	


protected RequestMappingInfo createRequestMappingInfo(
			RequestMapping requestMapping, RequestCondition<?> customCondition) 

		return RequestMappingInfo
				.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
				.methods(requestMapping.method())
				.params(requestMapping.params())
				.headers(requestMapping.headers())
				.consumes(requestMapping.consumes())
				.produces(requestMapping.produces())
				.mappingName(requestMapping.name())
				.customCondition(customCondition)
				.options(this.config)
				.build();
	

接下来进入registerHandlerMethod()方法:

// 注册HandlerMethod和它唯一映射信息	
protected void registerHandlerMethod(Object handler, Method method, T mapping) 
        // mappingRegistry是它的一个内部类的实例
		this.mappingRegistry.register(mapping, handler, method);
	

接下来,进入mappingRegistry.register()的源码实现,这也算是到了最后一步:

public void register(T mapping, Object handler, Method method) 
			this.readWriteLock.writeLock().lock();
			try 
                // 这里会创建一个HandlerMethod实例,HandlerMethod的介绍后期会单独抽出一篇介绍
				HandlerMethod handlerMethod = createHandlerMethod(handler, method);
				// 如果mappingLookup已经存在mapping信息这里会抛出异常
                assertUniqueMethodMapping(handlerMethod, mapping);
        
				if (logger.isInfoEnabled()) 
					logger.info("Mapped \\"" + mapping + "\\" onto " + handlerMethod);
				
                // 这是一个map,保存了Key: RequestMappingInfo和Value: HandlerMethod
				this.mappingLookup.put(mapping, handlerMethod);
                // 一个mapping是允许存在多条路径的
				List<String> directUrls = getDirectUrls(mapping);
				for (String url : directUrls) 
                    //urlLookup是个LinkedMultiValueMap,也就是说一个路径可以有多个mapping
                    // 在进行匹配的时候,就是先根据url找到合适的mapping,然后根据找到的mapping再去找到HandlerMethod
					this.urlLookup.add(url, mapping);
				

				String name = null;
				if (getNamingStrategy() != null) 
                    // 大写类名#方法名
					name = getNamingStrategy().getName(handlerMethod, mapping);
					addMappingName(name, handlerMethod);
				

                //CrossOrigin这个跨域注解请求的初始化配置
				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();
			
		

这样,注册过程就完了,后面有时间的话会单写篇文章来分析一下匹配的过程。

以上是关于spring mvc的RequestMappingHandlerMapping注册HandlerMethod源码分析的主要内容,如果未能解决你的问题,请参考以下文章

Spring mvc @RequestMapping

Spring Security + MVC:相同的@RequestMapping,不同的@Secured

Spring MVC 基础注解之@RequestMapping@Controller

Spring MVC - 02 RequestMapping映射请求

spring mvc requestmapping 配置多个

Spring MVC之@RequestMapping基本用法