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;
    }
}
posted @ 2022-10-04 17:35  写的代码很烂  阅读(368)  评论(0编辑  收藏  举报