专栏首页BAT的乌托邦【小家Spring】Spring MVC容器的web九大组件之---HandlerMapping源码详解(一)---BeanNameUrlHandlerMapping系列

【小家Spring】Spring MVC容器的web九大组件之---HandlerMapping源码详解(一)---BeanNameUrlHandlerMapping系列

前言

在这篇文章里: 【小家Spring】Spring MVC容器启动时,web九大组件初始化详解(Spring MVC的运行机制) 已经大概介绍过web九大组件,本文将聚焦于Spring MVC中最重要的一个组件:HandlerMapping展开讨论

HandlerMapping

用来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行处理呢?这就是HandlerMapping需要做的事

HandlerMapping:负责映射用户的URL和对应的处理类HandlerHandlerMapping并没有规定这个URL与应用的处理类如何映射。所以在HandlerMapping接口中仅仅定义了根据一个URL必须返回一个由HandlerExecutionChain代表的处理链,我们可以在这个处理链中添加任意的HandlerAdapter实例来处理这个URL对应的请求(这样保证了最大的灵活性映射关系)。

public interface HandlerMapping {
	//@since 4.3.21
	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;

}

看看它的继承树:

它有两大继承主线:MatchableHandlerMappingAbstractHandlerMapping

AbstractHandlerMapping

这是Spring的常用模式了,一言不合就先来个抽象实现。查看它的继承图谱:

有必要先对两个XXXSupport进行一个非常简单的说明~

WebApplicationObjectSupportApplicationObjectSupport

看他两的申明,他俩更像是ApplicationContextAwareServletContextAware的适配器

public abstract class ApplicationObjectSupport implements ApplicationContextAware { ... }

public abstract class WebApplicationObjectSupport extends ApplicationObjectSupport implements ServletContextAware { ... }

所以我们如果我们在继承允许的情况下,只需要继承此类就能自动拥有上面两个接口的功能了。

@Service
public class HelloServiceImpl extends WebApplicationObjectSupport implements HelloService {
    @Override
    public Object hello() {
        // 继承自ApplicationObjectSupport就可以很方便的获取到下面这两个值
        System.out.println(super.getApplicationContext());
        System.out.println(super.obtainApplicationContext()); //@since 5.0
        // MessageSourceAccessor参考:MessageSourceAware   它是对MessageSource的一个包装  处理国际化
        System.out.println(super.getMessageSourceAccessor());

        // 这里需要继承和web相关的:WebApplicationObjectSupport
        System.out.println(super.getWebApplicationContext());
        System.out.println(super.getServletContext());
        System.out.println(super.getTempDir()); //Tomcat9_demowar\work\Catalina\localhost\demo_war_war

        return "service hello";
    }

    @Override
    protected void initApplicationContext() throws BeansException {
        // 这是父类提供给子类的(父类为空实现~),子类可以自行实现,实现子类的逻辑
        // 比如子类AbstractDetectingUrlHandlerMapping就复写了此方法去detectHandlers();
        super.initApplicationContext();
    }
}

就这样可以通过继承的方式快速的实现获取上下文等,推荐使用~~~

WebApplicationObjectSupport用于提供上下文ApplicationContextServletContext的功能~ 很显然如果你已经有继承了,那就没办法只能选择实现接口的方式了~

继续来看看AbstractHandlerMapping这个抽象实现给我们做了哪些事情~

