Spring3.1.0实现原理分析(十三).MVC请求映射信息RequestMappingInfo详解
大家好,原本是打算先分析spring mvc的处理器映射接口的(HandlerMapping),但是发现其中的请求映射信息对象(RequestMappingInfo)特别重要,所以就决定专门写篇博客介绍请求映射信息对象,对于RequestMappingInfo有概念的读者可以先看这篇博客,其他读者可以等下篇博客,然后再回头看这篇。
RequestMappingInfo是什么样的东西呢?通常我们使用spring mvc框架时会在某个类上加个注解@Controller,用于标示这是一个request请求处理类,然后又会在类中定义的方法上增加注解@RequestMapping,标示这个方法是用来处理request请求的,当然@RequestMapping注解也能加在类上。那么用户使用@RequestMapping注解类或注解方法时可以指定哪些配置项呢,我们先看下@RequestMapping的定义。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping
{
/**
* 模式请求路径数组(最常用的一个配置项),诸如,/pets/{petId}
*/
String[] value() default {};
/**
* 请求方法枚举对象数组(支持GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE)
*/
RequestMethod[] method() default {};
/**
* 请求参数表达式数组,使用举例,
* 1.params="myParam=myValue", 必须存在参数myParam,并且值为myValue.
* 2.params="myParam" 必须存在参数myParam.
* 3.params="!myParam" 必须不存在参数myParam.
*/
String[] params() default {};
/**
* 头字段表达式数组,使用举例,
* 1.headers="myHeader=myValue" 必须存在头字段myHeader,并且值为myValue
*/
String[] headers() default {};
/**
* 请求内容费媒体类型数组,如以下配置只处理Content-Type值为application/json的请求
* 1.consumes="application/json"
*/
String[] consumes() default {};
/**
* 应答媒体类型数组,如以下配置只处理Accept值为application/json的请求
* 1.produces="application/json"
*/
String[] produces() default {};
}
一共有六项配置,可以看出这些配置项是用于限定被注解的方法对象可以处理哪些类型的request请求。当spring启动过程中创建处理器映射对象时,会寻找所有被@Controller注解的类中被@RequestMapping注解的方法对象,然后解析方法对象的@RequestMapping注解,把解析结果封装成RequestMappingInfo对象,也就是说RequestMappingInfo对象是用来装载请求处理方法的配置信息的,每个请求处理方法对象都会对应一个RequestMappingInfo对象。现在大家应该能明白RequestMappingInfo的作用了吧。
@RequestMapping注解一共有6个配置项,这6个配置项其实就是6种过滤器,限定了请求处理方法对象可处理的请求,通过这6种过滤条器把方法不能处理的请求都pass掉,RequestMappingInfo对象持有这6种过滤器,请看下面的类图。
从上图可以看出,最上面的RequestCondition<T>接口定义了三个方法,所有的过滤条件对象都要实现这三个方法,
1. 创建跟请求匹配的过滤条件对象
每种类型的过滤对象都会持有一个过滤条件列表,比如“PatternsRequestCondition”持有模式请求路径列表,“ParamsRequestCondition”持有请求参数表达式列表......。这个方法的作用是判断request请求对象是否匹配过滤对象持有的过滤条件列表中的某一元素或是所有元素。如“PatternsRequestCondition”,请求对象只要匹配其中一个模式路径就认为匹配成功,而对于“ParamsRequestCondition”,请求对象必须匹配其所有的参数表达式,才认为匹配成功。
2. 组合过滤条件对象
这个方法的作用是把两个同类过滤器的过滤条件列表组合起来。比如@RequestMapping注解同时存在于类和方法的场景,需要把过滤条件组合起来,通常就是取过滤条件的合集。
1. PatternsRequestCondition(模式请求路径过滤器)
a. 创建跟请求匹配的过滤器对象
从持有的模式请求路径列表中筛选出同request请求匹配的模式请求路径,可能会有多个,对筛选后的模式请求路径列表执行排序,最详细最具体的路径排在前面,然后使用过滤后的模式请求列表创建一个新的过滤器并返回。多个模式请求路径之间是或关系,只要有一个模式请求路径跟request请求匹配,就认为过滤对象跟request请求匹配。
b.组合两个过滤条件对象
对两个模式请求路径列表中的元素进行自然连接后,再执行简单拼接, 像这样,
Pattern 1 | Pattern 2 | Result |
---|---|---|
/hotels | null |
/hotels |
null |
/hotels | /hotels |
/hotels | /bookings | /hotels/bookings |
/hotels | bookings | /hotels/bookings |
/hotels/* | /bookings | /hotels/bookings |
/hotels/** | /bookings | /hotels/**/bookings |
c.比较过滤条件对象优先级
规则是谁的模式请求路径跟请求路径匹配度更高.
2. ParamsRequestCondition(请求参数过滤器)
a. 创建跟请求匹配的过滤条件对象
判断所有的参数表达式是否都匹配request请求对象, 是则返回本过滤对象, 否则返回null,认为过滤对象不匹配request请求。
b.组合两个过滤条件对象
把两个过滤对象的参数表达式集合累加起来, 然后创建新的请求参数过滤对象, 并返回。
c.比较过滤条件对象优先级
参数表达式个数多的那个过滤器优先级高。
3. HeadersRequestCondition(头字段过滤器)
a. 创建跟请求匹配的过滤条件对象
判断所有的头字段表达式是否都匹配请求对象, 是则返回本过滤器,否则返回null,认为过滤器不匹配request请求。
b.组合两个过滤条件对象
把两个过滤对象的头字段表达式集合累加起来, 然后创建新的过滤对象, 并返回。
c.比较过滤条件对象优先级
头字段表达式个数多的过滤器优先级高。
4.RequestMethodsRequestCondition (请求方法过滤器)
a. 创建跟请求匹配的过滤条件对象
筛选出同request请求匹配的方法名称, 然后创建新的过滤条件对象,并返回,如果返回null,则认为过滤器不匹配请求。
b.组合两个过滤条件对象
把两个过滤对象的方法名称集合累加起来, 然后创建新的过滤对象, 并返回。
c.比较过滤条件对象优先级
请求方法名称个数多的那个过滤器优先级高。
5.ProducesRequestCondition(应答媒体类型过滤器)
a. 创建跟请求匹配的过滤条件对象
筛选出同请求Content-Type匹配的媒体类型表达式列表, 然后创建新的过滤条件对象, 并返回。
b.组合两个过滤条件对象
如果传入的过滤器的媒体类型表达式列表不为空, 则优先使用,这样处理的目的是方法的匹配覆盖类的配置。
c.比较过滤条件对象优先级
同请求Content-Type匹配度高的过滤对象优先级高。
6.ConsumesRequestCondition(请求媒体类型过滤器)
a. 创建跟请求匹配的过滤条件对象
筛选出同请求Accpet匹配的媒体类型表达式列表, 然后创建新的过滤条件对象, 并返回。
b.组合两个过滤条件对象
如果传入的过滤器的媒体类型表达式列表不为空, 则优先使用,这样处理的目的是方法的匹配覆盖类的配置。
c.比较过滤条件对象优先级
同请求Accpet匹配度高的过滤对象优先级高。
最后说一下,万一以上6类过滤器不能满足你的需求该怎么办呢?别担心Spring预留了扩展方法,你可以这样处理。
1. 先编写一个自己的注解类,即增加一个自定义的过滤方式,然后把注解应用到处理类或处理方法上。
2. 然后重写RequestMappingHandlerMapping的两个方法即可。
protected RequestCondition<?> getCustomMethodCondition(Method method)
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType)
上一篇: mybatis分页效果实现代码