springMVC HandlerMapping组件介绍

1:简单介绍

组件作用是根据请求信息获取对应的处理请求的handler,职责很单一,只干这一件事。该组件对应的接口是org.springframework.web.servlet.HandlerMapping,只有唯一的一个用来根据请求获取handler的方法getHandler(req),源码如下:

public interface HandlerMapping {
	String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
	String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
	String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
	String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
	String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
	String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
	String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
	
	@Nullable
	HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

其它的还有一些供子类使用的常量,比如最匹配handler常量BEST_MATCHING_HANDLER_ATTRIBUTE,按照一般的编程套路,会给该接口提供一个默认的抽象实现类,提供骨架逻辑来获取处理器+拦截器数组,然后暴露一个模板方法供子类实现,确实是如此,而这个抽象骨架类就是org.springframework.web.servlet.handler.AbstractHanlderMapping,这个模板方法是handleInernal(req),该方法是一个抽象方法,强制子类实现,定义如下:

@Nullable
protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;

好的,下面我们就从这个骨架抽象类开始吧!

2:AbstractHandlerMapping

2.1:类定义

  • 源码
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
		implements HandlerMapping, Ordered, BeanNameAware {}
  • 继承WebApplicationObjectSupport
    首先继承了org.springframework.web.context.support.WebApplicationObjectSupport,而该类定义如下:
public abstract class WebApplicationObjectSupport extends ApplicationObjectSupport implements ServletContextAware {}

因为实现了org.spingframework.web.context.ServletContextAware因此会通过方法setServletContext(sc)索要javax.servlet.ServletContext,然后再看ApplicationObjectSupport,其定义如下:

public abstract class ApplicationObjectSupport implements ApplicationContextAware {}

那么是通过setApplicationContext(sc)来索要ApplicationContext对象吗?其实不是的,setApplicationContext方法是在ApplicationObjectSupport方法中定义,但是该方法会调用模板方法initApplicationContext,而AbstractHandlerMapping正式通过重写该模板方法,从而实现加入spring的启动过程,从而实现handler interceptor的初始化工作的,那么在AbstractHandlerMapping中应该有setServletContext(sc)initWebApplicationContext两个方法分别来索要代表web容器的ServletContext加入spring加载过程的intiApplicationContext,其实只有后一个方法,前一个方法是在WebApplicationObjectSupport中定义的,源码如下:

org.springframework.web.context.support.WebApplicationObjectSupport
@Override
public final void setServletContext(ServletContext servletContext) {
}

org.springframework.web.servlet.handler.AbstractHandlerMapping
@Override
protected void initApplicationContext() throws BeansException {
}

这里的org.springframework.web.servlet.handler.AbstractHandlerMapping#initApplicationContext方法也正是HandlerInterceptor初始化的入口了。

  • 实现Orderd接口
    当有多个时可以指定顺序,值越小优先级越高。
  • 实现BeanNameAware接口
    通过方法setBeanName(name)索要自己作为spring容器bean的名称,源码如下:
@Override
public void setBeanName(String name) {
	this.beanName = name;
}

2.2:AbstractHandlerMapping#initApplicationContext

源码如下:

@Override
protected void initApplicationContext() throws BeansException {
	extendInterceptors(this.interceptors);
	detectMappedInterceptors(this.adaptedInterceptors);
	initInterceptors();
}

方法extendInterceptors(this.interceptors)是一个模板方法,目前并没有用到,可以忽略,方法detectMappedInterceptors(this.adptedInterceptors)方法用于从容器中获取org.springframework.web.servlet.handler.MappedInterceptor以及其祖先类型的bean集合,方法源码如下:

protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
		mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors(
			obtainApplicationContext(), MappedInterceptor.class, true, false).values());
}

直接使用BeanFactoryUtils工具类来获取,如果我们定义了如下两个拦截器:

public class DemoHandlerInterceptor implements HandlerInterceptor {}
public class DemoHandlerInterceptor1 implements HandlerInterceptor {}

并通过如下方式注册:

<mvc:interceptors>
	<mvc:interceptor>
		<mvc:mapping path="/testinterceptor/hi"/>
		<bean class="dongshi.handlerinterceptor.DemoHandlerInterceptor"></bean>
	</mvc:interceptor>
	<mvc:interceptor>
		<mvc:mapping path="/testinterceptor/hi"/>
		<bean class="dongshi.handlerinterceptor.DemoHandlerInterceptor1"/>
	</mvc:interceptor>
