SpringMVC之RequestMappingHandlerMapping

Posted 木叶之荣

tags:

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

我们在上篇文章中大致说了一些和RequestMappingHandlerMapping相关的类,我们在这篇文章中重点分析下RequestMappingHandlerMapping这个类。从上篇的文章中我们看到,RequestMappingHandlerMapping这个类实现了和Bean的生命周期相关的一些接口(关于Bean的生命周期可以参考之前写的小文章:Spring Bean的生命周期小析(一)Spring Bean的生命周期小析(二)),Bean的生命周期的这些接口为我们在Bean的实力化的过程中对Bean进行扩展提供了很大的帮助。在RequestMappingHandlerMapping中我们先看它对InitializingBean这个接口的实现:

	@Override
	public void afterPropertiesSet() 
		//创建一个BuilderConfiguration的实例,这个实例是用来创建RequestMappingInfo使用
		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);
		//主要是给ProducesRequestCondition使用的
		this.config.setContentNegotiationManager(getContentNegotiationManager());
		//调用父类的afterPropertiesSet方法进行一些设置
		super.afterPropertiesSet();
	

在上面的afterPropertiesSet方法中,为BuilderConfiguration设置了一些变量,那么这些变量的值是从哪儿来的呢?因为我们的项目是用SpringBoot的,所以我们这里只分析SpringBoot中对RequestMappingHandlerMapping的加载过程。我这里先直接给出答案,后面我们在详细的分析SpringBoot对Spring MVC的自动加载。变量的初值是在这个方法礼设置的org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#requestMappingHandlerMapping。我们先不分析。继续分析super.afterPropertiesSet()这个方法。这个方法在AbstractHandlerMethodMapping中。

	@Override
	public void afterPropertiesSet() 
		initHandlerMethods();
	
	protected void initHandlerMethods() 
		//获取所有的Bean名称
		for (String beanName : getCandidateBeanNames()) 
			//beanName不是以scopedTarget.开头的Bean。这个一般是在AOP中才会出现这样的Bean
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) 
				//处理Bean 重点要分析的方法
				processCandidateBean(beanName);
			
		
		handlerMethodsInitialized(getHandlerMethods());
	

根据beanName判断类上是否带有Controller或者RequestMapping注解,如果有的话,则解析其中的带有RequestMapping注解的方法,并注册到MappingRegistry

	protected void processCandidateBean(String beanName) 
		Class<?> beanType = null;
		try 
			//从ApplicationContext中根据beanName获取Bean的类型
			beanType = obtainApplicationContext().getType(beanName);
		
		catch (Throwable ex) 
			....
		
		//判断这个Bean是不是带有Controller或者RequestMapping注解(包含父类)
		if (beanType != null && isHandler(beanType)) 
			//检测Bean中带有RequestMapping的Method封装成HandlerMethod
			//这个方法极其复杂
			detectHandlerMethods(beanName);
		
	

解析Bean中的带有RequestMapping注解的方法,注册到MappingRegistry中

	protected void detectHandlerMethods(Object handler) 
		//这里同样是先获取Bean的Class
		Class<?> handlerType = (handler instanceof String ?
				obtainApplicationContext().getType((String) handler) : handler.getClass());

		if (handlerType != null) 
			//这里是获取真正的Class,因为我们的类可能被CGlib代理类,如果被CGlib动态代理了的话,就获取它的父类
			Class<?> userType = ClassUtils.getUserClass(handlerType);
			//这里是获取Class中相应的方法,这里写的很复杂,并且用了JDK1.8的一些新特性
			//我们在下面慢慢分析
			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));
			
			//循环上面获取到的Method,进行一系列的封装
			methods.forEach((method, mapping) -> 
				//获取真正的可执行的方法
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
				//向MappingRegistry中注册Method
				registerHandlerMethod(handler, invocableMethod, mapping);
			);
		
	

