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


当我们在实际开发中会给类加注解@Controller,给方法加注解@RequestMapping,那么在请求的时候他是怎么知道对应的方法的呢。在介绍前,先了解下spring启动的时候,怎么把映射路劲加载的。

一、启动的时候

在Spring MVC里,有一专门处理请求映射的接口HandlerMapping
HandlerMapping是一个接口,他有很多子类,下面介绍个很重要的类。
RequestMappingHandlerMapping我们首先看一下RequestMappingHandlerMapping的抽象父类AbstractHandlerMethodMapping,省略其他方法,先关注这两个相关核心方法:

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
 
    @Override
    public void afterPropertiesSet() {
 
        initHandlerMethods();
        
        int total = this.getHandlerMethods().size();
 
        if ((logger.isTraceEnabled() && total == 0) || (logger.isDebugEnabled() && total > 0) ) {
            logger.debug(total + " mappings in " + formatMappingName());
        }
    }
 
    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) {
                    if (logger.isTraceEnabled()) {
                        logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
                    }
                }
                if (beanType != null && isHandler(beanType)) {
                    detectHandlerMethods(beanName);
                }
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }
}

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

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

public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
        implements EmbeddedValueResolverAware {
    
    @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) {

	if (element.isAnnotationPresent(annotationType)) {
		return true;
	}
	return Boolean.TRUE.equals(searchWithFindSemantics(element, annotationType, null, alwaysTrueAnnotationProcessor));
}

判断完继续执行了detectHandlerMethods方法

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);
		//这里有点绕,userType就是我们要处理的某个Controller,下面这段代码就是解析这个Controller里面方法,并把带有RequestMapping的方法返回封装成Mapper
		Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
				(MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));
		
		//methods就是userType这个类下面所有带有RequestMapping的方法。
		//对查找到的HandlerMethod进行注册,保存至内部类mappingRegistry对象中
		methods.forEach((key, mapping) -> {
			//作下判断,method是否从属于userType
			Method invocableMethod = AopUtils.selectInvocableMethod(key, userType);
			registerHandlerMethod(handler, invocableMethod, 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中迅速拿到对应信息。

二、请求进来是找到对应的Controller

1、启动完,请求进入DispatcherServlet的doDispatch方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ......
    HandlerExecutionChain mappedHandler = getHandler(processedRequest);
    .....
}

doDispatch会调用一个getHandler方法获得一个HandlerExecutionChain对象

1.1、HandlerExecutionChain是什么
public class HandlerExecutionChain {

	//handler是一个HandlerMethod对象
	private final Object handler;

	//interceptors,表示这个请求的拦截器
	private HandlerInterceptor[] interceptors;
	
	//....省略部分代码
}

HandlerExecutionChain主要包含了handler和interceptors两个属性。一个是HandlerMethod对象,一个是拦截器链。dispatcherServlet会对handler和interceptors进行顺序调用,从而实现拦截功能。

1.2、HandlerMethod是什么
public class HandlerMethod {
    private final Object bean;
    private final BeanFactory beanFactory;
    private final Class<?> beanType;
    private final Method method;
    private final Method bridgedMethod;
    private final MethodParameter[] parameters;
    private HttpStatus responseStatus;
    private String responseStatusReason;
    private HandlerMethod resolvedFromHandlerMethod;
    //...省略部分代码
}

HandlerMethod包含了要请求方法的方法名,参数,请求类型等。
还保存了这次请求对应的Controller中对应的方法的Method反射对象,只要拿到这个反射对象,就可以反射调用,从而实现从url到对应的Controller中对应的方法的对应关系。

2、getHandler(processedRequest)做了什么

现在我们来分析下他是如何拿到这个HandlerExecutionChain对象的。执行getHandler方法,看下getHandler做了什么。

protected HandlerExecutionChain getHandler(HttpServletRequest request){
	for (HandlerMapping hm : this.handlerMappings) {
		HandlerExecutionChain handler = hm.getHandler(request);
		if (handler != null) {
			return handler;
		}
	}
	return null;
}

我们看到DispatcherServlet是通过遍历自身的HandlerMapping的对象数组,然后找到一个合适的HandlerMapping,通过这个个HandlerMapping的getHandler()方法去获得一个HandlerExecutionChain

看一下这个HandlerMapping的getHandler()方法具体怎么实现,这个方法是在AbstractHandlerMapping里面实现的

3、HandlerMapping的getHandler做了什么
@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	Object handler = getHandlerInternal(request);
	if (handler == null) {
		handler = getDefaultHandler();
	}
	if (handler == null) {
		return null;
	}
	
	if (handler instanceof String) {
		String handlerName = (String) handler;
		handler = getApplicationContext().getBean(handlerName);
	}

	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
	if (CorsUtils.isCorsRequest(request)) {
		CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
		CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
		CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
	}
	return executionChain;
}

可以看到在第一行Object handler = getHandlerInternal(request)获得了一个handler对象,而这个handler对象就是HandlerMethod

4、getHandlerInternal是怎么做到的。
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
	
	//先拿到请求路径
	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
	
	this.mappingRegistry.acquireReadLock();//使用读取锁
	
	try {

		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
		
		return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
	
	}finally {
		this.mappingRegistry.releaseReadLock();
	}
}

请求的链接lookupPath,然后根据这个lookupPath和request对象调用了这个lookupHandlerMethod()方法,得到了handlerMethod对象。再看到这个lookupHandlerMethod()方法

5、lookupHandlerMethod做了什么
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	List<Match> matches = new ArrayList<Match>();
	List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
	if (directPathMatches != null) {
		addMatchingMappings(directPathMatches, matches, request);
	}
	if (matches.isEmpty()) {
		addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
	}

	if (!matches.isEmpty()) {
		Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
		Collections.sort(matches, comparator);
	
		Match bestMatch = matches.get(0);
		
		...........

		handleMatch(bestMatch.mapping, lookupPath, request);
		return bestMatch.handlerMethod;
	}else {
		return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
	}
}

这里看到关键代码

List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath)

1、他根据url获取了一个directPathMatches对象,而这个directPathMatches对象是一个集合,里面装的是RequestMappingInfo对象
2、而这个RequestMappingInfo对象就是我们的关键,后面他会根据这个RequestMappingInfo对象去得到HandlerMethod对象。我们看到这个RequestMappingInfo保存了这次请求的许多信息。

现在看一下他是如何获取这个RequestMappingInfo对象的。

public List<T> getMappingsByUrl(String urlPath) {
	return this.urlLookup.get(urlPath);
}
class MappingRegistry {

	private final Map<T, MappingRegistration<T>> registry = new HashMap<T, MappingRegistration<T>>();

	private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<T, HandlerMethod>();

	private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<String, T>();
}

可以看到所有的RequestMappingInfo对象,是存在内部类的一个属性中。而这个内部类就是MappingRegistry,他就是在bstractHandlerMethodMapping内部类中。

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request){
	List<Match> matches = new ArrayList<Match>();
	List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
	if (directPathMatches != null) {
		addMatchingMappings(directPathMatches, matches, request);
	}
	if (matches.isEmpty()) {
		addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
	}
	..........
}

这里的directPathMatches是一个数组,所以说可能会有多个RequestMappingInfo对象,在通过addMatchingMappings来找到最合适的HandlerMethod。

我们也终于得到了HandlerMethod对象,然后DispatcherServlet会将它交给HandlerAdater对象,然后执行反射调用。从而完成这个controller方法的调用。

本文转自:https://blog.csdn.net/songzehao/article/details/84979847

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值