</mvc:interceptors>

此时debug看的话,就能够看到我们定义的HandlerInterceptor:
在这里插入图片描述
一共三个元素,其中第一个位置为springmvc自己的,可以忽略,第二个和第三个就是我们自己定义的拦截器。方法initInterceptors方法是将private final List<Object> interceptors = new ArrayList<>();中的拦截器转换为HandlerInterceptor然后添加到adaptedInterceptors集合中,方法如下:

protected void initInterceptors() {
	if (!this.interceptors.isEmpty()) {
		for (int i = 0; i < this.interceptors.size(); i++) {
			Object interceptor = this.interceptors.get(i);
			if (interceptor == null) {
				throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
			}
			this.adaptedInterceptors.add(adaptInterceptor(interceptor));
		}
	}
}

其中方法adtedInterceptor(interceptor)方法是转换的方法,支持HanlderInteceptorWebReqeustInterceptor两种类型,源码如下:

protected HandlerInterceptor adaptInterceptor(Object interceptor) {
	if (interceptor instanceof HandlerInterceptor) {
		return (HandlerInterceptor) interceptor;
	}
	else if (interceptor instanceof WebRequestInterceptor) {
		return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
	}
	else {
		throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
	}
}

到这里HandlerExecutionChain中的所需要的HandlerInterceptor已经全部准备好了,其实在initApplicationContext也就做了这么多工作,这个过程是在启动的过程中完成的,剩下的一部分就是Handler的获取了,这部分内容的获取是在请求过程中动态执行的,调用的方法是org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler

2.3:AbstractHandlerMapping#getHandler

当请求到达,最终会通过org.springframework.web.servlet.DispatcherServlet#doDispatch处理,具体可以看一次GET请求在springmvc中是的处理流程
,好的,入口我们也就找到了,源码如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ...
	mappedHandler = getHandler(processedRequest);
	...
}

其中getHandler方法会循环HandlerMapping实现类获取handler,代码如下:

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;
}

这里我们测试的接口如下:

@Controller
@RequestMapping("/testinterceptor")
public class TestinterceptorController {

	@RequestMapping("/hi")
	@ResponseBody
	public String hi() {
		String msg = "testinterceptor hi";
		System.out.println(msg);
		return msg;
	}
}

因此最终会使用RequestMappingHandlerMapping来获取这个@RequestMapping("/hi")的handler。访问接口/testinterceptor/hidebug查看:
在这里插入图片描述
说明一下这个getHandlerInternal是一个模板方法,调用的是RequestMappingHandlerMapping的具体实现,这里先不详细看内部细节,接下来是通过方法getHandlerExecutionChain方法获取handler相关的HandlerInterceptor,源码如下:

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
	HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
			(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
	// <1>
	String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
	// <2>
	for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
		if (interceptor instanceof MappedInterceptor) {
			MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
			if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
				chain.addInterceptor(mappedInterceptor.getInterceptor());
			}
		}
		else {
			chain.addInterceptor(interceptor);
		}
	}
	return chain;
}

<1>处代码获取请求的路径,<2>处代码遍历所有的adaptedInterceptors如果是org.springframework.web.servlet.handler.MappedInterceptor类型,则判断拦截路径匹配才添加到chain中,否则直接添加,比如我们自定义的如下的两个HandlerInterceptor:

public class DemoHandlerInterceptor implements HandlerInterceptor {}
public class DemoHandlerInterceptor1 implements HandlerInterceptor {}

这里只看定义虽然和MappedInterceptor没关系,但是实际是会通过MappedInterptor进行封装,如下debug:
在这里插入图片描述
我们自定义的DemoHandlerInterceptor被封装为了MappedInterceptor的一个属性而已。因此代码interceptor instanceof MappedInterceptor为true。最终自定义的两个handler都会被添加到chain中,debug如下:
在这里插入图片描述
到这就获取到了请求对应的org.springframework.web.servlet.HandlerExecutionChain对象了。

3:MatchableHanlderMapping