MethodIntrospector.selectMethods这个方法是一个通用的方法,唯一的不同是MetadataLookup这个参数实现,我们这里的实现是用类lambda表达式,主要的是会调用getMappingForMethod这个方法

	public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) 
		final Map<Method, T> methodMap = new LinkedHashMap<>();
		Set<Class<?>> handlerTypes = new LinkedHashSet<>();
		Class<?> specificHandlerType = null;
		//如果不是JDK动态代理生成的类
		if (!Proxy.isProxyClass(targetType)) 
			//如果是CGlib动态代理生成的类,则获取它的父类
			specificHandlerType = ClassUtils.getUserClass(targetType);
			handlerTypes.add(specificHandlerType);
		
		//获取Class上的所有接口,如果传入的Class是一个接口的话,则直接返回这个接口
		handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));
		//循环处理上一步获取到的Class
		for (Class<?> currentHandlerType : handlerTypes) 
			//这里如果传入的Class是JDK动态代理生成的类,则targetClass和currentHandlerType是一样的,每次都会变
			//如果传入的Class不是JDK动态代理生成的类,则targetClass和currentHandlerType是不一样的。targetClass不会变
			final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
			//这个地方就是我们分析的重点 这里也同样用到类JDK1.8的新特性
			ReflectionUtils.doWithMethods(currentHandlerType, method -> 
				Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
				T result = metadataLookup.inspect(specificMethod);
				if (result != null) 
					Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
					if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) 
						methodMap.put(specificMethod, result);
					
				
			, ReflectionUtils.USER_DECLARED_METHODS);
		

		return methodMap;
	

我们来看下org.springframework.util.ReflectionUtils#doWithMethods这个方法的实现,这个方法也是一个通用的方法,不同的是MethodCallback和MethodFilter接口的实现。我们这里的MethodCallback的实现是通过lambda表达是实现的,而MethodFilter的实现是

//要这个方法不是桥接方法同时也不是isSynthetic方法同时也不是Object中的方法
//关于isSynthetic 可以参考这里: https://www.cnblogs.com/bethunebtj/p/7761596.html
//关于桥接方法可以参考这里: https://stackoverflow.com/questions/289731/what-method-isbridge-used-for https://www.zhihu.com/question/54895701
public static final MethodFilter USER_DECLARED_METHODS =
			(method -> !method.isBridge() && !method.isSynthetic() && method.getDeclaringClass() != Object.class);

接着看doWithMethods这个方法的实现

	public static void doWithMethods(Class<?> clazz, MethodCallback mc, @Nullable MethodFilter mf) 
		// Keep backing up the inheritance hierarchy.
		//获取所有的方法,包含实现的接口中的default方法
		Method[] methods = getDeclaredMethods(clazz);
		for (Method method : methods) 
			//这个mf就是上面说的USER_DECLARED_METHODS 
			//mf.matches 这个方法就是判断这个方法:
			//不是桥接方法同时也不是isSynthetic方法同时也不是Object中的方法
			//如果是桥接方法,则返回false,桥接方法不做处理
			if (mf != null && !mf.matches(method)) 
				continue;
			
			try 
				//调用mc的doWith方法进行处理
				mc.doWith(method);
			
			catch (IllegalAccessException ex) 
				throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);
			
		
		//递归调用父Class进行处理
		if (clazz.getSuperclass() != null) 
			doWithMethods(clazz.getSuperclass(), mc, mf);
		
		else if (clazz.isInterface()) 
			for (Class<?> superIfc : clazz.getInterfaces()) 
				doWithMethods(superIfc, mc, mf);
			
		
	

以上是关于SpringMVC之RequestMappingHandlerMapping的主要内容,如果未能解决你的问题,请参考以下文章

40 个 SpringBoot 常用注解:让生产力爆表!

springmvc学习笔记-springmvc整合mybatis之controller

springmvc学习笔记-springmvc整合mybatis之service

springmvc学习笔记(11)-springmvc注解开发之简单参数绑定

springmvc学习笔记(13)-springmvc注解开发之集合类型参数绑定

SpringMVC学习系列 之 初识SpringMVC