// 它自己又额外实现了BeanNameAware和Ordered排序接口
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
		implements HandlerMapping, Ordered, BeanNameAware {

	//默认的Handler,这边使用的Obejct,子类实现的时候,使用HandlerMethod,HandlerExecutionChain等
	// the default handler for this handler mapping
	@Nullable
	private Object defaultHandler;
	// url路径计算的辅助类、工具类
	private UrlPathHelper urlPathHelper = new UrlPathHelper();
	// Ant风格的Path匹配模式~  解决如/books/{id}场景
	private PathMatcher pathMatcher = new AntPathMatcher();

	// 保存着拦截器们~~~
	private final List<Object> interceptors = new ArrayList<>();
	// 从interceptors中解析得到,直接添加给全部handler
	private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();

	// 跨域相关的配置~
	private CorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
	private CorsProcessor corsProcessor = new DefaultCorsProcessor();

	// 最低的顺序(default: same as non-Ordered)
	private int order = Ordered.LOWEST_PRECEDENCE;
	@Nullable
	private String beanName;
	
	...
	
	// 关于UrlPathHelper 的属性的一些设置~~~
	public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {...}
	public void setUrlDecode(boolean urlDecode) { ... }
	public void setRemoveSemicolonContent(boolean removeSemicolonContent) { ... }
	public void setUrlPathHelper(UrlPathHelper urlPathHelper) { ... } //我们也是可议自己指定一个自己的UrlPathHelper 的
	...
	// PathMatcher我们也可以自己指定
	public void setPathMatcher(PathMatcher pathMatcher) { ... }

	// Set the interceptors to apply for all handlers mapped by this handler mapping
	// 可变参数:可以一次性添加多个拦截器~~~~  这里使用的Object
	public void setInterceptors(Object... interceptors) {
		this.interceptors.addAll(Arrays.asList(interceptors));
	}

	// 设值一个UrlBasedCorsConfigurationSource  Map表示它的一些属性们~~~
	public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) { ... }
	// 重载方法  @since 5.1  Spring5.1之后才有的方法
	public void setCorsConfigurationSource(CorsConfigurationSource corsConfigurationSource) {
		Assert.notNull(corsConfigurationSource, "corsConfigurationSource must not be null");
		this.corsConfigurationSource = corsConfigurationSource;
	}
	// Configure a custom {@link CorsProcessor} to use to apply the matched
	// @since 4.2
	public void setCorsProcessor(CorsProcessor corsProcessor) {
		Assert.notNull(corsProcessor, "CorsProcessor must not be null");
		this.corsProcessor = corsProcessor;
	}
	...

	// 这步骤是最重要的。相当于父类setApplicationContext完成了之后,就会执行到这里~~~
	// 这这步骤可议看出   这里主要处理的都是拦截器~~~相关的内容
	@Override
	protected void initApplicationContext() throws BeansException {
		// 给子类扩展:增加拦截器,默认为空实现
		extendInterceptors(this.interceptors);
		// 找到所有MappedInterceptor类型的bean添加到adaptedInterceptors中
		detectMappedInterceptors(this.adaptedInterceptors);
		// 将interceptors中的拦截器取出放入adaptedInterceptors
		// 如果是WebRequestInterceptor类型的拦截器  需要用WebRequestHandlerInterceptorAdapter进行包装适配
		initInterceptors();
	}

	// 去容器(含祖孙容器)内找到所有的MappedInterceptor类型的拦截器出来,添加进去   非单例的Bean也包含
	// 备注MappedInterceptor为Spring MVC拦截器接口`HandlerInterceptor`的实现类  并且是个final类 Spring3.0后出来的。
	protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
		mappedInterceptors.addAll(
				BeanFactoryUtils.beansOfTypeIncludingAncestors(
						obtainApplicationContext(), MappedInterceptor.class, true, false).values());
	}

	// 它就是把调用者放进来的interceptors们,适配成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));
			}
		}
	}
	// 适配其实也很简单~就是支持源生的HandlerInterceptor以及WebRequestInterceptor两种情况而已
	protected HandlerInterceptor adaptInterceptor(Object interceptor) {
		if (interceptor instanceof HandlerInterceptor) {
			return (HandlerInterceptor) interceptor;
		} else if (interceptor instanceof WebRequestInterceptor) {
			// WebRequestHandlerInterceptorAdapter它就是个`HandlerInterceptor`,内部持有一个WebRequestInterceptor的引用而已
			// 内部使用到了DispatcherServletWebRequest包request和response包装成`WebRequest`等等
			return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
		} else {
			throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
		}
	}


	protected final HandlerInterceptor[] getAdaptedInterceptors() { ... }
	// 它只会返回MappedInterceptor这种类型的,上面是返回adaptedInterceptors所有
	protected final MappedInterceptor[] getMappedInterceptors() { ... }

	// 这个方法也是一个该抽象类提供的一个非常重要的模版方法:根据request获取到一个HandlerExecutionChain
	// 也是抽象类实现接口HandlerMapping的方法~~~
	@Override
	@Nullable
	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		// 根据request获取对应的handler   抽象方法,由具体的子类去实现~~~~
		Object handler = getHandlerInternal(request);
		// 若没有匹配上处理器,那就走默认的处理器~~~   默认的处理器也是需要由子类给赋值  否则也会null的
		if (handler == null) {
			handler = getDefaultHandler();
		}
		// 若默认的处理器都木有  那就直接返回null啦~
		if (handler == null) {
			return null;
		}
		// 意思是如果是个String类型的名称,那就去容器内找这个Bean,当作一个Handler~
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}

		// 关键步骤:根据handler和request构造一个请求处理链~~
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

		// 4.2版本提供了对CORS跨域资源共享的支持  此处暂时略过~
		if (CorsUtils.isCorsRequest(request)) {
			...
		}

		return executionChain;
	}

	// 已经找到handler了,那就根据此构造一个请求链
	// 这里主要是吧拦截器们给糅进来~  构成对指定请求的一个拦截器链
	protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
		// 小细节:因为handler本身也许就是个Chain,所以此处需要判断一下~
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

		// 此处就用到了urlPathHelper来解析request 
		// 如我的请求地址为:`http://localhost:8080/demo_war_war/api/v1/hello`  那么lookupPath=/api/v1/hello
		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
		for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
			if (interceptor instanceof MappedInterceptor) {
			
				// 这里其实就能体现出MappedInterceptor的些许优势了:也就是它只有路径匹配上了才会拦截,没有匹配上的就不会拦截了,处理起来确实是更加的优雅些了~~~~
				// 备注:MappedInterceptor可以设置includePatterns和excludePatterns等~~~~~
				MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
				if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
					chain.addInterceptor(mappedInterceptor.getInterceptor());
				}
			} else {
				chain.addInterceptor(interceptor);
			}
		}
		return chain;
	}
	...
}