AbstractHandlerMapping只是HandlerMapping的其中一个子类,其类图如下图:
在这里插入图片描述
可以看到其另一个子接口MatchableHandlerMapping,其接口定义如下:

public interface MatchableHandlerMapping extends HandlerMapping {
	@Nullable
	RequestMatchResult match(HttpServletRequest request, String pattern);
}

只有一个方法match(req, res),提供了具体实现的子类有RequestMappingHandlerMappingAbstractUrlHandlerMapping添加这两个类后uml图如下:
在这里插入图片描述

4:DispatcherServlet的handlerMappings的初始化

DispatcherServlet的org.springframework.web.servlet.DispatcherServlet#handlerMappings是维护了所有的HandlerMapping的集合,会在请求到达时用来获取对应的handler。该变量的初始化是通过调用DispatcherServlet的org.springframework.web.servlet.DispatcherServlet#initHandlerMappings方法完成的,其实就是web容器按照servlet规范的要求来加载org.springframework.web.servlet.DispatcherServlet的时候来调用的,源码如下:

private void initHandlerMappings(ApplicationContext context) {
	this.handlerMappings = null;
	// <1>
	if (this.detectAllHandlerMappings) {
		Map<String, HandlerMapping> matchingBeans =
		BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerMappings = new ArrayList<>(matchingBeans.values());			AnnotationAwareOrderComparator.sort(this.handlerMappings);
		}
	}
	// <2>
	else {
		try {
			HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
			this.handlerMappings = Collections.singletonList(hm);
		}
		catch (NoSuchBeanDefinitionException ex) {
		}
	}
    // <3>
	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");
		}
	}
}

<1>处代码判断detectAllHandlerMappings如果是为true(该值默认为true,一般也不需要修改,如果有特殊需要修改,可以通过配置进行设置),则获取容器中HandlerMapping包括了其祖先类型的bean,debug查看变量Map<String, HandlerMapping> matchingBeans的结果如下:
在这里插入图片描述
获取后则设置到全局变量this.handlerMappings<2>处代码是直接从容器中获取bean名称为HANDLER_MAPPING_BEAN_NAME=handlerMapping的handlermapping。正常<3>处代码不会进入,我们可以通过在web.xml中设置检测开关为false,然后,容器中不要有名称为handlerMapping的HandlerMapping的bean,就可以了,设置如下:

<servlet>
	<servlet-name>letsGO</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<init-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>WEB-INF/letsGO-servlet.xml</param-value>
	</init-param>
	<init-param>
		<param-name>detectAllHandlerMappings</param-name>
		<param-value>false</param-value>
	</init-param>
</servlet>

代码getDefaultStrategies(context, clz)源码如下:

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
    // <1>
	String key = strategyInterface.getName();
	String value = defaultStrategies.getProperty(key);
	if (value != null) {
		// <2>
		String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
		// <3>
		List<T> strategies = new ArrayList<>(classNames.length);
		// <4>
		for (String className : classNames) {
			try {
				Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
				// <5>
				Object strategy = createDefaultStrategy(context, clazz);
				strategies.add((T) strategy);
			}
			catch (ClassNotFoundException ex) {
				...
			}
			catch (LinkageError err) {
			    ...
			}
		}
		return strategies;
	}
	else {
		return new LinkedList<>();
	}
}

先看下变量defaultStrategies其初始化源码如下:

private static final Properties defaultStrategies;
static {
	try {
		ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
		defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
	}
	catch (IOException ex) {
	}
}

直接加载和DispatcherServlet在相同路径的DEFAULT_STRATEGIES_PATH=DispatcherServlet.properties的文件的内容,该文件配置的都是一些默认的组件类,和此处相关的配置内容为:

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

将文件中键值对加载到变量defaultStrategies中。<1>处获取接口的名称其实就是配置文件中HandlerMapping的key,然后获取对应的值,debug看的话如下:
在这里插入图片描述
<2>处代码为将逗号分割字符串格式数据分割为String的数组,<3>处代码为最终的结果集合,<4>为循环创建Class实例对象,最终结果debug如下:
在这里插入图片描述
<5>处代码createDefaultStrategy(context, clz)是通过spring ioc容器创建对象,代码如下:

protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
	return context.getAutowireCapableBeanFactory().createBean(clazz);
}

最后:都让开,我要喝瑞幸

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值