Spring MVC为何能准确的找到一个http请求对应controller的某个方法进行处理

问题已抛出,如标题。Spring版本基于5.1.3

宏观而言,我们需要给一个类加注解@Controller,然后定义一个加了注解@RequestMapping的方法,这样Spring容器就可以准确找到对应的方法了。

其实要回答这个问题,可以从Spring源码去一步步分析。

在Spring MVC里,有一专门处理请求映射的接口HandlerMapping,查看此接口的实现类:

其中,RequestMappingHandlerMapping是我们需要关注的。我们首先看一下RequestMappingHandlerMapping的抽象父类AbstractHandlerMethodMapping,省略其他方法,先关注这两个相关核心方法:

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
 
    ...
    // Handler method detection
 
    /**
     * Detects handler methods at initialization.
     */
    @Override
    public void afterPropertiesSet() {
 
        initHandlerMethods();
 
        // Total includes detected mappings + explicit registrations via registerMapping..
        int total = this.getHandlerMethods().size();
 
        if ((logger.isTraceEnabled() && total == 0) || (logger.isDebugEnabled() && total > 0) ) {
            logger.debug(total + " mappings in " + formatMappingName());
        }
    }
 
    /**
     * Scan beans in the ApplicationContext, detect and register handler methods.
     * @see #isHandler(Class)
     * @see #getMappingForMethod(Method, Class)
     * @see #handlerMethodsInitialized(Map)
     */
    protected void initHandlerMethods() {
        String[] beanNames = obtainApplicationContext().getBeanNamesForType(Object.class);
 
        for (String beanName : beanNames) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                Class<?> beanType = null;
                try {
                    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)) {
                    detectHandlerMethods(beanName);
                }
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }
    ...
    
}

可以看到AbstractHandlerMethodMapping实现了InitializingBean接口,在Spring初始化bean的时候,如果bean实现了InitializingBean接口,会自动调用afterPropertiesSet方法。initHandlerMethods方法,顾名思义是初始化HandlerMethods,查看它是被afterPropertiesSet方法调用,这个方法代表bean在容器中被初始化的时候,会去执行initHandlerMethods方法。

initHandlerMethods方法具体做了什么事情?大概看一下方法内部的业务,首先拿到容器里的所有bean名称放进数组beanNames中;然后遍历数组,拿到每一个bean的类型beanType,对每一个beanType做了一个判断isHandler(beanType),查看此方法的实现,即进入RequestMappingHandlerMapping中:

public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
        implements EmbeddedValueResolverAware {
    ...
    /**
     * {@inheritDoc}
     * Expects a handler to have a type-level @{@link Controller} annotation.
     */
    @Override
    protected boolean isHandler(Class<?> beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }
    ...
}

·很容易看出来,是去判断该类是否加了注解@Controller或@RequestMapping。点进去查看AnnotatedElementUtils.hasAnnotation方法的实现:

public static boolean hasAnnotation(AnnotatedElement element, Class<? extends Annotation> annotationType) {
	// Shortcut: directly present on the element, with no processing needed?
	if (element.isAnnotationPresent(annotationType)) {
		return true;
	}
	return Boolean.TRUE.equals(searchWithFindSemantics(element, annotationType, null, alwaysTrueAnnotationProcessor));
}

所以我们判断一个类上是否加了什么注解,可以这么写:

public class Test {
    public static void main(String[] args) {
        Test.class.isAnnotationPresent(Override.class);
    }
}

判断完类上加了注解@Controller或者@RequestMapping后,看到继续执行了detectHandlerMethods方法:

/**
 * 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 ?
			obtainApplicationContext().getType((String) handler) : handler.getClass());

	if (handlerType != null) {
		final Class<?> userType = ClassUtils.getUserClass(handlerType);
		Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
				(MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));
		if (logger.isTraceEnabled()) {
			logger.trace(formatMappings(userType, methods));
		}
		methods.forEach((key, mapping) -> {
			Method invocableMethod = AopUtils.selectInvocableMethod(key, userType);
			registerHandlerMethod(handler, invocableMethod, mapping);
		});
	}
}

/**
 * Register a handler method and its unique mapping. Invoked at startup for
 * each detected handler method.
 * @param handler the bean name of the handler or the handler instance
 * @param method the method to register
 * @param mapping the mapping conditions associated with the handler method
 * @throws IllegalStateException if another method was already registered
 * under the same mapping
 */
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
	this.mappingRegistry.register(mapping, handler, method);
}

这个方法筛选出了类中加了注解@RequestMapping的方法,放进Map集合methods中,紧接着去遍历每一个method,进入registerHandlerMethod方法,注册到映射注册表mappingRegistry中,其中就是一些Map。

至此,我们就明白了在Spring初始化bean的时候,就把所有的加了@Controller/@RequestMapping的类里面的加了@RequestMapping的方法放进了map中,当http请求来临时,直接去map中迅速拿到对应信息。

  • 10
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值