关于Web相关使用到的工具类,也可以参考到这里的: 【小家Spring】Spring MVC好用工具介绍:UrlPathHelper、WebUtils、RequestContextUtils、WebApplicationContextUtils…

AbstractHandlerMapping主要实现了对方法getHandler()的模版实现,它主要是对HandlerInterceptor进行了一个通用处理,最终会把他们放进HandlerExecutionChain里面去~~~

MappedInterceptor

一个包括includePatternsexcludePatterns字符串集合并带有HandlerInterceptor功能的类。 很明显,就是对于某些地址做特殊包括排除的拦截器。 Spring3.0推出的对HandlerInterceptor的实现类,且是个final类。所以扩展它并不是像通过继承HandlerInterceptorAdapter这样去扩展,而是通过了类似代理的方式~~~~

// @since 3.0  它是个final类  所以不允许你直接使用继承的方式来扩展
public final class MappedInterceptor implements HandlerInterceptor {

	// 可以看到它哥俩都是可以不用指定,可以为null的
	@Nullable
	private final String[] includePatterns;
	@Nullable
	private final String[] excludePatterns;
	// 持有一个interceptor的引用,类似于目标类~
	private final HandlerInterceptor interceptor;

	// 注意:该类允许你自己指定路径的匹配规则。但是Spring里,不管哪个上层服务,默认使用的都是Ant风格的匹配
	// 并不是正则的匹配  所以效率上还是蛮高的~
	@Nullable
	private PathMatcher pathMatcher;

	//======构造函数:发现它不仅仅兼容HandlerInterceptor,还可以把WebRequestInterceptor转换成此~
	public MappedInterceptor(@Nullable String[] includePatterns, HandlerInterceptor interceptor) {
		this(includePatterns, null, interceptor);
	}
	...
	public MappedInterceptor(@Nullable String[] includePatterns, @Nullable String[] excludePatterns,
			WebRequestInterceptor interceptor) {
		// 此处使用WebRequestHandlerInterceptorAdapter这个适配器~~~
		this(includePatterns, excludePatterns, new WebRequestHandlerInterceptorAdapter(interceptor));
	}

	// 原则:excludePatterns先执行,includePatterns后执行
	// 如果excludePatterns执行完都木有匹配的,并且includePatterns是空的,那就返回true(这是个处理方式技巧~  对这种互斥的情况  这一步判断很关键~~~)  
	public boolean matches(String lookupPath, PathMatcher pathMatcher) { ... }
	...
}

因为它不能简单的像扩展HandlerInterceptorAdapter一样使用,下面给出一个Demo,推荐大家以后都采用这种方案去更加优雅的处理你的拦截器:

