上一篇
Spring MVC 核心类: HandlerMapping(一)
2. 详细说明
2.1 AbstractHandlerMapping
HandlerMapping 实现的基础类。这个基础类不支持暴露PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE 属性。这个属性的暴露取决于具体的子类实现,例如典型的通过URL映射来实现。
这个超类定义了默认handler、排序、拦截器的基本实现。
2.1.1 默认handler设置:
默认为null
。主要用于当无法匹配到handler时,返回这个具体指定的handler。
/**
* Set the default handler for this handler mapping.
* This handler will be returned if no specific mapping was found.
* <p>Default is {@code null}, indicating no default handler.
*/
public void setDefaultHandler(@Nullable Object defaultHandler) {
this.defaultHandler = defaultHandler;
}
2.1.2 顺序
默认为最低优先级,主要通过实现 Spring 框架中的 Ordered 接口来支撑。
private int order = Ordered.LOWEST_PRECEDENCE;
/**
* Specify the order value for this HandlerMapping bean.
* <p>The default value is {@code Ordered.LOWEST_PRECEDENCE}, meaning non-ordered.
* @see org.springframework.core.Ordered#getOrder()
*/
public void setOrder(int order) {
this.order = order;
}
2.1.3 URL 处理
通过 UrlPathHelper ,把URL的处理能力转交出去,从而实现各种URL处理的能力,例如实现ANT模式的URL处理方式。同时也提供了便利的设置 UrlPathHelper 属性的方法,方便直接配置:
- 可以设置一直使用全路径匹配;
- 可以设置URL的decode操作;
- 可以设置是否移除URL中分号的内容
通过 UrlPathHelper, Spring 框架可以提供灵活的URL处理方式,后续 HandlerMapping 子类实现中会使用到具体的实例来完成对应的功能。
2.1.4 拦截器(interceptor)
1.首先可以设置拦截器
设置匹配这个Handler映射的所有拦截器。目前支持的拦截器类型有HandlerInterceptor、WebRequestInterceptor、MappedInterceptor 三种。通过路径匹配找到对应拦截器处理。
/**
* Set the interceptors to apply for all handlers mapped by this handler mapping.
* <p>Supported interceptor types are HandlerInterceptor, WebRequestInterceptor, and MappedInterceptor.
* Mapped interceptors apply only to request URLs that match its path patterns.
* Mapped interceptor beans are also detected by type during initialization.
* @param interceptors array of handler interceptors
* @see #adaptInterceptor
* @see org.springframework.web.servlet.HandlerInterceptor
* @see org.springframework.web.context.request.WebRequestInterceptor
*/
public void setInterceptors(Object... interceptors) {
this.interceptors.addAll(Arrays.asList(interceptors));
}
2.初始化过程
首先 Spring 框架提供了在初始化或者是正式使用前的扩展点。支持用户自定义增加或者其他想要的操作来处理将要被配置的拦截器列表。
再次是在Spring Bean 容器中查找 MappedInterceptor 所有实例,包含父 Spring Bean 容器中的所有 MappedInterceptor 实例。同时把查找到的MappedInterceptor添加到adaptedInterceptors列表中。当然这是默认实现,如果你想改变这种策略,Spring 框架是支持通过重写 detectMappedInterceptors 方法来扩展。
最后把把 interceptors
列表中的拦截器添加到 adaptedInterceptors
中。放入代理的拦截器列表前会通过 MappedInterceptor 具体实现类型进行包装:如果直接实现HandlerInterceptor不包装;如果实现WebRequestInterceptor就使用 WebRequestHandlerInterceptorAdapter
包装。在默认实现中仅仅支持这两种类型的处理,如果需要可以自己扩展。
通过上面的处理,adaptedInterceptors
装载来所有的拦截器。
/**
* Initializes the interceptors.
* @see #extendInterceptors(java.util.List)
* @see #initInterceptors()
*/
@Override
protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.adaptedInterceptors);
initInterceptors();
}
2.1.5 获取Handler(getHandler)
根据Request,查找对应的Handler。当没有匹配的Handler时,使用指定的默认Handler。
/**
* Look up a handler for the given request, falling back to the default
* handler if no specific one is found.
* @param request current HTTP request
* @return the corresponding handler instance, or the default handler
* @see #getHandlerInternal
*/
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// 如果是使用的BeanName,从spring容器中获取
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
...
return executionChain;
}
实现中有两次扩展,可以供子类使用:
- getHandlerInternal :
- 真正的执行通过指定的Request查找对应的Handler,当无法匹配到对应的Handler 时,返回
null
。通过上面的 getHandler 方法的调用,我们可以看出,当此方法返回null
时,会触发默认 Handler 赋值。 - 这个方法你可以返回一个已经包装好的
HandlerExecutionChain
。因为 HandlerExecutionChain 中已经存在了相关拦截器的配置,所以这些拦截器会动态和HandlerMapping中配置的拦截器进行合并。也就是说静态的拦截器会合并到已经提供的 HandlerExecutionChain 中。
- 真正的执行通过指定的Request查找对应的Handler,当无法匹配到对应的Handler 时,返回
- getHandlerExecutionChain
- getHandler 方法在调用getHandlerInternal后获取到具体的handler后调用该方法。主要用于构建一个 Handler 处理器的处理链路。在构建这个处理链时,会选择合适的拦截器。
- 构建一个默认标准的
HandlerExecutionChain
包含指定的Handler、通用映射对应的拦截器、以及和当前请求URL匹配的MappedInterceptor
。拦截器的顺序依赖注册顺序的先后。如果需求,子类可以重写这个顺序来扩展/重新构建这个拦截器列表。 - 这个方法支持用原始的Handler创建新的HandlerExecutionChain和传入的HandlerExecutionChain两种方式。
如果仅仅是简单的在子类中增加一个拦截器,可以考虑调用super.getHandlerExecutionChain(handler, request)
,同时调用 HandlerExecutionChain 中的addInterceptor
方法。
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
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;
}
2.2 AbstractUrlHandlerMapping
用URL映射方式的基础HandlerMapping实现类。本类提供了通过URL或者配置的具体的URL查找方式的HandlerMapping实现的基础设施,对于后者,可以通过“alwaysUseFullPath”属性来了解更多。
支持直接匹配,例如配置 “/test” 来映射 “/test”;或者使用ANT形式来匹配,例如配置“/t”来映射“/test”和“/team”,使用“/test/”来匹配所有“/test”的直接目录下的URL,使用“/test/**”来匹配“/test”下面所有的URL包含子目录下的链接。更多配置详情,可以参考 AntPathMatcher 。
匹配过程中会查询所有匹配路径,同时找到最长匹配作为最精确的匹配。
同时此基础类定义了handlerMap,这个Map定义了URL映射关系。本基础类提供了Map注册的模版方法(registerHandler),供其子类调用。
2.2.1 设置根目录对应的Handler,即为“/”的handler。默认情况为null
/**
* Set the root handler for this handler mapping, that is,
* the handler to be registered for the root path ("/").
* <p>Default is {@code null}, indicating no root handler.
*/
public void setRootHandler(Object rootHandler) {
this.rootHandler = rootHandler;
}
2.2.2 覆盖getHandlerInternal 方法
@Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
Object handler = lookupHandler(lookupPath, request);
// 当通过URL路径无法匹配时,优先处理根Handler,再使用默认Handler。
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
if ("/".equals(lookupPath)) {
rawHandler = getRootHandler();
}
if (rawHandler == null) {
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// Bean name or resolved handler?
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
rawHandler = getApplicationContext().getBean(handlerName);
}
validateHandler(rawHandler, request);
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
…
return handler;
}
2.2.3 查到Handler (lookupHandler)
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// 1. 如果可以通过URL直接映射,使用这个映射对应的Handler
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
// 2. 通过匹配进行映射
List<String> matchingPatterns = new ArrayList<String>();
// 2.1 找到所有的可能的映射对应的Handler
for (String registeredPattern : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPattern, urlPath)) {
matchingPatterns.add(registeredPattern);
}
else if (useTrailingSlashMatch()) {
if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
matchingPatterns.add(registeredPattern + "/");
}
}
}
// 2.2 决策最优映射
String bestMatch = null;
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
if (!matchingPatterns.isEmpty()) {
// 通过路径比较器对所有匹配的映射进行排序,最长映射放在第一位
Collections.sort(matchingPatterns, patternComparator);
if (logger.isDebugEnabled()) {
logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
}
// 返回第一个映射,即为最优映射
bestMatch = matchingPatterns.get(0);
}
if (bestMatch != null) {
// 通过最优的匹配路径来找对应的Handler,如果没有找到对应的Handler,报错
handler = this.handlerMap.get(bestMatch);
if (handler == null) {
if (bestMatch.endsWith("/")) {
handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
}
if (handler == null) {
throw new IllegalStateException(
"Could not find handler for best pattern match [" + bestMatch + "]");
}
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);
// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
// for all of them
Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
uriTemplateVariables.putAll(decodedVars);
}
}
if (logger.isDebugEnabled()) {
logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
}
return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
}
// No handler found...
return null;
}
2.2.4 模版方法:registerHandler
通过给定的URL路径,注册指定的Handler。
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
...
Object resolvedHandler = handler;
...
// 从HandlerMap中获取URL路径对应的Handler
Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
// 如果出现一个URL路径两个匹配的Handler的情况报错
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(...);
}
}
else {
// 如果是根目录的情况下,设置根目录处理器
if (urlPath.equals("/")) {
...
setRootHandler(resolvedHandler);
}
// 如果是根目录匹配的情况下,设置根目录处理器
else if (urlPath.equals("/*")) {
...
setDefaultHandler(resolvedHandler);
}
// 其他情况下,设置URL路径和处理器的映射
else {
this.handlerMap.put(urlPath, resolvedHandler);
...
}
}
}
2.3 AbstractUrlHandlerMapping 子类
2.3.1 BeanNameUrlHandlerMapping
该类是 AbstractDetectingUrlHandlerMapping 的子类。在 AbstractDetectingUrlHandlerMapping 中定义了检测所有Bean的基础方法。其子类通过扩展 determineUrlsForHandler 方法进行判断是否需要进行URL注册。
protected void detectHandlers() throws BeansException {
ApplicationContext applicationContext = obtainApplicationContext();
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
applicationContext.getBeanNamesForType(Object.class));
// Take any bean name that we can determine URLs for.
for (String beanName : beanNames) {
String[] urls = determineUrlsForHandler(beanName);
if (!ObjectUtils.isEmpty(urls)) {
// URL paths found: Let's consider it a handler.
registerHandler(urls, beanName);
}
}
...
}
BeanNameUrlHandlerMapping 扩展了 determineUrlsForHandler,定义通过Bean名称来定义URL,通过配置Bean名称的别名(以“/”开头)来确定对应的URL:
@Override
protected String[] determineUrlsForHandler(String beanName) {
List<String> urls = new ArrayList<>();
if (beanName.startsWith("/")) {
urls.add(beanName);
}
String[] aliases = obtainApplicationContext().getAliases(beanName);
for (String alias : aliases) {
if (alias.startsWith("/")) {
urls.add(alias);
}
}
return StringUtils.toStringArray(urls);
}
2.3.2 SimpleUrlHandlerMapping
这个类是通过配置URL到对应的处理器的Bean名称的方式的HandlerMapping。支持URL到bean实例和URL到bean名称两种方式。使用Bean名称映射时,仅仅支持该Bean为单例模式,否则无法通过Bean名称初始化。
配置URL到处理器Handler有两种方式:
1.通过属性文件配置,属性文件的的样子如下:
/welcome.html=ticketController
/show.html=ticketController
2.直接通过XML配置 urlMap
/**
* Set a Map with URL paths as keys and handler beans (or handler bean names)
* as values. Convenient for population with bean references.
* <p>Supports direct URL matches and Ant-style pattern matches. For syntax
* details, see the {@link org.springframework.util.AntPathMatcher} javadoc.
* @param urlMap map with URLs as keys and beans as values
* @see #setMappings
*/
public void setUrlMap(Map<String, ?> urlMap) {
this.urlMap.putAll(urlMap);
}
同时本类定义了处理URL集合的方法
/**
* 触发解析URL配置
*/
@Override
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
registerHandlers(this.urlMap);
}
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
if (urlMap.isEmpty()) {
logger.trace("No patterns in " + formatMappingName());
}
else {
urlMap.forEach((url, handler) -> {
// Prepend with slash if not already present.
if (!url.startsWith("/")) {
url = "/" + url;
}
// Remove whitespace from handler bean name.
if (handler instanceof String) {
handler = ((String) handler).trim();
}
registerHandler(url, handler);
});
...
}
}
综上,这个类处理的核心是配置具体的URL到Controller的映射关系。当然URL可以遵循 ANT 模式。
2.3.3 WelcomePageHandlerMapping
首先在构建时定义默认的Controller和根路径处理Handler:
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
ApplicationContext applicationContext, Optional<Resource> welcomePage,
String staticPathPattern) {
if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
logger.info("Adding welcome page: " + welcomePage.get());
setRootViewName("forward:index.html");
}
else if (welcomeTemplateExists(templateAvailabilityProviders,
applicationContext)) {
logger.info("Adding welcome page template: index");
setRootViewName("index");
}
}
private void setRootViewName(String viewName) {
ParameterizableViewController controller = new ParameterizableViewController();
controller.setViewName(viewName);
setRootHandler(controller);
setOrder(2);
}
再次覆盖 getHandlerInternal 方法,使用超类中通过URL匹配不到就判断是否为根路径同时使用根路径对应的处理Handler的逻辑,达到使用通用的欢迎页的Controller的目的。
@Override
public Object getHandlerInternal(HttpServletRequest request) throws Exception {
for (MediaType mediaType : getAcceptedMediaTypes(request)) {
if (mediaType.includes(MediaType.TEXT_HTML)) {
return super.getHandlerInternal(request);
}
}
return null;
}
综上,这个类处理的核心是配置Index对应的Controller为默认的根处理Handler。
下一篇:方法级别的映射分析
Spring MVC 核心类: HandlerMapping(三)