SpringMVC请求映射建立和相应原理
本文主要讲解请求映射的建立和处理和拦截器
根据以前自定义SpringMVC的基础条件中,可以知道我们的创建条件无非以下几个步骤:
- 1、先扫描上下文;
- 2、判断类和方法上是否存在着对应的注解,并将信息进行封装;
- 3、添加映射;
- 4、请求过来时,根据条件来处理请求;
所以猜想以下springmvc中应该也是这样子来进行实现的,无非是功能实现上丰富一点。
一、解析HandlerMapping
什么是HandlerMapping
看看官方给的解释:由定义请求和处理程序对象之间的映射的对象实现的接口。
由定义请求和处理程序对象之间的映射的对象实现的接口。
这个类可以由应用程序开发人员实现,尽管这不是必需的,因为 org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping 和 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping 都包含在框架中。如果在应用程序上下文中没有注册 HandlerMapping bean,则前者是默认值。
HandlerMapping 实现可以支持映射拦截器,但不是必须的。处理程序将始终包装在 HandlerExecutionChain 实例中,可选地伴随一些 HandlerInterceptor 实例。 DispatcherServlet 将首先按给定顺序调用每个 HandlerInterceptor 的 preHandle 方法,如果所有 preHandle 方法都返回 true,则最后调用处理程序本身。
参数化这个映射的能力是这个 MVC 框架的一个强大而不同寻常的能力。例如,可以根据会话状态、cookie 状态或许多其他变量编写自定义映射。似乎没有其他 MVC 框架具有同样的灵活性。
注意:实现可以实现 org.springframework.core.Ordered 接口,以便能够指定排序顺序,从而指定 DispatcherServlet 应用的优先级。无序实例被视为最低优先级。
HandlerMapping的作用
通俗的来说,request交给哪个对象来进行处理,也就是说哪个对象来处理当前的request请求。
Handler和HandMethod
SpringMVC可以让HandlerMapping找到处理当前请求(request)的对象,这个对象叫做handler;
众所周知,handler是对象,对象要想处理request请求,肯定是需要对应的方法来进行处理,这个方法叫做HandMethod。
在SpringMVC中,作为handler来处理请求有好几种方式
直接看下DispatcherServlet.properties中的默认定义
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping
看下这三种HandlerMapping分别是怎么得到对应handler
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
每种HandlerMapping都会针对每种请求获取得到对应的handler,那么对于DispatchserServlet来说,就需要面对各种各样的handler。
难道springmvc中的DispatchserServlet要面对各种各样的handler进行if...else...来进行处理吗?
springmvc中设计了HandlerAdapter来进行适配各种各样的handler,利用handler来处理各种各样的reqeust。
springmvc中的hanler举例
直接看下Adapter接口中定义的方法
public interface HandlerAdapter {
// 是否支持这种类型的处理器
boolean supports(Object handler);
// 如果支持这种handler,那么就调用方法来进行处理
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
// 最后一次处理时间
long getLastModified(HttpServletRequest request, Object handler);
}
SimpleControllerHandlerAdapter
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
// handler是Controller类型的
@Override
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
// 利用Controller接口中的handleRequest来进行处理
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified) {
return ((LastModified) handler).getLastModified(request);
}
return -1L;
}
}
SimpleServletHandlerAdapter
public class SimpleServletHandlerAdapter implements HandlerAdapter {
// 支持实现了Servlet接口的类
@Override
public boolean supports(Object handler) {
return (handler instanceof Servlet);
}
// 调用里面的service处理
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((Servlet) handler).service(request, response);
return null;
}
// 最后一次修改时间
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}
HttpRequestHandlerAdapter
public class HttpRequestHandlerAdapter implements HandlerAdapter {
// 实现了HttpRequestHandler接口的类
@Override
public boolean supports(Object handler) {
return (handler instanceof HttpRequestHandler);
}
// 调用实现类handle来进行处理当前请求
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((HttpRequestHandler) handler).handleRequest(request, response);
return null;
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified) {
return ((LastModified) handler).getLastModified(request);
}
return -1L;
}
}
HandlerFunctionAdapter
public class HandlerFunctionAdapter implements HandlerAdapter {
// 实现了HandlerFunction接口的类
@Override
public boolean supports(Object handler) {
return handler instanceof HandlerFunction;
}
// 调用了handle方法来进行处理
@Nullable
@Override
public ModelAndView handle(HttpServletRequest servletRequest,
HttpServletResponse servletResponse,
Object handler) throws Exception {
WebAsyncManager asyncManager = getWebAsyncManager(servletRequest, servletResponse);
ServerRequest serverRequest = getServerRequest(servletRequest);
ServerResponse serverResponse;
if (asyncManager.hasConcurrentResult()) {
serverResponse = handleAsync(asyncManager);
}
else {
HandlerFunction<?> handlerFunction = (HandlerFunction<?>) handler;
serverResponse = handlerFunction.handle(serverRequest);
}
if (serverResponse != null) {
return serverResponse.writeTo(servletRequest, servletResponse, new ServerRequestContext(serverRequest));
}
else {
return null;
}
}
}
AbstractHandlerMethodAdapter
public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {
/**
* This implementation expects the handler to be an {@link HandlerMethod}.
* @param handler the handler instance to check
* @return whether or not this adapter can adapt the given handler
*/
// handler是HandlerMethod类型的
@Override
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
/**
* This implementation expects the handler to be an {@link HandlerMethod}.
*/
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
}
所以说handler并不是我们通常理解的在类上加了@Controller或者是@RestController以及@RequestMapping的类
二、Hanler和HandlerAdapter的初始化流程
看下DispatchserServlet的继承体系,可以看到HttpServletBean继承了HttpServlet,所以HttpServletBean也就有了HttpServletBean的功能,看一下init()方法
public final void init() throws ServletException {
.......
// Let subclasses do whatever initialization they like.
initServletBean();
}
看下具体的实现过程:
protected final void initServletBean() throws ServletException {
.......
try {
// 重点代码,创建web容器
this.webApplicationContext = initWebApplicationContext();
// 空实现
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
...........
}
看一下FrameworkServlet框架中对initWebApplicationContext的初始化过程
protected WebApplicationContext initWebApplicationContext() {
...........
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
..........
return wac;
}
看一下刷新的功能:org.springframework.web.servlet.DispatcherServlet#onRefresh
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
看下具体的初始化策略
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
重点看下初始化HandlerMapping的过程
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
// 首先从容器中找到类型为HandlerMapping类型的bean
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
// 不是空的,进行排序
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
// 如果没有,那么直接查找beanName叫做handlerMapping的bean
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
// 如果没有发现,那么就去根据默认策略来进行查询
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
for (HandlerMapping mapping : this.handlerMappings) {
if (mapping.usesPathPatterns()) {
this.parseRequestPath = true;
break;
}
}
}
看上面的日志打印,没有找到对servlet配置的HandlerMappings,只能够从DispatcherServlet.properties中来进行查找了
DispatcherServlet通过SPI机制来加载默认提供的相关组件,而SPI的核心就在于DispathcerServlet.properties文件:
看一下对应的处理逻辑:
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
if (defaultStrategies == null) {
try {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
}
// 获取得到HandlerMapping对应的key和value
String key = strategyInterface.getName();
String value = defaultStrategies.getProperty(key);
if (value != null) {
// 获取得到里面的所有值
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
// 放入到容器中来
List<T> strategies = new ArrayList<>(classNames.length);
for (String className : classNames) {
try {
// 加载并在容器中创建HandlerMapping类型的bean
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
}
catch (ClassNotFoundException ex) {
throw new BeanInitializationException(
"Could not find DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]", ex);
}
catch (LinkageError err) {
throw new BeanInitializationException(
"Unresolvable class definition for DispatcherServlet's default strategy class [" +
className + "] for interface [" + key + "]", err);
}
}
return strategies;
}
else {
return Collections.emptyList();
}
}
2.1、解析HandlerAdapter
原理类似于上面的HandlerMapping,具体可以参考spring给我们来进行配置的。
2.2、解析Handler和HandlerMethod
看一下RequestMappingHandlerMapping中的afterPropertiesSet在做的事情
每个类的说明:
-
AbstractHandlerMapping:提供基础设施支持,例如: 路径解析,拦截器,跨域。 规定了根据request得到handler的模板方法处理流程getHandler,具体如何根据request寻找到某个handler,则是由子类实现。
-
AbstractHandlerMethodMapping:囊括了对注解Controller寻找,建立映射和根据request找到对应handler的流程支持,核心在于建立Reuqest和HandlerMethod的映射关系,将识别处理器方法和建立映射的任务交给子类实现。
-
RequestMappingHandlerMapping:核心在于解析处理器方法和对应Controller上@RequestMapping注解,然后合并生成一个RequestMappingInfo作为映射的关键一环返回。
三、请求映射建立和处理阶段
以下讲解将会在注解的基础上来进行分析
3.1、自定义springmvc
想到以前自定义springmvc的时候的流程,无非是以下几个步骤:
- 1、在servlet的init方法中初始化集合,扫描当前整个路径;
- 2、获取得到类上标注了@Controller和@RequestMapping注解的类;
- 3、获取类中有@ReqeustMapping的方法;
- 4、将注解信息组合形成Map<String,RequestHandInfo>,key作为路径,value封装了处理的类和方法<Class,Method>
- 5、当一个请求(request)进来的时候,获取得到URL,然后从Map中进行匹配,找到对应的RequestHandInfo,然后调用class中的method来进行处理
而springmvc中也是如此,只不过建立请求映射阶段和处理请求比我们要做的更加完美。总结以下几点:
- 1、获取得到类和方法上@RequestMapping注解中的信息,分别解析成为RequestMappingInfo,然后调用RequestMappingInfo中的组合方法(combine),将类上的RequestMappingInfo信息和方法上的RequestMappingInfo信息组合形成新的RequestMappingInfo对象;而在RequestMappingInfo信息中,利用@RequestMapping信息会更加精准的找到我们的handlermethod来处理request请求;
- 2、在RequestMappingInfo中可以指定url映射的handlermethod。浏览器和服务器之间可以做内容协商功能,浏览器发送什么类型的数据(Content-Type执行),期望得到什么样的数据类型(Accept字段指定,减少排列组合,提高后台系统响应性能);
- 3、通过请求方式(get/post等)减少匹配次数,还有请求时候必须携带哪些请求头和请求参数来进行匹配;
- 4、预留扩展接口给用户自定义操作;
3.2、Springmvc中请求映射建立流程
直接来到RequestMappingHandlerMapping的afterPropertiesSet方法
public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setTrailingSlashMatch(useTrailingSlashMatch());
this.config.setContentNegotiationManager(getContentNegotiationManager());
if (getPatternParser() != null) {
this.config.setPatternParser(getPatternParser());
Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch,
"Suffix pattern matching not supported with PathPatternParser.");
}
else {
// 后缀匹配规则
this.config.setSuffixPatternMatch(useSuffixPatternMatch());
this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
this.config.setPathMatcher(getPathMatcher());
}
// 访问父类中的方法
super.afterPropertiesSet();
}
父类中的后置处理方式,初始化处理器方法
public void afterPropertiesSet() {
initHandlerMethods();
}
具体流程:
protected void initHandlerMethods() {
// 遍历所有的候选的beanname,这个时候已经是一个bean了
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
看下候选bean的流程
根据bean的名称来获取得到bean的类型。判断判断对应的类型是否是handler。
判断类上是否加了Controller或者RequestMapping注解
注解版的handler一定要是一个bean,类上加了Controller或者RequestMapping注解的类都可以是一个handler。
判断是一个handler之后,接着就去找对应的handlermethod
1、首先根据bean的名称去找到对应的bean的类型,也就是说找到类;
2、判断handler是否是经过动态代理过(CGLIB和JDK动态代理),如果是的话,找到本来的类;
3、筛选类中是否有继承和实现的方法以及去掉桥接方法,然后循环遍历对每个方法进行处理;
4、对每个类中加了@RequestMapping注解的方法进行封装成RequestMappingInfo对象,然后和类上的进行组合形成最终的RequestMappingInfo。
如下所示:
看一下RequestMappingInfo的封装过程
这里可以看到springmvc中给开发者预留的接口RequestCondition,当然,先把这个分析完成之后,再来分析如何自定义条件来进行操作。
5、将每个方法产生的RequestMappingInfo封装注册到集合中来
那么直接看下这里的map即可,这个map是每个类中的方法对应的RequestMappingInfo
final Map<Method, T> methodMap = new LinkedHashMap<>();
获取得到集合之后,看接下来是如何来进行操作的。
遍历key和value,key是对应的method,value是对应的RequestMappingInfo。
1、获取得到可执行的方法。这里是为了JDK动态代理设计的。通常我们的controller都是使用CGLIB来进行动态代理的;
2、开始来注册对应的方法成为HandlerMethod;
看下具体的实现过程:
1、首先根据handler和method创建出来HandlerMethod。
具体的创建过程如下所示:
这里有具体的过程:
- 1、找到beanType的类型;
- 2、封装方法和初始化参数信息;
- 3、解析方法上的ResponseStatus信息:responseStatus和responseStatusReason
- 4、封装对当前handlermethod的描述信息:
也就是经常断点显示的信息,哪个类中的哪个方法,方法参数类型是什么。
2、校验MethodMapping。也就是controller中定义了相同的@ReuqestMapping信息,引起了错误信息显示
3、然后在结合中注册路径对应的映射信息
一定要注意,这里使用的MultiValueMap,而不是普通的HashMap,这里可以有相同的key,value返回的是一个List数据信息。
这里做封装的时候,后面会用到这里的信息。
4、方法名称策略
这块我直接贴上一篇帖子:https://www.cnblogs.com/yourbatman/p/11491517.html
5、然后将最终的结果封装成MappingRegistration放入到集合中来
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directPaths, name));
映射建立完毕
3.3、映射请求处理
请求的时候一定会走DispatcherServlet中的getHandler方法
mappedHandler = getHandler(processedRequest);
然后根据请求路径找到对应的HandlerMethod
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
看看是如何找到的
// 从pathLookup中来进行查找集合,因为存在/user,但是请求方式不同的方法
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
然后选择一个最佳匹配的HandlerMethod。
然后会有两个设置:
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
关键是第二个,看一下子类中的实现:
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
super.handleMatch(info, lookupPath, request);
RequestCondition<?> condition = info.getActivePatternsCondition();
if (condition instanceof PathPatternsRequestCondition) {
extractMatchDetails((PathPatternsRequestCondition) condition, lookupPath, request);
}
else {
extractMatchDetails((PatternsRequestCondition) condition, lookupPath, request);
}
// 如果@RequestMappign中设置了produce产生的媒体类型
if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
// 那么这里就直接存入request作用域中来。然后在内容协商的时候获取得到
request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
}
}
直接到内容协商org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#getProducibleMediaTypes(javax.servlet.http.HttpServletRequest, java.lang.Class<?>, java.lang.reflect.Type)
而且默认基于请求头的内容协商器也可以砂纸Accept字段中的值,设置期望得到什么媒体类型的数据。和能够产生的媒体类型来进行匹配。
可以从上面看到,通过内容协商功能,降低匹配次数,提供系统性能。,在@RequestMapping中还是推荐写consumes和produces的值的。
然后通过response将对应的值写出去。
四、@RequestMapping注解
@RequestMapping注解中各个属性含义详解如下:
// @since 2.5 用于将Web请求映射到具有灵活方法签名的请求处理类中的方法的注释 Both Spring MVC and `Spring WebFlux` support this annotation
// @Mapping这个注解是@since 3.0 但它目前还只有这个地方使用到了~~~ 我感觉是多余的
@Target({ElementType.METHOD, ElementType.TYPE}) // 能够用到类上和方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
//给这个Mapping取一个名字。若不填写,就用HandlerMethodMappingNamingStrategy去按规则生成
String name() default "";
// 路径 数组形式 可以写多个。 一般都是按照Ant风格进行书写~
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
// 请求方法:GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
// 显然可以指定多个方法。如果不指定,表示适配所有方法类型~~
// 同时还有类似的枚举类:org.springframework.http.HttpMethod
RequestMethod[] method() default {};
// 指定request中必须包含某些参数值时,才让该方法处理
// 使用 params 元素,你可以让多个处理方法处理到同一个URL 的请求, 而这些请求的参数是不一样的
// 如:@RequestMapping(value = "/fetch", params = {"personId=10"} 和 @RequestMapping(value = "/fetch", params = // {"personId=20"}
// 这两个方法都处理请求`/fetch`,但是参数不一样,进入的方法也不一样~~~~
// 支持!myParam和myParam!=myValue这种~~~
String[] params() default {};
// 指定request中必须包含某些指定的header值,才能让该方法处理请求
// @RequestMapping(value = "/head", headers = {"content-type=text/plain"}
String[] headers() default {};
// 指定处理请求request的**提交内容类型**(Content-Type),例如application/json、text/html等
// 相当于只有指定的这些Content-Type的才处理
// @RequestMapping(value = "/cons", consumes = {"application/json", "application/XML"}
// 不指定表示处理所有~~ 取值参见枚举类:org.springframework.http.MediaType
// 它可以使用!text/plain形如这样非的表达方式
String[] consumes() default {};
// 指定返回的内容类型,返回的内容类型必须是request请求头(Accept)中所包含的类型
// 仅当request请求头中的(Accept)类型中包含该指定类型才返回;
// 参见枚举类:org.springframework.http.MediaType
// 它可以使用!text/plain形如这样非的表达方式
String[] produces() default {};
}
Spring4.3之后提供了组合注解5枚:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
consumes 与 headers 区别
- consumes 、produces 、params、 headers四个属性都是用来缩小请求范围。
- consumes只能指定 content-Type 的内容类型,但是headers可以指定所有。
所以可以认为:headers是更为强大的(所有需要指定key和value嘛),而consumes和produces是专用的,头的key是固定的,所以只需要写value值即可,使用起来也更加的方便~。
推荐两个类
- org.springframework.http.HttpHeaders,它里面有常量:几乎所有的请求头的key,以及我们可以很方便的构建一个HttpHeader,平时可以作为参考使用
- org.springframework.http.MediaType,几乎支持所有的媒体类型,无论是请求的还是响应的媒体类型
五、RequestCondition
接着之前对RequestCondition的分析。
RequestMappingInfo
: 包含各种匹配规则RequestCondition,请求到method的映射规则信息都包含在这个对象中
Condition | 说明 |
---|---|
PatternsRequestCondition | url匹配规则 |
RequestMethodsRequestCondition | http方法匹配规则,例如GET,POST等 |
ParamsRequestCondition | 参数匹配规则 |
HeadersRequestCondition | http header匹配规则 |
ConsumesRequestCondition | 请求Content-Type头部的媒体类型匹配规则 |
ProducesRequestCondition | 请求Accept头部媒体类型匹配规则 |
RequestCondition | 自定义condition |
RequestMappingHandlerMapping
: 继承RequestMappingInfoHandlerMapping
,处理方法的@ReqeustMapping
注解,将其与method handler与@ReqeustMapping
注解构建的RequestMappingInfo
关联
看一下创建过程:
看下在类上和方法上的创建过程
方法都是空实现
然后在看下请求映射的时候的判断阶段
看下具体的匹配规则
看下具体的匹配规则:
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
if (methods == null) {
return null;
}
ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
if (params == null) {
return null;
}
HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
if (headers == null) {
return null;
}
ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
if (consumes == null) {
return null;
}
ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
if (produces == null) {
return null;
}
PathPatternsRequestCondition pathPatterns = null;
if (this.pathPatternsCondition != null) {
pathPatterns = this.pathPatternsCondition.getMatchingCondition(request);
if (pathPatterns == null) {
return null;
}
}
PatternsRequestCondition patterns = null;
if (this.patternsCondition != null) {
patterns = this.patternsCondition.getMatchingCondition(request);
if (patterns == null) {
return null;
}
}
// 看下这里的匹配过程即可
RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
if (custom == null) {
return null;
}
// 创建一个新的RequestMappingInfo
return new RequestMappingInfo(
this.name, pathPatterns, patterns, methods, params, headers, consumes, produces, custom);
}
看下具体的过程
public RequestConditionHolder getMatchingCondition(HttpServletRequest request) {
// 如果是空的,那么返回当前RequestConditionHolder
if (this.condition == null) {
return this;
}
// 获取得到匹配的结果,所以我们就可以来进行匹配
RequestCondition<?> match = (RequestCondition<?>) this.condition.getMatchingCondition(request);
return (match != null ? new RequestConditionHolder(match) : null);
}
自定义RequestCondition实现版本控制
首先看下RequestCondition定义:
public interface RequestCondition<T> {
/**
* 同另一个condition组合,例如,方法和类都配置了@RequestMapping的url,可以组合
*/
T combine(T other);
/**
* 检查request是否匹配,可能会返回新建的对象,例如,如果规则配置了多个模糊规则,可能当前请求
* 只满足其中几个,那么只会返回这几个条件构建的Condition
*/
@Nullable
T getMatchingCondition(HttpServletRequest request);
/**
* 比较,请求同时满足多个Condition时,可以区分优先使用哪一个
*/
int compareTo(T other, HttpServletRequest request);
}
需求
如下所示,根据不同的版本,访问不同的接口。
@RestController
@RequestMapping("/api/{version}")
public class Controller {
@GetMapping("/user/{id}")
// 第二个版本
public Result<User> getUser(@PathVariable("id") String id) {
return new Result<>("0", "get user V2 :" + id, new User("user2_" + id, 20));
}
@GetMapping("/user/{id}")
// 第三个版本
public Result<User> getUserV4(@PathVariable("id") String id) {
return new Result<>("0", "get user V4 :" + id, new User("user4_" + id, 20));
}
@GetMapping("/cat/{id}")
public Result<User> getCatV1(@PathVariable("id") String id) {
return new Result<>("0", "get cat V1 :" + id, new User("cat1_" + id, 20));
}
@GetMapping("/dog/{id}")
public Result<User> getDogV1(@PathVariable("id") String id) {
return new Result<>("0", "get dog V3 :" + id, new User("dog1_" + id, 20));
}
}
实现
同@RequestMapping一样,我们同样定义一个自定义注解,来保存接口方法的规则信息:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiVersion {
// 定义接口的版本号
int value() default 1;
}
自定义一个新的RequestCondition:
public class ApiVersionRequestCondition implements RequestCondition<ApiVersionRequestCondition> {
// 用于匹配request中的版本号 v1 v2
private static final Pattern VERSION_PATTERN = Pattern.compile("/v(\\d+).*");
// 保存当前的版本号
private int version;
// 保存所有接口的最大版本号
private static int maxVersion = 1;
public ApiVersionRequestCondition(int version) {
this.version = version;
}
// 组合,看看是怎么组合。使用它们的,还是我们自己的?
// 又该怎么来进行组合
@Override
public ApiVersionRequestCondition combine(ApiVersionRequestCondition other) {
// 上文的getMappingForMethod方法中是使用 类的Condition.combine(方法的condition)的结果
// 确定一个方法的condition,所以偷懒的写法,直接返回参数的版本,可以保证方法优先,可以优化
// 在condition中增加一个来源于类或者方法的标识,以此判断,优先整合方法的condition
return new ApiVersionRequestCondition(other.version);
}
// 返回匹配条件,也就是使用哪个RequestCondition
@Override
public ApiVersionRequestCondition getMatchingCondition(HttpServletRequest request) {
// 正则匹配请求的uri,看是否有版本号 v1
Matcher matcher = VERSION_PATTERN.matcher(request.getRequestURI());
if (matcher.find()) {
String versionNo = matcher.group(1);
int version = Integer.valueOf(versionNo);
// 超过当前最大版本号或者低于最低的版本号均返回不匹配
if (version <= maxVersion && version >= this.version) {
return this;
}
}
return null;
}
// 如果找到了多个,那么需要按照优先级来进行匹配
@Override
public int compareTo(ApiVersionRequestCondition other, HttpServletRequest request) {
// 以版本号大小判定优先级,越高越优先
return other.version - this.version;
}
public int getVersion() {
return version;
}
public static void setMaxVersion(int maxVersion) {
ApiVersionRequestCondition.maxVersion = maxVersion;
}
}
因为默认的RequestMappingHandlerMapping
实现只有一个空的获取自定义RequestCondition的实现,所以需要继承实现:
public class ApiHandlerMapping extends RequestMappingHandlerMapping {
private int latestVersion = 1;
// 设置类上的匹配条件
@Override
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
// 判断是否有@ApiVersion注解,构建基于@ApiVersion的RequestCondition
ApiVersionRequestCondition condition = buildFrom(AnnotationUtils.findAnnotation(handlerType, ApiVersion.class));
// 保存最大版本号
if (condition != null && condition.getVersion() > latestVersion) {
ApiVersionRequestCondition.setMaxVersion(condition.getVersion());
}
return condition;
}
// 设置方法上的匹配条件
@Override
protected RequestCondition<?> getCustomMethodCondition(Method method) {
// 判断是否有@ApiVersion注解,构建基于@ApiVersion的RequestCondition
ApiVersionRequestCondition condition = buildFrom(AnnotationUtils.findAnnotation(method, ApiVersion.class));
// 保存最大版本号
if (condition != null && condition.getVersion() > latestVersion) {
ApiVersionRequestCondition.setMaxVersion(condition.getVersion());
}
return condition;
}
private ApiVersionRequestCondition buildFrom(ApiVersion apiVersion) {
return apiVersion == null ? null : new ApiVersionRequestCondition(apiVersion.value());
}
}
在SpringBoot项目中增加Config,注入自定义的ApiHandlerMapping
:
@Configuration
public class Config extends WebMvcConfigurationSupport {
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
ApiHandlerMapping handlerMapping = new ApiHandlerMapping();
handlerMapping.setOrder(0);
handlerMapping.setInterceptors(getInterceptors());
return handlerMapping;
}
}
自定义Contoller测试:
@RestController
@ApiVersion
// 在url中增加一个占位符,用于匹配未知的版本 v1 v2...
@RequestMapping("/api/{version}")
public class Controller {
@GetMapping("/user/{id}")
@ApiVersion(2)
public Result<User> getUser(@PathVariable("id") String id) {
return new Result<>("0", "get user V2 :" + id, new User("user2_" + id, 20));
}
@GetMapping("/user/{id}")
@ApiVersion(4)
public Result<User> getUserV4(@PathVariable("id") String id) {
return new Result<>("0", "get user V4 :" + id, new User("user4_" + id, 20));
}
@GetMapping("/cat/{id}")
public Result<User> getCatV1(@PathVariable("id") String id) {
return new Result<>("0", "get cat V1 :" + id, new User("cat1_" + id, 20));
}
@GetMapping("/dog/{id}")
public Result<User> getDogV1(@PathVariable("id") String id) {
return new Result<>("0", "get dog V3 :" + id, new User("dog1_" + id, 20));
}
}
看下最终的Result定义:
// Result定义
public class Result<T> {
private String code;
private String msg;
private T data;
public Result() {
}
public Result(String code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}