@ComponentScan(value = "com.fsx", useDefaultFilters = false,
        includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class, ControllerAdvice.class, RestControllerAdvice.class})}
)
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    //// 方式一:最源生的使用方式:直接注册进去即可
    //// 其实它也挺强大,支持includePatterns和exclude...
    //// 其实它底层原理是一个依赖于`InterceptorRegistration`,它是个普通类,协助create一个`MappedInterceptor`
    //// 由此可见最终底层还是使用的`MappedInterceptor`哦~~~~~
    //@Override
    //public void addInterceptors(InterceptorRegistry registry) {
    //    registry.addInterceptor(new HelloInterceptor())
    //            .addPathPatterns() // 就是includePatterns
    //            .excludePathPatterns();
    //}

    // 方式二:如果说上述方式是交给Spring去帮我自动处理,这种方式相当于自己手动来处理~~~~~
    // 请务必注意:  请务必注意:此处的返回值必须是MappedInterceptor,而不能是HandlerInterceptor  否则不生效~~~
    // 因为BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), MappedInterceptor.class, true, false)
    // 这个方法它只去找MappedInterceptor类型,如果你是父类型,那就匹配不上了的~~~  这个工厂方法的Bean定义信息有关~~
    @Bean
    public MappedInterceptor myHandlerInterceptor() {
        String[] includePatterns = {"/api/v1/hello"};
        MappedInterceptor handlerInterceptor = new MappedInterceptor(includePatterns, new HelloInterceptor());
        return handlerInterceptor;
    }
}

HelloInterceptor自己的业务拦截逻辑写在自己里面即可。**需要注意的是,**若你的拦截器里想去使用Spring容器内的其它Bean,请不用使用new的方式,而是应该交给Spring管理(可用@Component)。然后此处可写如方法入参或者通过@Autowired的方式注入进来~~~~ 提供了两种方法,但推荐使用方式一,直观且不容易出错些~~~

Tips:在基于XML的配置中,如下配置其实使用的就是MappedInterceptor

<mvc:interceptors>
	<mvc:interceptor>
    	<!--拦截器mapping 符合的才会执行拦截器-->
    	<mvc:mapping path="/**"/>
         	<!--在拦截器mapping中除去下面的url -->
         	<mvc:exclude-mapping path="/transactional_test/*"/>
         	<!--执行的拦截器(其实这个Bean并没有必要放进容器里面)-->
         	<ref bean="apiInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

MatchableHandlerMapping

这是HandlerMapping的另外一个分支,这是它的一个子接口。

// @since 4.3.1 出现得挺晚的接口
public interface MatchableHandlerMapping extends HandlerMapping {
	// 确定给定的请求是否符合请求条件  pattern:模版
	@Nullable
	RequestMatchResult match(HttpServletRequest request, String pattern);
}

目前有两个类实现了此方法RequestMappingHandlerMappingAbstractUrlHandlerMapping,但是Spring内部并还没有调用过此接口方法,因此此处暂时略过此部分~


接下来最重要的就是以AbstractHandlerMapping为主线,看看他的真正实现类们了。它主要分为两大主线: AbstractUrlHandlerMapping方向和AbstractHandlerMethodMapping

AbstractHandlerMethodMapping系列是当前使用得最多的,基于方法的Handler模式,文章二会着重继续分析

AbstractUrlHandlerMapping系列

从命名中也能看出来,它和URL有关。它的大致思路为:将url对应的Handler保存在一个Map中,在getHandlerInternal方法中使用url从Map中获取Handler

