Spring MVC 源码分析之 加载及查找 Controller

目录

一、前言

二、查找Handler

2.1、回顾 doDispatch

2.2、查看 getHandler方法

2.3、handlerMappings的前世今生

三、补充说明

1、通过  方式

2、SpringBoot方式

四、总结 


 

一、前言

上一篇文章介绍了SpringMVC的请求过程,其中在DispatcherServlet中的 doDispatch方法中,说到了根据 request 查找具体Handler的,这篇文章主要介绍 Handler的查找,即为怎么根据Request 请求URL查找到 Controller 。

二、查找Handler

2.1、回顾 doDispatch

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		
		try {	
			try {				
                // Determine handler for the current request.
                //根据 request 查找Handler
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}
				
			}catch (Exception ex) {
				dispatchException = ex;
			}						
		}catch (Exception ex) {
			
		}catch (Throwable err) {
			
		}finally {

		}
	}

2.2、查看 getHandler方法

@Nullable
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

根据源码可知,getHandler起始返回的是 HandlerExecutionChain,其实是在 handlerMappings中查找的,那handlerMappings又是从哪来的呢,接下来分析 handlerMappings的出处。

2.3、handlerMappings的前世今生

上篇文章中结束Dispatcher的初始化时介绍到了,在DispatcherServletonRefresh 方法中调用了 initHandlerMappings 方法,接下来看看initHandlerMappings方法的实现逻辑。

private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;
		if (this.detectAllHandlerMappings) {
			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
			// 在SpringContext的容器中查找 HandlerMapping 接口的所有实例。如果查询到进行排序
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}else {
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}catch (NoSuchBeanDefinitionException ex) {				
			}
		}
		// Ensure we have at least one HandlerMapping, by registering
		// a default HandlerMapping if no other mappings are found.
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isTraceEnabled()) {
				logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
						"': using default strategies from DispatcherServlet.properties");
			}
		}
	}

查看看 handlerMappings中的HandlerMapper,如下图所示,其中主要解析Controller是在 RequestMappingHandlerMapping中。

接下来看看 HandlerMapping 类的实现结构

接下来重点看HandlerMapping的实现类, RequestMappingHandlerMapping 类。

1、先查看下ReqeustMappingHandlerMapping类的继承关系图

 根据继承关系图可知,RequestMappingHandlerMapping同时实现了 InitializingBeanApplicationContextAware两个接口,熟悉这两个接口的都知道,其中InitializingBean 的方法 afterPropertiesSet 是在类初始化完成且设置属性完成后调用,那么接下来看看 RequestMappingHandlerMappingafterPropertiesSet的实现。

2、

	public void afterPropertiesSet() {
		this.config = new RequestMappingInfo.BuilderConfiguration();
		this.config.setUrlPathHelper(getUrlPathHelper());
		this.config.setPathMatcher(getPathMatcher());
		this.config.setSuffixPatternMatch(useSuffixPatternMatch());
		this.config.setTrailingSlashMatch(useTrailingSlashMatch());
		this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
		this.config.setContentNegotiationManager(getContentNegotiationManager());
        //用调用了父类的 方法
		super.afterPropertiesSet();
	}

此实现中除了设置一下参数配置,又调用了父类的afterPropertiesSet的实现,在看看父类的方法

@Override
	public void afterPropertiesSet() {
        
		initHandlerMethods();
	}

	/**
	 * Scan beans in the ApplicationContext, detect and register handler methods.
	 * @see #getCandidateBeanNames()
	 * @see #processCandidateBean
	 * @see #handlerMethodsInitialized
	 */
	protected void initHandlerMethods() {

        //在Spring容器中获取所有的Bean,
		for (String beanName : getCandidateBeanNames()) {
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
               //解析Bean
				processCandidateBean(beanName);
			}
		}
		handlerMethodsInitialized(getHandlerMethods());
	}

