前言
在这篇文章里:
【小家Spring】Spring MVC容器启动时,web九大组件初始化详解(Spring MVC的运行机制)
已经大概介绍过web九大组件,本文将聚焦于Spring MVC中最重要的一个组件:HandlerMapping展开讨论
HandlerMapping
用来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行处理呢?这就是HandlerMapping需要做的事
HandlerMapping:负责映射用户的URL和对应的处理类Handler,HandlerMapping并没有规定这个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; }
看看它的继承树:
它有两大继承主线:MatchableHandlerMapping和AbstractHandlerMapping
AbstractHandlerMapping
这是Spring的常用模式了,一言不合就先来个抽象实现。查看它的继承图谱:
有必要先对两个XXXSupport进行一个非常简单的说明~
WebApplicationObjectSupport和ApplicationObjectSupport
看他两的申明,他俩更像是ApplicationContextAware和ServletContextAware的适配器
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用于提供上下文ApplicationContext和ServletContext的功能~
很显然如果你已经有继承了,那就没办法只能选择实现接口的方式了~
继续来看看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
一个包括includePatterns和excludePatterns字符串集合并带有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>