// 虽然附加实现了MatchableHandlerMapping ,但本文并不准备详细分析
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {

	// 根路径 / 的处理器~
	@Nullable
	private Object rootHandler;
	// 是否使用斜线/匹配   如果为true  那么`/users`它也会匹配上`/users/`  默认是false的
	private boolean useTrailingSlashMatch = false;
	// 设置是否延迟初始化handler。仅适用于单实例handler   默认是false表示立即实例化
	private boolean lazyInitHandlers = false;

	// 这个Map就是缓存下,URL对应的Handler(注意这里只是handler,而不是chain)
	private final Map<String, Object> handlerMap = new LinkedHashMap<>();
	...

	// 这个就是父类留给子类实现的抽象方法,此抽象类相当于进行了进一步的模版实现~
	@Override
	@Nullable
	protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
		// 找到URL的后半段:如`/api/v1/hello`  由此可见Spring MVC处理URL路径匹配都是从工程名后面开始匹配的~~~~
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
		// 根据url查找handler 
		// 1、先去handlerMap里找,若找到了那就实例化它,并且并且给chain里加入一个拦截器:`PathExposingHandlerInterceptor`  它是个private私有类的HandlerInterceptor
		// 该拦截器的作用:request.setAttribute()请求域里面放置四个属性 key见HandlerMapping的常量们~~~
		// 2、否则就使用PathMatcher去匹配URL,这里面光匹配其实是比较简单的。但是这里面还解决了一个问题:那就是匹配上多个路径的问题
		// 因此:若匹配上多个路径了,就按照PathMatcher的排序规则排序,取值get(0)~~~最后就是同上,加上那个HandlerInterceptor即可
		// 需要注意的是:若存在uriTemplateVariables,也就是路径里都存在多个最佳的匹配的情况  比如/book/{id}和/book/{name}这两种。
		// 还有就是URI完全一样,但是一个是get方法,一个是post方法之类的  那就再加一个拦截器`UriTemplateVariablesHandlerInterceptor`  它request.setAttribute()了一个属性:key为 xxx.uriTemplateVariables
		// 这些Attribute后续都是有用滴~~~~~~ 请注意:这里默认的两个拦截器每次都是new出来的和Handler可议说是绑定的,所以不会存在线程安全问题~~~~
		Object handler = lookupHandler(lookupPath, request);
		
		// 若没找到:
		if (handler == null) {
			// 处理跟路径 / 和默认的Handler~~~~
			Object rawHandler = null;
			if ("/".equals(lookupPath)) {
				rawHandler = getRootHandler();
			}
			if (rawHandler == null) {
				rawHandler = getDefaultHandler();
			}
			if (rawHandler != null) {
				if (rawHandler instanceof String) {
					String handlerName = (String) rawHandler;
					rawHandler = obtainApplicationContext().getBean(handlerName);
				}
				validateHandler(rawHandler, request);
				// 就是注册上面说的默认的两个拦截器~~~~~~~  第四个参数为null,就只会注册一个拦截器~~~
				// 然后把rawHandler转换成chain(这个时候chain里面可能已经有两个拦截器了,然后父类还会继续把用户自定义的拦截器放上去~~~~)
				handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
			}
		}
		return handler;
	}

	// =========该抽象类提供的这个方法就特别重要了:向handlerMap里面put值的唯一入口~~~  可以批量urls
	protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
		Assert.notNull(urlPaths, "URL path array must not be null");
		for (String urlPath : urlPaths) {
			registerHandler(urlPath, beanName);
		}
	}
	protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
		Assert.notNull(urlPath, "URL path must not be null");
		Assert.notNull(handler, "Handler object must not be null");
		Object resolvedHandler = handler;

		// 如果是beanName,并且它是立马加载的~~~~
		if (!this.lazyInitHandlers && handler instanceof String) {
			String handlerName = (String) handler;
			ApplicationContext applicationContext = obtainApplicationContext();
			// 并且还需要是单例的,那就立马实例化吧~~~~
			if (applicationContext.isSingleton(handlerName)) {
				resolvedHandler = applicationContext.getBean(handlerName);
			}
		}

		// 先尝试从Map中去获取
		Object mappedHandler = this.handlerMap.get(urlPath);
		if (mappedHandler != null) {
			// 这个异常错误信息,相信我们在开发中经常碰到吧:简单就是说就是一个URL只能映射到一个Handler上(但是一个Handler是可以处理多个URL的,这个需要注意)
			// 这个校验必不可少啊~~~~
			if (mappedHandler != resolvedHandler) {
				throw new IllegalStateException("Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
						"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
			}
		} else {
			// 如果你的handler处理的路径是根路径,那太好了  你的这个处理器就很特殊啊~~~~
			if (urlPath.equals("/")) {
				setRootHandler(resolvedHandler);
			}
			// 这个路径相当于处理所有  优先级是最低的  所以当作默认的处理器来使用~~~~
			else if (urlPath.equals("/*")) {
				setDefaultHandler(resolvedHandler);
			}
			// 正常的路径了~~~
			// 注意此处:好像是Spring5之后 把这句Mapped的日志级别   直接降低到trace级别了,简直太低了有木有~~~
			// 在Spring 5之前,这里的日志级别包括上面的setRoot等是info(所以我们在控制台经常能看见大片的'Mapped URL path'日志~~~~)
			// 所以:自Spring5之后不再会看controller这样的映射的日志了(除非你日志界别调低~~~)可能Spring认为这种日志多,且不认为是重要的信息吧~~~
			else {
				this.handlerMap.put(urlPath, resolvedHandler);
				if (logger.isTraceEnabled()) {
					logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
				}
			}
		}
	}

	// 该缓存也提供了一个只读视图给调用者访问~~~
	public final Map<String, Object> getHandlerMap() {
		return Collections.unmodifiableMap(this.handlerMap);
	}

}

