SpringMVC学习教程之RequestMappingHandlerAdapter
详解
前⾔
RequestMappingHandlerAdapter实现了HandlerAdapter接⼝,顾名思义,表⽰handler的adapter,这⾥的handler指的是Spring处理具体请求的某个Controller的⽅法,也就是说HandlerAdapter指的是将当前请求适配到某个Handler的处理器。RequestMappingHandlerAdapter是HandlerAdapter的⼀个具体实现,主要⽤于将某个请求适配给@RequestMapping类型的Handler处理。
如下是HandlerMapping接⼝的声明:
public interface HandlerAdapter {
// ⽤于判断当前HandlerAdapter是否能够处理当前请求
boolean supports(Object handler);
// 如果当前HandlerAdapter能够⽤于适配当前请求,那么就会处理当前请求中
/
/ 诸如参数和返回值等信息,以便能够直接委托给具体的Handler处理
ModelAndView handle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception;
// 获取当前请求的最后更改时间,主要⽤于供给浏览器判断当前请求是否修改过,
// 从⽽判断是否可以直接使⽤之前缓存的结果
long getLastModified(HttpServletRequest request, Object handler);
}
1. supports()
HandlerAdapter.supports()⽅法的主要作⽤在于判断当前的HandlerAdapter是否能够⽀持当前的handler的适配。这⾥的handler指的是某个Controller的⽅法,其是由Handler(HttpServletRequest)⽅法获取到的。从这⾥可以看出,HandlerMapping的作⽤主要是根据request请求获取能够处理当前request的handler,⽽HandlerAdapter的作⽤在于将request中的各个属性,如request param适配为handler能够处理的形式。
关于HandlerAdapter.supports()⽅法,有这个⽅法的主要原因是,HandlerMapping是可以有多种实现的,Spring会遍历这些具体的实现类,判断哪⼀个能够根据当前request产⽣⼀个handler,因⽽对于HandlerAdapter⽽⾔,其是不知道当前获取到的handler具体是什么形式的,不同的HandlerMapping产⽣的handler形式是不⼀样的,⽐如RequestMappingHandlerMapping 产⽣的handler则是封装在HandlerMethod对象中的,因⽽这⾥HandlerAdapter需要⼀个⽅法能够快速过滤掉当前产⽣的handler是否为其能够进⾏适配的,这个⽅法就是HandlerAdapter.supports()⽅法。如下是该⽅法的实现:
// AbstractHandlerMethodAdapter
@Override
public final boolean supports(Object handler) {
// 判断当前handler是否为HandlerMethod类型,并且判断supportsInternal()⽅法返回值是否为true,
// 这⾥supportsInternal()⽅法是提供给⼦类实现的⼀个⽅法,对于RequestMappingHandlerAdapter
// ⽽⾔,其返回值始终是true,因为其只需要处理的handler是HandlerMethod类型的即可
return (handler instanceof HandlerMethod
&& supportsInternal((HandlerMethod) handler));
}
// RequestMappingHandlerAdapter
@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {
// 这⾥RequestMappingHandlerAdapter只是对supportsInternal()返回true,因为其只需要
// 处理的handler类型是HandlerMethod类型即可
return true;
}
2. handle()
在supports()⽅法判断了所处理的handler是HandlerMethod类型之后,RequestMappingHandlerAdapter就会调⽤handle()⽅法处理当前请求。该⽅法的主要作⽤在于有五点:
获取当前Spring容器中在⽅法上配置的标注了@ModelAttribute但是没标注@RequestMapping注解的⽅法,在真正调⽤具体的handler之前会将这些⽅法依次进⾏调⽤;
获取当前Spring容器中标注了@InitBinder注解的⽅法,调⽤这些⽅法以对⼀些⽤户⾃定义的参数进⾏转换并且绑定;
根据当前handler的⽅法参数标注的注解类型,如@RequestParam,@ModelAttribute等,获取其对应的
ArgumentResolver,以将request中的参数转换为当前⽅法中对应注解的类型;
配合转换⽽来的参数,通过反射调⽤具体的handler⽅法;
通过ReturnValueHandler对返回值进⾏适配,⽐如ModelAndView类型的返回值就由
ModelAndViewMethodReturnValueHandler处理,最终将所有的处理结果都统⼀封装为⼀个ModelAndView类型的返回值,这也是RequestMappingHandlerAdapter.handle()⽅法的返回值类型。
这⾥我们⾸先看看RequestMappingHandlerAdapter.handle()⽅法的实现源码:
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// 判断当前是否需要⽀持在同⼀个session中只能线性地处理请求
if (this.synchronizeOnSession) {
// 获取当前请求的session对象
HttpSession session = Session(false);
if (session != null) {
// 为当前session⽣成⼀个唯⼀的可以⽤于锁定的key
Object mutex = SessionMutex(session);
synchronized (mutex) {
// 对HandlerMethod进⾏参数等的适配处理,并调⽤⽬标handler
mav = invokeHandlerMethod(request, response, handlerMethod);
}
} else {
// 如果当前不存在session,则直接对HandlerMethod进⾏适配
mav = invokeHandlerMethod(request, response, handlerMethod);
}
} else {
// 如果当前不需要对session进⾏同步处理,则直接对HandlerMethod进⾏适配
mav = invokeHandlerMethod(request, response, handlerMethod);
}
// 判断当前请求头中是否包含Cache-Control请求头,如果不包含,则对当前response进⾏处理,
// 为其设置过期时间
if (!ainsHeader(HEADER_CACHE_CONTROL)) {
// 如果当前SessionAttribute中存在配置的attributes,则为其设置过期时间。
// 这⾥SessionAttribute主要是通过@SessionAttribute注解⽣成的
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
} else {
// 如果当前不存在SessionAttributes,则判断当前是否存在Cache-Control设置,
// 如果存在,则按照该设置进⾏response处理,如果不存在,则设置response中的
/
/ Cache的过期时间为-1,即⽴即失效
prepareResponse(response);
}
}
return mav;
}
上述代码主要做了两部分处理:①判断当前是否对session进⾏同步处理,如果需要,则对其调⽤进⾏加锁,不需要则直接调⽤;②判断请求头中是否包含Cache-Control请求头,如果不包含,则设置其Cache⽴即失效。可以看到,对于HandlerMethod的具体处理是在invokeHandlerMethod()⽅法中进⾏的,如下是该⽅法的具体实现:
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
// 获取容器中全局配置的InitBinder和当前HandlerMethod所对应的Controller中
// 配置的InitBinder,⽤于进⾏参数的绑定
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
// 获取容器中全局配置的ModelAttribute和当前当前HandlerMethod所对应的Controller
// 中配置的ModelAttribute,这些配置的⽅法将会在⽬标⽅法调⽤之前进⾏调⽤
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
// 将handlerMethod封装为⼀个ServletInvocableHandlerMethod对象,
// 该对象⽤于对当前request的整体调⽤流程进⾏了封装
ServletInvocableHandlerMethod invocableMethod =
createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
// 设置当前容器中配置的所有ArgumentResolver
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (urnValueHandlers != null) {
// 设置当前容器中配置的所有ReturnValueHandler
invocableMethod.urnValueHandlers);
}
// 将前⾯创建的WebDataBinderFactory设置到ServletInvocableHandlerMethod中
invocableMethod.setDataBinderFactory(binderFactory);
// 设置ParameterNameDiscoverer,该对象将按照⼀定的规则获取当前参数的名称
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.InputFlashMap(request));
// 这⾥initModel()⽅法主要作⽤是调⽤前⾯获取到的@ModelAttribute标注的⽅法,
// 从⽽达到@ModelAttribute标注的⽅法能够在⽬标Handler调⽤之前调⽤的⽬的
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
// 获取当前的AsyncWebRequest,这⾥AsyncWebRequest的主要作⽤是⽤于判断⽬标
// handler的返回值是否为WebAsyncTask或DefferredResult,如果是这两种中的⼀种,
/
/ 则说明当前请求的处理应该是异步的。所谓的异步,指的是当前请求会将Controller中
// 封装的业务逻辑放到⼀个线程池中进⾏调⽤,待该调⽤有返回结果之后再返回到response中。
// 这种处理的优点在于⽤于请求分发的线程能够解放出来,从⽽处理更多的请求,只有待⽬标任务
// 完成之后才会回来将该异步任务的结果返回。
AsyncWebRequest asyncWebRequest = WebAsyncUtils
.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
// 封装异步任务的线程池,request和interceptors到WebAsyncManager中
WebAsyncManager asyncManager = AsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
// 这⾥就是⽤于判断当前请求是否有异步任务结果的,如果存在,则对异步任务结果进⾏封装
if (asyncManager.hasConcurrentResult()) {
Object result = ConcurrentResult();
mavContainer = (ModelAndViewContainer)
asyncManager.clearConcurrentResult();
if (logger.isDebugEnabled()) {
logger.debug("Found concurrent result value [" + result + "]");
}
/
/ 封装异步任务的处理结果,虽然封装的是⼀个HandlerMethod,但只是Spring简单的封装
// 的⼀个Callable对象,该对象中直接将调⽤结果返回了。这样封装的⽬的在于能够统⼀的
// 进⾏右⾯的ServletInvocableHandlerMethod.invokeAndHandle()⽅法的调⽤
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
// 对请求参数进⾏处理,调⽤⽬标HandlerMethod,并且将返回值封装为⼀个ModelAndView对象
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
// 对封装的ModelAndView进⾏处理,主要是判断当前请求是否进⾏了重定向,如果进⾏了重定向,
/
/ 还会判断是否需要将FlashAttributes封装到新的请求中
return getModelAndView(mavContainer, modelFactory, webRequest);
} finally {
// 调⽤request destruction callbacks和对SessionAttributes进⾏处理
}
}
上述代码是RequestMappingHandlerAdapter处理请求的主要流程,其主要包含四个部分:①获取当前容器中使⽤@InitBinder 注解注册的属性转换器;②获取当前容器中使⽤@ModelAttribute标注但没有使⽤@RequestMapping标注的⽅法,并且在调⽤⽬标⽅法之前调⽤这些⽅法;③判断⽬标handler返回值是否使⽤了WebAsyncTask或DefferredResult封装,如果封装了,则按照异步任务的⽅式进⾏执⾏;④处理请求参数,调⽤⽬标⽅法和处理返回值。这⾥我们⾸先看RequestMappingHandlerAdapter是如何处理标注@InitBinder的⽅法的,如下是getDataBinderFactory()⽅法的源码:
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod)
throws Exception {
// 判断当前缓存中是否缓存了当前bean所需要装配的InitBinder⽅法,如果存在,则直接从缓存中取,
// 如果不存在,则在当前bean中进⾏扫描获取
Class<?> handlerType = BeanType();
Set<Method> methods = (handlerType);
springmvc面试题常用注解if (methods == null) {
// 在当前bean中查所有标注了@InitBinder注解的⽅法,这⾥INIT_BINDER_METHODS就是⼀个
// 选择器,表⽰只获取使⽤@InitBinder标注的⽅法
methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
this.initBinderCache.put(handlerType, methods);
}
// 这⾥initBinderAdviceCache是在RequestMappingHandlerAdapter初始化时同步初始化的,
// 其内包含的⽅法有如下两个特点:①当前⽅法所在类使⽤@ControllerAdvice进⾏标注了;
// ②当前⽅法使⽤@InitBinder进⾏了标注。也就是说其内保存的⽅法可以理解为是全局类型
// 的参数绑定⽅法
List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
this.initBinderAdviceCache.forEach((clazz, methodSet) -> {
// 这⾥判断的是当前配置的全局类型的InitBinder是否能够应⽤于当前bean,
// 判断的⽅式主要在@ControllerAdvice注解中进⾏了声明,包括通过包名,类所在的包,
// 接⼝或者注解的形式限定的范围
if (clazz.isApplicableToBeanType(handlerType)) {
Object bean = solveBean();
for (Method method : methodSet) {
initBinderMethods.add(createInitBinderMethod(bean, method));
}
}
});
// 这⾥是将当前HandlerMethod所在bean中的InitBinder添加到需要执⾏的initBinderMethods中。
// 这⾥从添加的顺序可以看出,全局类型的InitBinder会在当前bean中的InitBinder之前执⾏
for (Method method : methods) {
Object bean = Bean();
initBinderMethods.add(createInitBinderMethod(bean, method));
}
// 将需要执⾏的InitBinder封装到InitBinderDataBinderFactory中
return createDataBinderFactory(initBinderMethods);
}
这⾥获取InitBinder的⽅式主要有两种,⼀种是获取全局配置的InitBinder,全局类型的InitBinder需要声明的类上使⽤
@ControllerAdvice进⾏标注,并且声明⽅法上使⽤@InitBinder进⾏标注;另⼀种则是获取当前handler所在类中的使⽤
@InitBinder注解标注的⽅法。这两种InitBinder都会执⾏,只不过全局类型的InitBinder会先于局部类型的InitBinder执⾏。关于使⽤@InitBinder标注的⽅法的执⾏时间点,需要说明的是,因为其与参数绑定有关,因⽽其只会在参数绑定时才会执⾏。
这⾥我们继续看RequestMappingHandlerAdapter是如何获取@ModelAttribute标注的⽅法并且执⾏的,如下是getModelFactory()⽅法的源码:
private ModelFactory getModelFactory(HandlerMethod handlerMethod,
WebDataBinderFactory binderFactory) {
// 这⾥SessionAttributeHandler的作⽤是声明⼏个属性,使其能够在多个请求之间共享,
// 并且其能够保证当前request返回的model中始终保有这些属性
SessionAttributesHandler sessionAttrHandler =
getSessionAttributesHandler(handlerMethod);
// 判断缓存中是否保存有当前handler执⾏之前所需要执⾏的标注了@ModelAttribute的⽅法
Class<?> handlerType = BeanType();
Set<Method> methods = (handlerType);
if (methods == null) {
// 如果缓存中没有相关属性,那么就在当前bean中查所有使⽤@ModelAttribute标注,但是
/
/ 没有使⽤@RequestMapping标注的⽅法,并将这些⽅法缓存起来
methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
}
// 获取全局的使⽤@ModelAttribute标注,但是没有使⽤@RequestMapping标注的⽅法,
// 这⾥全局类型的⽅法的声明⽅式需要注意的是,其所在的bean必须使⽤@ControllerAdvice进⾏标注
List<InvocableHandlerMethod> attrMethods = new ArrayList<>();
// 判断@ControllerAdvice中指定的作⽤的bean范围与当前bean是否匹配,匹配了才会对其应⽤
if (clazz.isApplicableToBeanType(handlerType)) {
Object bean = solveBean();
for (Method method : methodSet) {
attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
}
}
});
// 将当前⽅法中使⽤@ModelAttribute标注的⽅法添加到需要执⾏的attrMethods中。从这⾥的添加顺序
// 可以看出,全局类型的⽅法将会先于局部类型的⽅法执⾏
for (Method method : methods) {
Object bean = Bean();
attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
}
/
/ 将需要执⾏的⽅法等数据封装为ModelFactory对象
return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
}
上述getModelFactory()⽅法主要⼯作还是获取当前需要先于⽬标handler执⾏的⽅法,并且获取的⽅式与前⾯的InitBinder⾮常的相似,这⾥就不再赘述。关于这⾥获取的⽅法,其具体的执⾏过程实际上是在后⾯的ModelFactory.initModel()⽅法中进⾏。这⾥我们直接阅读该⽅法的源码:
public void initModel(NativeWebRequest request, ModelAndViewContainer container,
HandlerMethod handlerMethod) throws Exception {
// 在当前request中获取使⽤@SessionAttribute注解声明的参数
Map<String, ?> sessionAttributes =
ieveAttributes(request);
// 将@SessionAttribute声明的参数封装到ModelAndViewContainer中
// 调⽤前⾯获取的使⽤@ModelAttribute标注的⽅法
invokeModelAttributeMethods(request, container);
// 这⾥⾸先获取⽬标handler执⾏所需的参数中与@SessionAttribute同名或同类型的参数,
// 也就是handler想要直接从@SessionAttribute中声明的参数中获取的参数。然后对这些参数
// 进⾏遍历,⾸先判断request中是否包含该属性,如果不包含,则从之前的SessionAttribute缓存
// 中获取,如果两个都没有,则直接抛出异常
for (String name : findSessionAttributeArguments(handlerMethod)) {
if (!ainsAttribute(name)) {
Object value = ieveAttribute(request, name);
if (value == null) {
throw new HttpSessionRequiredException("Expected session attribute '"
+ name + "'", name);
}
container.addAttribute(name, value);
}
}
}
这⾥initModel()⽅法主要做了两件事:①保证@SessionAttribute声明的参数的存在;②调⽤使⽤@ModelAttribute标注的⽅法。我们直接阅读invokeModelAttributeMethods()⽅法的源码:
private void invokeModelAttributeMethods(NativeWebRequest request,
ModelAndViewContainer container) throws Exception {
while (!delMethods.isEmpty()) {
// 这⾥getNextModelMethod()⽅法始终会获取modelMethods中的第0号为的⽅法,
// 后续该⽅法执⾏完了之后则会将该⽅法从modelMethods移除掉,因⽽这⾥while
// 循环只需要判断modelMethods是否为空即可
InvocableHandlerMethod modelMethod =
getNextModelMethod(container).getHandlerMethod();
// 获取当前⽅法中标注的ModelAttribute属性,然后判断当前request中是否有与该属性中name字段
// 标注的值相同的属性,如果存在,并且当前ModelAttribute设置了不对该属性进⾏绑定,那么
// 就直接略过当前⽅法的执⾏
ModelAttribute ann = MethodAnnotation(ModelAttribute.class);
Assert.state(ann != null, "No ModelAttribute annotation");
if (ainsAttribute(ann.name())) {
if (!ann.binding()) {
container.setBindingDisabled(ann.name());
}
continue;
}
// 通过ArgumentResolver对⽅法参数进⾏处理,并且调⽤⽬标⽅法
Object returnValue = modelMethod.invokeForRequest(request, container);
// 如果当前⽅法的返回值不为空,则判断当前@ModelAttribute是否设置了需要绑定返回值,
// 如果设置了,则将返回值绑定到请求中,后续handler可以直接使⽤该参数
if (!modelMethod.isVoid()){
String returnValueName = getNameForReturnValue(returnValue,
if (!ann.binding()) {
container.setBindingDisabled(returnValueName);
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论