根据源码可知:

  •  直接调用了 initHandlerMethods方法
  • initHandlerMethods方法是获取了Spring容器中的所有beanName。
  • 然后调用了processCandidateBean方法。

 3、查看 processCandidateBean 方法

protected void processCandidateBean(String beanName) {
		Class<?> beanType = null;
		try {
            //获取beanNamed对应的类型
			beanType = obtainApplicationContext().getType(beanName);
		}
		catch (Throwable ex) {
			
		}
        //判断类型是否为空,和 此类型是否为 Handler
		if (beanType != null && isHandler(beanType)) {
			detectHandlerMethods(beanName);
		}
	}


//此方法是RequestMappingHandlerMapping类中实现的,判断了此类型是否添加了
// Controller 和 RequestMapping 注解。
@Override
protected boolean isHandler(Class<?> beanType) {
  return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
				AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

有源码可知

  • 如果判断类型为Handler,即类添加了 Controller 和RequestMapping 注解。
  • 如果是 则调用 detectHandlerMethods 方法,此方法即为生成 RequestMappingInfo 的方法。此方法在父类 

 接下来看看 detectHandlerMethods 方法的实现

protected void detectHandlerMethods(Object handler) {
		Class<?> handlerType = (handler instanceof String ?
				obtainApplicationContext().getType((String) handler) : handler.getClass());

		if (handlerType != null) {
			Class<?> userType = ClassUtils.getUserClass(handlerType);
			Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
					(MethodIntrospector.MetadataLookup<T>) method -> {
						try {
                            // 解析类中的方法,生成 RequestMappingInfo 
							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);
				registerHandlerMethod(handler, invocableMethod, mapping);
			});
		}
	}

detectHandlerMethods 方法说明:

  1. 通过反射机制获取 handlerType的所有方法,并调用 getMappingForMethod() 方法,把方法解析为 RequestMappingInfo 类型对象。
  2. 解析完所有的方法后,在调用 registerHandlerMethod 方法注册到HandlerMapping中。具体注AbstractHandlerMethodMapping内部类MappingRegistry的 register 方法,再次方法中 把 handler 和 具体的方法再次封装为 HandlerMethod。然后进行注册。

 至此所有的查找注册工作完成了。

三、补充说明

查看RequestMappingHandlerMapping类的创建。

1、通过  <annotation-driven/> 方式

 此方式是在解析类  AnnotationDrivenBeanDefinitionParser 中的 parse 方法中实现的。如下所示:

2、SpringBoot方式

此种方式是在 类 WebMvcConfigurationSupport 中创建的,如下所示:

四、总结 

  1. 在系统启动时,会在Spring容器中查找所有通过 Controller和RequestMapping注解的类,并解析器方法,把所有符合条件的方法都以RequestMappingInfo及HandlerMethod 注册到 RequestMappingHandlerMapping中。
  2. 在Servlet初始化时,会在Spring容器中查找所有的 HandlerMapping对象。
  3. 在请求是主要通过DispatcherServlet中的 doDispatch方法进行转发。
  4. 在doDispatch中调用 getHandler中,在此方法中循环所欲的HandlerMapping,找到符合条件的HandlerMapping进行处理。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring MVC源码包括多个组件和类。其中,Tomcat在启动时会通知Spring初始化容器,加载bean的定义信息并初始化所有单例bean。然后,Spring MVC会遍历容器中的bean,获取每个controller中方法访问的URL,并将URL和Controller保存到一个Map中。这一过程是由HandlerMapping组件完成的,它是Spring MVC中负责URL到Controller映射的组件。此外,在Spring MVC源码中还有一个抽象类FrameworkServlet,它重写了初始化方法initServletBean(),可以在控制台或日志中打印初始化Servlet的名称以及初始化所需的时间。 以上是关于Spring MVC源码的一些重要信息,这些组件和类协同工作,实现了Spring MVC框架的核心功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Spring MVC源码分析](https://blog.csdn.net/qq_38826019/article/details/117877511)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [SpringMVC源码解析](https://blog.csdn.net/qq_35512802/article/details/120659719)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值