我们都知道Spring mvc基于请求信息, 根据handlerMapping查找对应mapping 及 handler,使用适配器对handler进行处理,返回对应的视图或响应参数,那么我们从源码的角度看下这句话是什么意思
本文重点:
- Spring 如何基于注解注册handlerMapping
- Spring 如何基于请求信息找到的handlerMapping及handler 是什么
- Spring 下的Restful 一定好吗
- Spring 如何进行参数转换,使得Controller层可以直接获取相应参数
- @DateTimeFormat 和 JsonFormat 原理 和 区别
- 如何添加自定义的路由规则
1.路由注册
1.handlerMapping
可以看到handlerMapping有很多实现类,简单介绍几种
RequestMappingHandlerMapping : 基于@Controller ,@RequestMapping 注解 ,我们主要和这个类打交道
BeanNameUrlHandlerMapping :基于beanName 是不是以 /开始
SimpleUrlHandlerMapping :需要自行注册 自定义 SimpleUrlHandlerMapping
ControllerEndpointHandlerMapping :actuator
1.RequestMappingHandlerMapping
先看注解 @RequestMapping
public @interface RequestMapping {
//这三个一致 就是url
String name() default "";
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
//POST,GET,PUT、、、、
RequestMethod[] method() default {};
//包含特定param 如paramA=asd
String[] params() default {};
//包含特定请求头的值 如 headA = asd
String[] headers() default {};
//请求类型 如application/json
String[] consumes() default {};
//响应类型 如application/json
String[] produces() default {};
}
我们日常使用过的 无非就是 url,method, 其他是匹配相关的其他参数
RequestMappingHandlerMapping间接实现了 InitializingBean ,我们看他的afterPropertiesSet方法
public void afterPropertiesSet() {
...
//AbstractHandlerMethodMapping#afterPropertiesSet
//其内又调用this.initHandlerMethods 开始进行初始化
super.afterPropertiesSet();
}
AbstractHandlerMethodMapping#initHandlerMethods
protected void initHandlerMethods() {
//获取容器中所有的beanName 循环判断
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
//在此处获取bean 的类类型beanType 调用 isHandler(beanType) 判断是不是 有@Controller 或者 @RequestMapping 注解 如果有,调用 detectHandlerMethods方法
processCandidateBean(beanName);
}
}
...
}
detectHandlerMethods
protected void detectHandlerMethods(Object handler) {
//handler是beanName
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
//这里是获取当前bean类型 在方法内部 获取其所有接口及自己 的所有方法, 自己的私有方法也是可以的,然后遍历调用下方的getMappingForMethod ,将方法与返回结果绑定起来 ,保存为一个map
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
//根据method返回一个特定对象
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
....
}
});
....
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
//内部调用 AbstractHandlerMethodMapping.MappingRegistry#register
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
RequestMappingHandlerMapping#getMappingForMethod
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
//可以看到最终返回的是RequestMappingInfo对象,那么上述维护的map就是method 与 RequestMappingInfo 的映射关系
//createRequestMappingInfo接收AnnotatedElement参数 即method 和class都可以,这一步获取方法上的RequestMapping注解,包装成RequestMappingInfo
如果你点开这个方法内部看 发现其还根据element类型 调用了 RequestMappingHandlerMapping.getCustomMethodCondition /getCustomTypeCondition 返回RequestCondition对象,默认为null,这意味着我们可以重写这两个方法
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
//这里就是获取根据 方法所在类型 获取类上的 RequestMapping 包装成 RequestMappingInfo
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
//合二为一 这就是我们常用的 类上的注解 @RequestMapping("/A"),方法上的 @RequestMapping("/B"),那么真实的匹配路径就是/A/B
info = typeInfo.combine(info);
}
....
}
return info;
}
RequestMappingInfo
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
public RequestMappingInfo(@Nullable String name, @Nullable PatternsRequestCondition patterns,
@Nullable RequestMethodsRequestCondition methods, @Nullable ParamsRequestCondition params,
@Nullable HeadersRequestCondition headers, @Nullable ConsumesRequestCondition consumes,
@Nullable ProducesRequestCondition produces, @Nullable RequestCondition<?> custom) {
this.name = (StringUtils.hasText(name) ? name : null);
this.patternsCondition = (patterns != null ? patterns : EMPTY_PATTERNS);
this.methodsCondition = (methods != null ? methods : EMPTY_REQUEST_METHODS);
this.paramsCondition = (params != null ? params : EMPTY_PARAMS);
this.headersCondition = (headers != null ? headers : EMPTY_HEADERS);
this.consumesCondition = (consumes != null ? consumes : EMPTY_CONSUMES);
this.producesCondition = (produces != null ? produces : EMPTY_PRODUCES);
//这就是上文所说的 RequestMappingHandlerMapping.getCustomMethodCondition /getCustomTypeCondition 返回RequestCondition对象 ,而其他属性 就基本和 @RequestMapping一一对应了。而@RequestMapping每个属性都可以作为路由匹配的依据,这也就意味着,如果RequestMapping注解中预留的属性不够业务开发使用,我们可以通过重写getCustomMethodCondition /getCustomTypeCondition方法自定义 RequestCondition
this.customConditionHolder = (custom != null ? new RequestConditionHolder(custom) : EMPTY_CUSTOM);
this.hashCode = calculateHashCode(
this.patternsCondition, this.methodsCondition, this.paramsCondition, this.headersCondition,
this.consumesCondition, this.producesCondition, this.customConditionHolder);
}
//可以看到重写了 hashcode 和 equals方法,这往往意味着我们需要去重,作比较
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof RequestMappingInfo)) {
return false;
}
RequestMappingInfo otherInfo = (RequestMappingInfo) other;
return (this.patternsCondition.equals(otherInfo.patternsCondition) &&
this.methodsCondition.equals(otherInfo.methodsCondition) &&
this.paramsCondition.equals(otherInfo.paramsCondition) &&
this.headersCondition.equals(otherInfo.headersCondition) &&
this.consumesCondition.equals(otherInfo.consumesCondition) &&
this.producesCondition.equals(otherInfo.producesCondition) &&
this.customConditionHolder.equals(otherInfo.customConditionHolder));
}
}
AbstractHandlerMethodMapping.MappingRegistry
class MappingRegistry {
//通过下面的代码示例 我们知道 T 是RequestMappingInfo对象 ,HandlerMethod 是@RequestMapping所在的method及bean
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
}
AbstractHandlerMethodMapping.MappingRegistry#register
//mapping就是RequestMappingInfo handler是beanName
public void register(T mapping, Object handler, Method method) {
...
this.readWriteLock.writeLock().lock();
try {
//这里就是new 了一个 HandlerMethod对象,根据handler是否为String 注入beanFactory, HandlerMethod对象 就是bean加指定的method
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
//这里进行重复路由校验 校验的方式就是根据 mapping到 mappingLookup查找,如果和handlerMethod不一致,就抛出Ambiguous mapping异常,这往往是因为多个方法上的 @RequestMapping注解完全一致导致的 ,有什么方法@RequestMapping 完全一致 也不抛出异常呢?
validateMethodMapping(handlerMethod, mapping);
//保存RequestMappingInfo 与 HandlerMethod 的关系
this.mappingLookup.put(mapping, handlerMethod);
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
//添加路径 与mapping 的映射,MultiValueMap,这里可以看出 一个路径可以缓存多个mapping对象
this.urlLookup.add(url, mapping);
}
...
@CrossOrigin 跨域相关
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
HandlerMethod构造方法
public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
this.bean = beanName;
this.beanFactory = beanFactory;
Class<?> beanType = beanFactory.getType(beanName);
if (beanType == null) {
throw new IllegalStateException("Cannot resolve bean type for bean with name '" + beanName + "'");
}
this.beanType = ClassUtils.getUserClass(beanType);
this.method = method;
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
//之前的文章有提到过 Spring 基于 MethodParameter 进行方法的参数注入, MethodParameter类似BeanDefinition一样的概念,是 方法参数的元信息,这里init的主要是创建MethodParameter(HandlerMethodParameter),标注参数index,嵌套层级。MethodParameter的get方法大多都是调用是才进行初始化获取属性。
this.parameters = initMethodParameters();
evaluateResponseStatus();
this.description = initDescription(this.beanType, this.method);
}
MethodParameter
public class MethodParameter {
private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
private final Executable executable;
private final int parameterIndex;
private volatile Parameter parameter;
private int nestingLevel;
private volatile Class<?> parameterType;
private volatile Type genericParameterType;
private volatile Annotation[] parameterAnnotations;
private volatile ParameterNameDiscoverer parameterNameDiscoverer;
private volatile String parameterName;
private volatile MethodParameter nestedMethodParameter;
....
}
至此 我们看到 RequestMappingHandlerMapping在初始化阶段,获取容器中所有的beanName, 将方法上的RequestMapping和类上的RequestMapping注解整合,包装为RequestMappingInfo对象,同时将method和bean包装为HandlerMethod对象,RequestMappingInfo和
HandlerMethod的绑定关系会维护在MappingRegistry 的mappingLookup map中,除此之外,还会将url和 RequestMappingInfo的关系维护在
MappingRegistry urlLookup中,这两个缓存将是后续匹配路由的关键。
2.DispatcherServlet
在日常开发中,如果需要基于请求前后进行拦截,通常的做法是过滤器 或者拦截器 , 它们的一个区别是过滤器作用于进入DispatcherServlet前,拦截器作用 DispatcherServlet基于handlerAdapter调用目标方法前
我们声明的或者自带的Filter被封装为一个ApplicationFilterConfig对象,多个过滤器构成数组 + Servlet构成了 ApplicationFilterChain 对象
我们熟悉的chain.doFilter(request, response);
org.apache.catalina.core.ApplicationFilterChain#doFilter
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (Globals.IS_SECURITY_ENABLED) {
...
} else {
this.internalDoFilter(request, response);
}
}
internalDoFilter
private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (this.pos < this.n) {
ApplicationFilterConfig filterConfig = this.filters[this.pos++];
try {
Filter filter = filterConfig.getFilter();
if (request.isAsyncSupported() && "false".equalsIgnoreCase(filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
}
...
} else {
//按顺序执行过滤器链
filter.doFilter(request, response, this);
}
} catch (ServletException | RuntimeException | IOException var15) {
....
}
} else {
try {
....
} else {
//过滤器执行完毕 进入DispatcherServlet (父类FrameworkServlet)
this.servlet.service(request, response);
}
}
...
}
}
FrameworkServlet#processRequest
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
...
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//request, response被包装进ServletRequestAttributes
ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
...
//我们常用的 ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest(); 就是在这里放入到线程变量中
this.initContextHolders(request, localeContext, requestAttributes);
try {
//正式进入DispatcherServlet 表演时间 方法内部调用 doDispatch方法
this.doService(request, response);
} catch (IOException | ServletException var16) {
..
} finally {
//清空线程变量里保存的请求信息
this.resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
...
}
}
3.路由匹配
DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//如果是multipart(multipart/form-data)请求 即上传文件 解析成MultipartHttpServletRequest对象,从Content-Disposition(类似Content-Disposition: attachment; filename="filename.xls")请求头中解析出文件,封装进MultiValueMap<String, MultipartFile> multipartFiles中
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
//根据请求信息返回handler,这里的handler内部是包含拦截器的 获取handlerMapping 根据handlerMapping 查找handlerMethod在方法内部
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//选择合适的HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//拦截器前置处理 注意这里的逻辑是 某个拦截器不放行时,已放行过的拦截器会立即逆序执行afterCompletion方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
applyDefaultViewName(processedRequest, mv);
//拦截器后置处理
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
....
}
getHandler
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
//基于内部维护的handlerMappings进行处理,处理逻辑是只要有一个符合就返回 我们只需要关心 RequestMappingHandlerMapping
for (HandlerMapping mapping : this.handlerMappings) {
//可以看到这里返回的是HandlerExecutionChain对象
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
AbstractHandlerMapping#getHandler
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//内部调用 AbstractHandlerMethodMapping#lookupHandlerMethod 返回 HandlerMethod
Object handler = getHandlerInternal(request);
...
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
..
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
//跨域相关
}
return executionChain;
}
AbstractHandlerMethodMapping#lookupHandlerMethod
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
//Match就是 RequestMappingInfo + HandlerMethod
List<Match> matches = new ArrayList<>();
// 先根据url路径查找 去urlLookup 中查找 RequestMappingInfo
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
//如过根据url查找不到 那么就根据其他条件遍历判断 上文介绍的 RequestMappingInfo每个condition都是判断条件,也就是需要遍历维护的所有RequestMappingInfo对象,遍历其内部的condition看是否满足条件。
匹配不到基本上是使用了Restful形式的调用,如用@PutMapping,@PostMapping来标注方法,而又不填充url值,写法简洁了,但毫无疑问,这会带来不必要的开销
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
//查找到多个对象 只有当我们重写了getCustomMethodCondition /getCustomTypeCondition 方法才会进入这个if
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
// 按顺序调用RequestCondition的compareTo方法执行,==0比较不出来就 继续下一个RequestCondition就行比较 ,最后是自定义的
matches.sort(comparator);
bestMatch = matches.get(0);
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
//重写getCustomMethodCondition /getCustomTypeCondition 方法返回了RequestCondition对象,这必然是我们自行实现的一个对象,对象需要实现compareTo方法
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
...
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
至此 我们根据请求信息找到了HandlerMethod ,也就是对应controller中的method ,接下来就是结合拦截器了
AbstractHandlerMapping#getHandlerExecutionChain
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
//一般我们自定义的拦截器属于MappedInterceptor 根据url判断是否需要调用
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
//这里关心一个 ConversionServiceExposingInterceptor :pre阶段将 ConversionService放入到 Request维护的map中
chain.addInterceptor(interceptor);
}
}
return chain;
}
getHandlerAdapter
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
//如果命中的RequestMappingHandlerMapping 那这里就是返回 RequestMappingHandlerAdapter了
return adapter;
}
}
}
....
}
至此我们看到了如何基于请求信息 找到controller 中的method,那么接下来 我们看看 spring 如果自动封装成我们需要的参数
同样 以 RequestMappingHandlerAdapter 为例
4.参数封装
RequestMappingHandlerAdapter#invokeHandlerMethod
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
//InitBinder 注册controller层的参数解析 :扫描handlerMethod的bean,也就是对应controller中带@InitBinder注解的方法,包装成InvocableHandlerMethod 。最终返回ServletRequestDataBinderFactory对象
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
//包装 可以填充参数处理组件等
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
//请求参数处理组件
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
//返回参数处理组件 我们主要关心 RequestResponseBodyMethodProcessor 就行
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
//开始参数处理 反射调用目标方法 返回值处理
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
这些都是参数处理组件,会从上至下判断,一个能处理就返回,可以看到 ServletModelAttributeMethodProcessor 是最后的兜底组件
ServletInvocableHandlerMethod#invokeAndHandle
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//参数处理及反射调用 内部调用 InvocableHandlerMethod#getMethodArgumentValues获取参数
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
...
try {
//处理返回值
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
}
}
InvocableHandlerMethod#getMethodArgumentValues
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//providedArgs 是 空数组[]
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
//这里是个循环 每个参数都可能会有不同的resolver进行处理
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
//有参数传入的时候 就不去请求信息中获取参数了。这也就是@InitBinder方法中能获取到WebDataBinder参数的原因,因为是手动传入的
continue;
}
//遍历上面截图的参数处理组件,看是否能支持解析该类型的参数 比如 普通类型,有 @RequestParam,@Requesbody注解等 ...,很多类型的参数是可以被多个处理器处理的,所以上面的处理器的排序是有一定讲究的,
同时会发现有同样类型的resolver出现多次,但是其中的属性值不一样,所以会有不同的处理逻辑。 任意处理器满足就返回,并将parameter 与 resolver缓存起来,下次就不用遍历了
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
//这里拿到具体的resolver了 开始进行参数处理 转换成我们需要的参数类型
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
...
throw ex;
}
}
return args;
}
下面我们以这四个例子来看看 参数处理过程
@PostMapping("/getInfoA")
public Object getInfoA(Long id, Date startTime){
return startTime;
}
@PostMapping("/getInfoB")
public Object getInfoB(@RequestParam("id") Long id,@RequestParam("startTime") Date startTime){
return startTime;
}
@PostMapping("/getInfoC")
public Object getInfoC(OrderParams orderParams){
return orderParams;
}
@PostMapping("/getInfoD")
public Object getInfoIII(@RequestBody OrderParams orderParams){
return orderParams;
}
@Data
static class OrderParams{
private Long id;
private Date startTime;
}
case 1 无注解,简单参数类型
Long id 属于无注解简单类型 进入第二个 RequestParamMethodArgumentResolver :可以处理简单类型 及 简单类型数组的参数 简单类型:8大类型,Enum ,Number,CharSequence,Date等 (第一个主要RequestParamMethodArgumentResolver主要处理 @RequestParam注解)
父类是多个处理器的共同父类
AbstractNamedValueMethodArgumentResolver
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//尝试获取相关注解中的配置@RequestParam @PathVariable @RequestHeader等,由子类实现方法
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
//没有相关注解 就用参数名称 这里是 "id"
MethodParameter nestedParameter = parameter.nestedIfOptional();
...
//这里同样由子类实现 RequestParamMethodArgumentResolver 在这里的逻辑是尝试先取MultipartFile,取不到就调用 request.getParameterValues(name) 方法获取参数
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
//从请求信息获取不到指定名称参数的逻辑 可被子类重写
}
if (binderFactory != null) {
//WebDataBinder内部维护了一个SimpleTypeConverter(PropertyEditorRegistry),在此处调用controller的带@InitBinder注解方法,往Map<Class<?>, PropertyEditor> customEditors中注册PropertyEditor
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
//binder进行参数转换时,调用维护的SimpleTypeConverter,而SimpleTypeConverter再调用维护的TypeConverterDelegate的convertIfNecessary方法。
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
...
}
}
//可被子类重写 唯一实现 PathVariableMethodArgumentResolver
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
TypeConverterDelegate#convertIfNecessary
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
//根据MethodParameter描述的请求类型,查看controller是不是有自定义的PropertyEditor(也就是有无@InitBinder方法)
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
ConversionFailedException conversionAttemptEx = null;
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
//无自定义PropertyEditor的就调用ConversionService进行转换
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
//ConversionService 内部维护大量GenericConverter 基于 TypeDescriptor类型描述符判断是否可以进行参数转换
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
//sourceTypeDesc一般为String ,sourceTypeDesc和typeDescriptor公同组成ConverterCacheKey,描述本次请求拿到的类型和需要的类型,ConverterCacheKey重写了hashcode和equals方法,因此可以缓存,下次可以快速找到对应的converter。这里会基于请求拿到的类型 和需要的类型,递归查找其父类 和 接口,两两组合 进行查找可以处理的Converter。案例中第一次查找到的两个Converter属于 ConditionalGenericConverter(多了一个matches条件判断方法),分别需要DateTimeFormat和NumberFormat注解。判断不通过,第二次使用String和Long的父类Number就成功匹配了。这里即使有多个满足也只会返回匹配上的第一个。
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
try {
return conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
} catch (ConversionFailedException var14) {
//有可能抛异常
conversionAttemptEx = var14;
}
}
}
Object convertedValue = newValue;
if (editor != null || requiredType != null && !ClassUtils.isAssignableValue(requiredType, newValue)) {
...
//基于PropertyEditor
convertedValue = this.doConvertValue(oldValue, convertedValue, requiredType, editor);
..尝试 按集合 枚举 解等操作
}
return convertedValue;
}
Date startTime 属于无注解简单类型 同样进入第二个 RequestParamMethodArgumentResolver 同样由于无自定义PropertyEditor的就调用ConversionService进行转换 最终匹配到Object -> Object的 Converter 这里直接调用的是Date的构造方法
case 2 带注解,简单参数类型
同case1 因为携带注解@RequestParam 且不是 Map,进入第一个 RequestParamMethodArgumentResolver
case 3 无注解pojo
进入兜底 ServletModelAttributeMethodProcessor 父类 ModelAttributeMethodProcessor
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
...ModelAttribute相关...
else {
// Create attribute instance
try {
//尝试按参数名从请求uri,parameters中获取参数,如果没有 ,根据类型调用构造方法,创建对象
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
...
}
}
if (bindingResult == null) {
//这里创建的空对象直接维护到了binder的target
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
//这里 遍历请求信息中parameters挨个 调用TypeConverterDelegate#convertIfNecessary方法进行参数转换,然后调用set方法进行赋值
bindRequestParameters(binder, webRequest);
}
//Validated 参数校验
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
...
return attribute;
}
前三个例子都是表单类型的请求,通过源码我们可以发现,可基于 PropertyEditor 或 ConversionService (GenericConverter) 扩展参数转换逻辑,我们先看 @DateTimeFormat,spring提供的一个时间格式类型conveter AnnotationParserConverter
@DateTimeFormat
前文说过 String ->Date 最终匹配到Object -> Object的 Converter 如果参数/属性 带 @DateTimeFormat,那匹配的就是 AnnotationParserConverter了 AnnotationParserConverter属于ConditionalGenericConverter,在这里必须有DateTimeFormat注解才会匹配到
private class AnnotationParserConverter implements ConditionalGenericConverter {
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
//annotationType : DateTimeFormat
return targetType.hasAnnotation(this.annotationType);
}
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
Annotation ann = targetType.getAnnotation(this.annotationType);
if (ann == null) {
throw ...
}
AnnotationConverterKey converterKey = new AnnotationConverterKey(ann, targetType.getObjectType());
GenericConverter converter = cachedParsers.get(converterKey);
if (converter == null) {
Parser<?> parser = this.annotationFormatterFactory.getParser(
converterKey.getAnnotation(), converterKey.getFieldType());
converter = new ParserConverter(this.fieldType, parser, FormattingConversionService.this);
cachedParsers.put(converterKey, converter);
}
return converter.convert(source, sourceType, targetType);
}
}
自定义Converter
这也能实现String->Date 自定义格式转换。但是灵活性就没有 @DateTimeFormat高了。优先匹配自定义的
@Component
public class DateConvert implements Converter<String, Date> {
@Override
public Date convert(String stringDate) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
return simpleDateFormat.parse(stringDate);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
自定义PropertyEditor
在controller 类中定义,作用范围为当前controller
@InitBinder(value可为空或指定的参数名称)
public void initBinder(WebDataBinder webDataBinder){
webDataBinder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd HH:mm"), false));
}
case 4 RequestBody
因为参数携带@RequestBody注解,所以进入的是 RequestResponseBodyMethodProcessor 这个处理器能同时处理请求和响应
public boolean supportsParameter(MethodParameter parameter) {
return getArgumentResolver(parameter) != null;
}
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
//和之前表单类型/uri参数 基于ConversionService(GenericConverter)处理不同 这里调用的是HttpMessageConverter
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
HttpMessageConverter
父类 AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
...
Class<?> contextClass = parameter.getContainingClass();
Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
...
HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
Object body = NO_VALUE;
EmptyBodyCheckingHttpInputMessage message;
try {
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
//同ConversionService处理一样 有一个converter能处理就行 主要判断 canRead方法
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
HttpInputMessage msgToUse =
//getAdvice返回的是RequestResponseBodyAdviceChain 内部维护了两个List集合 可分别在作用于读取数据前后,spring在这里相当于给我们留下了自行扩展的空间,我们可以自定义advice
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
//后置处理
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
catch (IOException ex) {
...
}
...
return body;
}
读取inputStream转化为参数,这里spring直接调用了jackson的ObjectMapper,所以具体的转换过程其实是jackson做完了,jackso本身也提供了一些注解给开发者使用 ,@JsonFormat @JsonIgnore @JsonIgnoreProperties... 同样,我们以Date类型举例 ,简单 看下@JsonFormat 怎么作用的
ObjectMapper#_readMapAndClose
protected Object _readMapAndClose(JsonParser p0, JavaType valueType)
throws IOException
{
try (JsonParser p = p0) {
Object result;
JsonToken t = _initForReading(p, valueType);
final DeserializationConfig cfg = getDeserializationConfig();
final DeserializationContext ctxt = createDeserializationContext(p, cfg);
if (t == JsonToken.VALUE_NULL) {
...
} else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
...
} else {
//解析对象属性 包装成 BeanDeserializerBase,解析的过程就扫描了类、属性上的 @JsonIgnoreProperties,@JsonFormat
JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
if (cfg.useRootWrapping()) {
...
} else {
//无参构造创建bean,遍历MethodProperty, MethodProperty 调用自己维护的TypeDeserializer#deserialize 方法转换参数 并set到bean 中,例子中Date类型属性最终调用了DateDeserializers ,JsonFormat注解在这里被解析成了例子中Date类型属性最终调用了DateDeserializers的属性 DateFormat(_customFormat),如果_customFormat不为空,就_customFormat.parse(str),否则调用父类的parseDate方法
result = deser.deserialize(p, ctxt);
}
ctxt.checkUnresolvedObjectId();
}
....
return result;
}
}
最后 我们参考内置实现 JsonViewRequestBodyAdvice 封装一个
自定义的增强 aes解密
@Component
@RestControllerAdvice
public class MyAnnotationAdvice extends RequestBodyAdviceAdapter {
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return (AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType) &&
methodParameter.getParameterAnnotation(MyAnnotation.class) != null);
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> selectedConverterType) throws IOException {
InputStream body = inputMessage.getBody();
HttpHeaders headers = inputMessage.getHeaders();
headers.remove(HttpHeaders.CONTENT_LENGTH);
String encrypt = StreamUtils.copyToString(body, StandardCharsets.UTF_8);
//方便演示 直接做aes解密....
String decode = URLDecoder.decode(encrypt, StandardCharsets.UTF_8.name());
InputStream byteArrayInputStream = new ByteArrayInputStream(decode.getBytes(StandardCharsets.UTF_8));
//headers.setContentType(MediaType.APPLICATION_JSON);
return new MappingJacksonInputMessage(byteArrayInputStream, inputMessage.getHeaders());
}
}
5.总结
ApplicationFilterChain 内部维护了一个ApplicationFilterConfig数组,ApplicationFilterConfig是Filter的封装,请求经过 过滤器链的循环判断后,调用维护的servlet的service方法,默认的实现是DispatcherServlet,DispatcherServlet最终调用doDispatch方法。
在doDispatch方法内部,维护了List handlerMappings 和 List handlerAdapters 集合,HandlerMapping是处理路由的,常见的有RequestMappingHandlerMapping ,BeanNameUrlHandlerMapping ,SimpleUrlHandlerMapping,
HandlerAdater主要是进行参数解析,调用目标方法,处理返回值等,常见的有RequestMappingHandlerAdapter、HandlerFunctionAdapter、HttpRequestHandlerAdapter,这里我们说下RequestMappingHandlerMapping ,RequestMappingHandlerAdapter。
RequestMappingHandlerMapping 先根据url 筛选出合适的 RequestMappingInfo ,我们的@RequestMapping 注解 最终被注册为RequestMappingInfo 对象,如果找不到合适的,会根据method,header,param等其他RequestCondition属性 ,筛选出其他合适的路由。我们还可以自定义RequestCondition,来添加自定义的匹配规则,不过这需要重写RequestMappingHandlerMapping 的getCustomMethodCondition或者getCustomTypeCondition方法。
筛选出 RequestMappingInfo 后根据 mappingLookup 找到对应的 HandlerMethod, 然后根据url 添加匹配的拦截器 和默认参数解析拦截器等,返回一个HandlerMethod + HandlerInterceptor[] interceptors 也就是 HandlerExecutionChain对象,然后顺序调用拦截器,RequestMappingHandlerAdapter 就HandlerMethod反射执行 获取请求结果 。 程序继续 降序调用拦截器postHandle, 以及过滤器链的后续逻辑代码。
HandlerMethod反射执行,调用目标方法前后,处理请求参数和响应报文,spring都留下了可供开发者扩展的的地方。比如表单类型的请求参数可自定义PropertyEditor 或 GenericConverter。json类型的请求参数可以自定义HttpMessagesConverter或 继承RequestBodyAdviceAdapter 实现解析前后方法增强。
6.自定义路由规则
案例代码
自定义注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiVersion {
double value() default 1.0;
}
重写 RequestMappingHandlerMapping
public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected RequestCondition<?> getCustomMethodCondition(Method method) {
ApiVersion annotation = AnnotationUtils.getAnnotation(method, ApiVersion.class);
//要么不写注解就是最高版本 要么就是最低版本
double value = 9999;
if(annotation != null) {
value = annotation.value();
}
return new HeaderRequestCondition(value);
}
}
实例化
@Component
public class InterceptorConfig extends WebMvcConfigurationSupport {
@Override
protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
return new CustomRequestMappingHandlerMapping();
}
}
自定义RequestCondition
public class HeaderRequestCondition implements RequestCondition<HeaderRequestCondition> {
private Double apiVersion;
public HeaderRequestCondition(Double apiVersion) {
this.apiVersion = apiVersion;
}
@Override
public HeaderRequestCondition combine(HeaderRequestCondition headerRequestCondition) {
//这里的调用逻辑是typeInfo.combine(info) 即类注解 拼接 方法注解,这里直接返回方法注解,我们的自定义注解只打在方法上
return headerRequestCondition;
}
@Override
public HeaderRequestCondition getMatchingCondition(HttpServletRequest httpServletRequest) {
//这里的调用逻辑是 this.customConditionHolder.getMatchingCondition(request);
// 也就是this(this是根据方法上的ApiVerion创建的路由信息) 比较 请求对象
String header = httpServletRequest.getHeader("apiVersion");
if(StringUtils.isBlank(header)){
//不传版本号 默认放行 方法上的注解值是 1.0 ,2.0都认为匹配上
return this;
}
Double requestApiVersion = Double.parseDouble(header);
//this版本高于请求版本 不命中
if(this.apiVersion.compareTo(requestApiVersion) > 0){
return null;
}
return this;
}
@Override
public int compareTo(HeaderRequestCondition headerRequestCondition, HttpServletRequest httpServletRequest) {
//这里的调用是 return info1.compareTo(info2, request); 版本高的优先
return headerRequestCondition.apiVersion.compareTo(this.apiVersion);
}
}
测试
@RestController
@RequestMapping("/test/controller")
public class TestController {
@PutMapping
@ApiVersion
public String testPuttingMapping1(){
return "1";
}
@PutMapping
@ApiVersion(2.0)
public String testPuttingMapping2(){
return "2";
}
}