spring MVC之自定义动态路由

简单回顾spring MVC

  1. DispatcherServletAutoConfiguration自动注册DispatcherServlet bean
  2. DispatcherServletRegistrationConfiguration基于DispatcherServlet bean自动注册Servlet注册器 bean:ServletRegistrationBean
  3. JettyEmbeddedWebAppContext设置ServletContextInitializerConfiguration,ServletContextInitializerConfiguration基于ServletContextInitializer bean构建,作为Handler注册为由Jetty的Server实例所管理的bean,Server启动前先启动它所管理的bean,即包含启动JettyEmbeddedWebAppContext,调用父类:WebAppContext.doStart,启动上下文
  4. 配置前置动作WebAppContext.preConfigure
  5. 调用父类启动动作:ContextHandler.doStart。启动上下文:startContext,调用子类重写的WebAppContext.startContext
  6. 遍历_configurations执行配置:ServletContextInitializerConfiguration.configure。将ServletContextInitializerConfiguration.Initializer添加至WebAppContext上下文的可管理bean,此时上下文正处于启动中,因此立即启动Initializer
  7. 启动Initializer.doStart,回调ServletContextInitializer.onStartup。即:EmbeddedWebApplicationContext.getSelfInitializer方法中创建的匿名实现类ServletContextInitializer直接回调EmbeddedWebApplicationContext.selfInitialize
  8. EmbeddedWebApplicationContext.selfInitialize通过ServletContextInitializerBeans获取并排序ServletContextInitializer bean集合后遍历工厂中ServletContextInitializer bean回调onStartup
  9. 回调ServletContextInitializer bean实现类之一(其他实现例如:FilterRegistrationBean、ServletListenerRegistrationBean等):ServletRegistrationBean.onStartup
  10. DispatcherServlet自动注册bean将该Servlet即mapping映射(默认为根:/)注册至Servlet上下文
  11. 由DispatcherServlet转发所有的web请求至对应RequestMapping映射的业务处理类

DispatcherServlet

  1. 父类doGet、doPost都会直接转至子类DispatcherServlet.doService处理
  2. doService转至org.springframework.web.servlet.DispatcherServlet#doDispatch处理
  3. 根据Request请求获取业务处理类句柄org.springframework.web.servlet.DispatcherServlet#getHandler
  4. 遍历org.springframework.web.servlet.DispatcherServlet#handlerMappings根据Request请求获取处理链org.springframework.web.servlet.HandlerMapping#getHandler

org.springframework.web.servlet.DispatcherServlet#handlerMappings

  1. 该句柄映射在spring应用上下文刷新完成时进行初始化
  2. org.springframework.web.servlet.FrameworkServlet.ContextRefreshListener#onApplicationEvent该监听器回调子类初始化策略:org.springframework.web.servlet.DispatcherServlet#initStrategies
  3. 初始化句柄映射:org.springframework.web.servlet.DispatcherServlet#initHandlerMappings

初始化句柄映射

  1. 如果org.springframework.web.servlet.DispatcherServlet#detectAllHandlerMappings为true并且工厂中存在HandlerMapping bean则使用HandlerMapping的bean列表作为句柄映射
  2. 否则,指定bean名称:handlerMapping,类型HandlerMapping为句柄映射
  3. 否则,从DispatcherServlet.properties配置中获取key=org.springframework.web.servlet.HandlerMapping的配置为句柄映射

RequestMappingHandlerMapping

org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration自动配置org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.EnableWebMvcConfiguration#requestMappingHandlerMapping句柄映射处理逻辑

  1. 调用父类方法构建句柄映射:org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#requestMappingHandlerMapping
  2. 调用子类重写方法创建句柄映射:org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#createRequestMappingHandlerMapping
  3. 如果org.springframework.beans.factory.ObjectProvider#getIfUnique不为空则调用org.springframework.boot.autoconfigure.web.WebMvcRegistrations#getRequestMappingHandlerMapping返回RequestMappingHandlerMapping,否则调用父类方法创建:org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#createRequestMappingHandlerMapping,直接无参构造器构建
  4. 设置mapping order顺序为0
  5. 设置拦截器:org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#getInterceptors,等等一系列设置
  6. 获取路径匹配配置:org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#getPathMatchConfigurer
  7. 设置是否使用后缀模式匹配,默认为true,例如:路径"/users"与路径"/users.*"映射的业务逻辑均是匹配的
  8. 设置是否使用已注册的后缀模式匹配,默认为false,后缀模式匹配仅针对显示用ContentNegotiationManager注册的后缀生效。例如:.json,.html等显示注册的后缀
  9. 设置是否使用结尾为"/“斜划线情况的匹配,默认为true,例如:”/users" 与 "/users/"均匹配
  10. 设置url路径工具类,默认为空,工具类由配置类提供:org.springframework.web.servlet.config.annotation.PathMatchConfigurer#getUrlPathHelper

根据请求获取业务处理句柄类

假定RequestMappingHandlerMapping句柄映射处理

  1. 根据Request请求调用父类方法获取:org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
  2. 根据Request请求获取句柄内部处理类:org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandlerInternal,钩子方法由子类实现具体逻辑
  3. 如果为空则获取默认句柄处理类,默认为null:org.springframework.web.servlet.handler.AbstractHandlerMapping#getDefaultHandler

如果以上三种均没有找到对应的业务处理类,则抛出404异常页面或者如果设置throwExceptionIfNoHandlerFound为true则抛出NoHandlerFoundException类型异常

根据请求获取内部业务处理句柄类

根据下图中的HandlerMapping模型,可以确定子类实现类为:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal

spring MVC之自定义动态路由_第1张图片

根据请求获取业务处理句柄类。调用父类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;
    }
	...
}

你可能感兴趣的:(java)