本文已参与「新人创作礼」活动,一起开启掘金创作之路。
简单回顾spring MVC
- DispatcherServletAutoConfiguration自动注册DispatcherServlet bean
- DispatcherServletRegistrationConfiguration基于DispatcherServlet bean自动注册Servlet注册器 bean:ServletRegistrationBean
- JettyEmbeddedWebAppContext设置ServletContextInitializerConfiguration,ServletContextInitializerConfiguration基于ServletContextInitializer bean构建,作为Handler注册为由Jetty的Server实例所管理的bean,Server启动前先启动它所管理的bean,即包含启动JettyEmbeddedWebAppContext,调用父类:WebAppContext.doStart,启动上下文
- 配置前置动作WebAppContext.preConfigure
- 调用父类启动动作:ContextHandler.doStart。启动上下文:startContext,调用子类重写的WebAppContext.startContext
- 遍历_configurations执行配置:ServletContextInitializerConfiguration.configure。将ServletContextInitializerConfiguration.Initializer添加至WebAppContext上下文的可管理bean,此时上下文正处于启动中,因此立即启动Initializer
- 启动Initializer.doStart,回调ServletContextInitializer.onStartup。即:EmbeddedWebApplicationContext.getSelfInitializer方法中创建的匿名实现类ServletContextInitializer直接回调EmbeddedWebApplicationContext.selfInitialize
- EmbeddedWebApplicationContext.selfInitialize通过ServletContextInitializerBeans获取并排序ServletContextInitializer bean集合后遍历工厂中ServletContextInitializer bean回调onStartup
- 回调ServletContextInitializer bean实现类之一(其他实现例如:FilterRegistrationBean、ServletListenerRegistrationBean等):ServletRegistrationBean.onStartup
- DispatcherServlet自动注册bean将该Servlet即mapping映射(默认为根:/)注册至Servlet上下文
- 由DispatcherServlet转发所有的web请求至对应RequestMapping映射的业务处理类
DispatcherServlet
- 父类doGet、doPost都会直接转至子类DispatcherServlet.doService处理
- doService转至org.springframework.web.servlet.DispatcherServlet#doDispatch处理
- 根据Request请求获取业务处理类句柄org.springframework.web.servlet.DispatcherServlet#getHandler
- 遍历org.springframework.web.servlet.DispatcherServlet#handlerMappings根据Request请求获取处理链org.springframework.web.servlet.HandlerMapping#getHandler
org.springframework.web.servlet.DispatcherServlet#handlerMappings
- 该句柄映射在spring应用上下文刷新完成时进行初始化
- org.springframework.web.servlet.FrameworkServlet.ContextRefreshListener#onApplicationEvent该监听器回调子类初始化策略:org.springframework.web.servlet.DispatcherServlet#initStrategies
- 初始化句柄映射:org.springframework.web.servlet.DispatcherServlet#initHandlerMappings
初始化句柄映射
- 如果org.springframework.web.servlet.DispatcherServlet#detectAllHandlerMappings为true并且工厂中存在HandlerMapping bean则使用HandlerMapping的bean列表作为句柄映射
- 否则,指定bean名称:handlerMapping,类型HandlerMapping为句柄映射
- 否则,从DispatcherServlet.properties配置中获取key=org.springframework.web.servlet.HandlerMapping的配置为句柄映射
RequestMappingHandlerMapping
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration自动配置org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.EnableWebMvcConfiguration#requestMappingHandlerMapping句柄映射处理逻辑
- 调用父类方法构建句柄映射:org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#requestMappingHandlerMapping
- 调用子类重写方法创建句柄映射:org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#createRequestMappingHandlerMapping
- 如果org.springframework.beans.factory.ObjectProvider#getIfUnique不为空则调用org.springframework.boot.autoconfigure.web.WebMvcRegistrations#getRequestMappingHandlerMapping返回RequestMappingHandlerMapping,否则调用父类方法创建:org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#createRequestMappingHandlerMapping,直接无参构造器构建
- 设置mapping order顺序为0
- 设置拦截器:org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#getInterceptors,等等一系列设置
- 获取路径匹配配置:org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#getPathMatchConfigurer
- 设置是否使用后缀模式匹配,默认为true,例如:路径"/users"与路径"/users.*"映射的业务逻辑均是匹配的
- 设置是否使用已注册的后缀模式匹配,默认为false,后缀模式匹配仅针对显示用ContentNegotiationManager注册的后缀生效。例如:.json,.html等显示注册的后缀
- 设置是否使用结尾为"/"斜划线情况的匹配,默认为true,例如:"/users" 与 "/users/"均匹配
- 设置url路径工具类,默认为空,工具类由配置类提供:org.springframework.web.servlet.config.annotation.PathMatchConfigurer#getUrlPathHelper
根据请求获取业务处理句柄类
假定RequestMappingHandlerMapping句柄映射处理
- 根据Request请求调用父类方法获取:org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
- 根据Request请求获取句柄内部处理类:org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandlerInternal,钩子方法由子类实现具体逻辑
- 如果为空则获取默认句柄处理类,默认为null:org.springframework.web.servlet.handler.AbstractHandlerMapping#getDefaultHandler
如果以上三种均没有找到对应的业务处理类,则抛出404异常页面或者如果设置throwExceptionIfNoHandlerFound为true则抛出NoHandlerFoundException类型异常
根据请求获取内部业务处理句柄类
根据下图中的HandlerMapping模型,可以确定子类实现类为:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal
根据请求获取业务处理句柄类。调用父类org.springframework.web.servlet.handler.AbstractHandlerMapping#getUrlPathHelper方法获取Url路径工具类,默认实现为:org.springframework.web.util.UrlPathHelper
// 父类:AbstractHandlerMapping,默认getUrlPathHelper为:UrlPathHelper
private UrlPathHelper urlPathHelper = new UrlPathHelper();
public UrlPathHelper getUrlPathHelper() {
return urlPathHelper;
}
// 子类:AbstractHandlerMethodMapping
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 根据请求获取需要查找的url路径
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
if (logger.isDebugEnabled()) {
logger.debug("Looking up handler method for path " + lookupPath);
}
this.mappingRegistry.acquireReadLock();
try {
// 根据url路径获取对应业务处理类的具体方法
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
if (logger.isDebugEnabled()) {
if (handlerMethod != null) {
logger.debug("Returning handler method [" + handlerMethod + "]");
}
else {
logger.debug("Did not find handler method for [" + lookupPath + "]");
}
}
// 返回对应业务类的业务处理方法
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
总结
url与业务处理方法的映射关系通过HandlerMapping接口实现类维护,spring通过该实现类获取对应的handler进行业务处理。如果我们想要自定义动态的路由可以通过该扩展点进行入手,并且目前我们的生产线上也已经正式投入使用,简直不要太好用。生产的案例如下:
功能描述
app端请求网关http服务,路径均加了一个版本号例如:/myurl/v92/getFoodInfo。如果app没有升级到92版本是看不到该路径方法的,只有新升级的92或者93、94版本才能使用该路径获取相关食品信息。可以通过此方式进行版本灰发控制,仅给部分用户提供新版本安装包进行更新安装后可以使用新功能,其他老版本app不会受影响,因为代码仅修改了92版本的业务逻辑,92以下的版本业务逻辑是没有任何代码改动的。通过该方式可以有效的规避升级风险
实现方法
配置自定义RequestMapping句柄映射
@Configuration
public class WebMvcConfig extends WebMvcRegistrationsAdapter {
public WebMvcConfig() {
}
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new CustomRequestMappingHandlerMapping();
}
}
自定义RequestMapping句柄映射中重写获取Url路径工具类方法,返回自定义的工具类
public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
...
@PostConstruct
public void init() throws Exception {
// 自定义的url路径版本映射逻辑
this.urlPathHelper = new CustomUrlPathHelper(this.versionManager.getVersionMap());
}
@Override
public UrlPathHelper getUrlPathHelper() {
return this.urlPathHelper;
}
...
}