该抽象类提供了一个Map,缓存着了URL和它对应的Handler,这是个非常重要的缓存。它提供了registerHandler()允许子类调用,向缓存里注册url和handler的对应关系~

注意:此处肯定不能把Map放出去,让子类直接put的。因为程序必须要高内聚,才能保证更好的隔离性以及稳定性

AbstractDetectingUrlHandlerMapping

这又是个抽象类,继承自AbstractUrlHandlerMapping。它就越来越具有功能化了:Detecting表明它是有检测URL的功能的~

// @since 2.5  它Spring2.5后才出来
public abstract class AbstractDetectingUrlHandlerMapping extends AbstractUrlHandlerMapping {
	// 是否要去祖先容器里面检测所有的Handlers    默认是false表示只在自己的容器里面找
	// 若设置为true,相当于在父容器里的Controller也会被挖出来~~~~ 一般我并不建议这么去做
	private boolean detectHandlersInAncestorContexts = false;

	public void setDetectHandlersInAncestorContexts(boolean detectHandlersInAncestorContexts) {
		this.detectHandlersInAncestorContexts = detectHandlersInAncestorContexts;
	}

	// 说白了,这里是检测的入口 detectHandlers();
	@Override
	public void initApplicationContext() throws ApplicationContextException {
		super.initApplicationContext();
		detectHandlers();
	}

	protected void detectHandlers() throws BeansException {
		// 这个就不解释了:默认只会在当前容器里面去查找检测~~~
		// 注意:这里使用的Object.class  说明是把本容器内所有类型的Bean定义都拿出来了~~~~
		String[] beanNames = (this.detectHandlersInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
				getApplicationContext().getBeanNamesForType(Object.class));

		// Take any bean name that we can determine URLs for.
		for (String beanName : beanNames) {
			// 这是个抽象方法由子类去实现。  它的作用就是看看url和bean怎么才算是匹配呢?也就是说这个handler到底能够处理哪些URL呢?
			// 注意:此处还是类级别(Bean),相当于一个类就是一个Handler哦~~~~
			String[] urls = determineUrlsForHandler(beanName);
			if (!ObjectUtils.isEmpty(urls)) {
				// 注册进去  缓存起来~
				registerHandler(urls, beanName);
			}
		}
	}

}

AbstractDetectingUrlHandlerMapping是通过扫描方式注册Handler,收到请求时由AbstractUrlHandlerMappinggetHandlerInternal进行分发看看到底是交给哪个Handler进行处理~

最后真的是得看它实现类的时候了

BeanNameUrlHandlerMapping

它是AbstractDetectingUrlHandlerMapping的唯一实现类

说明:DefaultAnnotationHandlerMapping、BeanNameUrlHandlerMapping、AbstractControllerUrlHandlerMapping在Spring4.3的时候都被标记为过期,在Spring5以后直接就把这些类干掉了,因此本处说的唯一、源码都是基于Spring5.以上的版本的~~~

public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {

	@Override
	protected String[] determineUrlsForHandler(String beanName) {
		List<String> urls = new ArrayList<>();
		// 意思就是必须以/开头才行~~~~~~这算是一种约定吧~~~
		// 这种方式和@WebServlet方式一毛一样~~~~~
		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);
	}
}

该实现有点那啥,就是根据bean的名称来匹配URL。方式同@WebServlet一毛一样

SimpleUrlHandlerMapping

它是AbstractUrlHandlerMapping的直接实现类,也是一个基于Map的简单实现。

public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
	private final Map<String, Object> urlMap = new LinkedHashMap<>();

	public void setMappings(Properties mappings) {
		CollectionUtils.mergePropertiesIntoMap(mappings, this.urlMap);
	}
	public void setUrlMap(Map<String, ?> urlMap) {
		this.urlMap.putAll(urlMap);
	}

	@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) -> {
				// 如果还没有斜线,在前面加上斜线
				if (!url.startsWith("/")) {
					url = "/" + url;
				}
				if (handler instanceof String) {
					handler = ((String) handler).trim();
				}
				registerHandler(url, handler);
			});
		}
	}
}

它的实现就是吧开发者指定的一个Map,然后容器启动的时候把它注册进去即可,非常的简单的一个实现。当然我们自己已经知道了URL和Handler的映射关系了,然后需要进一步构造出一个HandlerMapping的时候,或许它是一个较快解决问题的选择~~~~ 它最重要的是urlMap这个参数~

它一般用于基于XML的配置文件的 形式,形如:

<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
	<property>
		<map>
			<entry key="/hello.do" value="myController"/>
			<entry key="/my.do" value="myController"/>
		</map>
	</property>
</bean>

Demo

写一个控制器:

@Controller("/hello") // 注意此处BeanName必须是/开头,否则是不会作为handler的
public class HelloController { }

这样项目启动的时候,就可以看到这么一句日志,证明url是映射成功了的~:

org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping.registerHandler Mapped URL path [/hello] onto handler '/hello'

但是请求后报出如下错误(http://localhost:8080/demo_war_war/hello):

No adapter for handler [com.fsx.controller.HelloController@5e31e1a4]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler

找不到适配器,这其实是下一节讲解HandlerAdapter的内容,此处介绍怎么做:

// 实现`org.springframework.web.servlet.mvc.Controller`这个接口,才被认为是一个控制器~
@Controller("/hello")
public class HelloController extends AbstractController {

    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("this is my demo");
        return null;
    }
}

这样子我们再请求,就能正常的进入handleRequestInternal方法来处理请求了。

我们的Controller类必须(直接|间接)实现org.springframework.web.servlet.mvc.Controller接口,否则无法判断具体处理方法是谁!!!

总结

本篇介绍的HandlerMapping,除了介绍它的抽象实现外。就是介绍了AbstractUrlHandlerMapping系列。 它主要实现是BeanNameUrlHandlerMappingSimpleUrlHandlerMapping,属于Spring最早期的控制器实现。完全是基于类级别的:一个类就是一个Handler,模式和源生的Servlet没太大差异(还是和Servlet源生API有耦合的~)。

显然这种模式在现在全注解时代已经完全过时了,开发、运行效率都太低下了。但是了解了这些过去的技术,才会发现这些都是一脉相承的,了解背后作者的设计意图、走向才是最为重要的目的

从分析DispatcherServlet的时候发现,SpringMVC默认是给容器内注入了两个HandlerMapping组件的:RequestMappingHandlerMappingBeanNameUrlHandlerMapping由此可见Spring还是保持了充分的向下兼容的~

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • [享学Eureka] 五、Eureka核心概念:应用(Application)和注册表(Applications)

    代码下载地址:https://github.com/f641385712/netflix-learning

    YourBatman
  • [享学Netflix] 一、Apache Commons Configuration:你身边的配置管理专家

    代码下载地址:https://github.com/f641385712/netflix-learning

    YourBatman
  • 从原理层面掌握@InitBinder的使用【享学Spring MVC】

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

    YourBatman
  • 1035. Spell checker

    用户1147447
  • Java 判断是否是 Ajax 异步请求

    一个会写诗的程序员
  • SpringMVC实现原理

    今天我们来实现一个简单的springMVC框架,可以理解为 springMVC1.0这个版本,只是功能比较简单而已;

    用户2141593
  • 写一个自己的springMVC?

    今天我们来实现一个简单的springMVC框架,可以理解为 springMVC1.0这个版本,只是功能比较简单而已;

    用户2141593
  • 写一个自己的springMVC

    今天我们来实现一个简单的springMVC框架,可以理解为 springMVC1.0这个版本,只是功能比较简单而已;

    矿泉水
  • 聊聊spring cloud的ConsulAutoConfiguration

    本文主要研究一下spring cloud的ConsulAutoConfiguration

    codecraft
  • 如何处理java中对{},[],() 的解析,不仅可以用正则,也可以用堆栈的方式,算法的想法做

    gfu

扫码关注云+社区

领取腾讯云代金券