一、handlerAdapter类
1、handlerAdapter简介
在前面我们已经分析了通过HandlerMapping(处理器映射器)将请求映射到了对应的Handler上,下面就需要考虑如何解析并执行该handler对象,这里HandlerAdapter(处理器适配器)就登场了,我直接解析调用handler不就行了为啥还要使用HandlerAdapter包装一层呢?在之前的分析中我们了解到了两种类型的handler对象。
(1)以实现了Controller接口的Handler类
public class DemoController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("进入DemoController方法执行处理逻辑");
return new ModelAndView("demo");
}
}
(2)以@RequestMapping注解修饰的HandlerMethod对象
@RequestMapping(value = "/book/ListPage",method = RequestMethod.POST)
@ResponseBody
public String getBookPage(@RequestBody BookQuery bookQuery){
return success(pageTotal(pageInfo));
}
(3)其他类型Handler对象
其他还有实现Servlet的实例,HandlerFunction实例、HttpRequestHandler实例等,不同的实例对象调用时走不同的方法,为了能将不同的方法转换成统一的调用形式,这里使用了适配器模式,将各个实例的方法调用包装到HandlerAdapter统一调用。
2、HandlerAdapter的体系结构
HandlerAdapter是一个顶层接口,其提供如下三个方法
supports(Object handler) | 该方法判断该适配器类是否支持解析调用Handler对象,因为Adapter被设计成了一个只适配执行某一种类型比如Controller接口类型或@RequestMapping类型的Handler需要使用SimpleControllerHandlerAdapter来进行处理,该方法在执行handler()方法之前需要使用该方法来判断是否支持调用这种类型的Handler对象。 |
handle(req,rep,handler) | 解析并调用Handler对象的方法执行业务逻辑,从request请求中获取参数,执行handler并将响应结果放入reponse对象 |
getLastModified | 获取请求的资源的最终修改时间,如果请求的资源未被修改则直接使用浏览器缓存,这个方法主要是针对http请求为get/head请求为提高性能的缓存处理。 |
通过HandlerAdapter的UML类图我们可以看到该接口的继承关系比较简单,其提供了若干个分别对应上述不同Handler实例的适配器对象实现,由于@RequestMapping修饰的方法一个普通的方法因此比较复杂这里提供了一AbstractHandlerMethodAdapter简单抽象和继承前者的RequestMappingHandlerAdapter子类来处@RequestMapping修饰的普通方法的调用。
类实例 | 功能描述 |
SimpleControllerHandlerAdapter | 用于处理实现Controller接口的实现类,调用其handleRequest方法处理请求 |
HandlerFunctionAdapter | 用于处理实现HandlerFunction接口的实现类,调用其handle方法处理请求 |
HttpRequestHandlerAdapter | 用于处理实现HttpRequestHandler接口的实现类,调用其handleRequest方法处理请求 |
SimpleServletHandlerAdapter | 用于处理实现Servlet接口的实现类,调用其service方法处理请求 |
AbstractHandlerMethodAdapter | 简单对HandlerAdappter进行简单实现,并通过重写方法让子类按需扩展 |
RequestMappingHandlerAdapter | 用于处理使用@RequestMapping注解修饰的方法。 |
在上面的适配器子类中其他的实现逻辑都很简单实现形式比较固定,只要调用固定的方法即可完成执行。但是对于处理@RequestMapping注解修饰的方法调用的RequestMappingHandlerAdapter类就比较复杂了,因为@RequestMapping修饰的是一个普通方法,对于方法调用可以直接使用反射,但是对于方法的有无入参,入参的个数以及类型的解析这个是一个难点同时该类也是我们常用的处理器映射器组件,所以我们下面会着重讲解与@RequestMapping注解修饰的普通公有方法调用相关的处理器适配器类AbstractHandlerMethodAdapter以及其子类RequestHandlerMethodMappingAdapter。
二、AbstractHandlerMethodAdapter
1、AbstractHandlerMethodAdapter类
根据类的继承关系RequestMappingHandlerAdapter继承了AbstractHandlerMethodAdapter类,AbstractHandlerMethodAdapter类对核心HandlerAdapter接口三个方法进行了重写,并提供了相应的XXXInternal()方法供子类重写,抽象类同时也实现了Order接口(有顺序),Applicationaware接口(持有Application对象)、ServletAware(持有ServletContext对象)、继承了WebContentGenerator类(提供了http请求的相关参数GET/POST等等)。
1.1、Supports()方法
子类重写supportsInternal()方法 返回true,该方法逻辑满足是使用@RequestMapping修饰的普通方法转换的HandlerMethod对象才可以使用该Adapter进行处理
@Override
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
1.2、getLastModified()方法
完全交由子类重写getLastModifiedInternal()方法,子类重写返回固定值-1,表明无最后修改时间,每次请求都重新获取数据。
1.3、handle()方法
上面的两个方法比较简单其核心的方法应该是如何针对查找的到Handler对象处理web请求对象并返回参数的handler()方法,该方法涉及参数解析,执行、封装返回参数三大步骤,比较复杂,我们这里使用单独的章节来说明。、
/**
* Adapter的核心方法 主要是将请求中的参数解析出来,调用其中的Handler方法,并将其执行结果
* 包装成ModelAndView 返回。
* @param request web请求对象
* @param response web响应对象
* @param handler 业务处理对象类
* @return 包含数据和视图的对象类型
*/
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler){
}
通过方法签名,可以分析出来该方法主要的处理逻辑分成三个部分
- 从request中解析出来相关的请求参数
- 根据请求参数调用相关的执行方法(handler) 处理业务逻辑,
- 针对@ReponseBody等对相关的执行方法的返回值进行相关处理(序列化等等)设置到MdodelAndView中
三、RequestMappingHandlerAdapter类
该类是我们日常开发中使用的核心类,其对抽象Adapter的暴露出来的supportsInternal、handleInternal、getLastModifiedInternal进行重写,其中supportsInternal返回值固定为true结合父类该类只对使用@RequestMapping修饰的HandlerMethod对象进行处理,同时getLastModifiedInternal返回-1 则说明springMVC对于GET/HEAD请求不走使用本地缓存。排除掉这两个简单的方法我们需要重点关注的便是对应处理handler对象的handleInternal方法。在详细分析该方法之前我们先了解一下与之相关的知识补充。
1、知识补充
1.1、@InitBinder的使用
使用该注解修饰的方法会在每一个web请求到达Controller的@RequestMapping修饰的方法之前先执行该注解修饰的方法,如果在某个Controller中声明则该方法作用范围仅限于该Controller类,
如果想要使其全局起作用则需要搭配@ControllerAdvice修饰的类,即在使用@ControllerAdvice注解修饰的类中再使用@InitBinder注解修饰的方法则该方法是全局的,该项目中所有web请求执行都会调用该方法。
主要的使用场景如下(上图也进行了说明)
1、注册属性编辑器 针对表单提交的日期类型的转换(默认日期类型的数据会转换失败)
2、注册校验器 对入参进行校验
1.2、@ModelAttribute的使用
@ModelAttribute注解用于将方法的参数或方法的返回值绑定到指定的模型属性上,该注解修饰的方式和上面@InitBinder注解修饰的方法执行时机一样都是在每次请求到达Controller的方法之前执行,也有全局和局部范围,例子如下
//ModelAttribute注解的第一种方式 使用其中的value属性作为key
//其中方法的返回值作为value 设置到model中
@ModelAttribute("name")
public String setName(){
return "索隆";
}
//ModelAttribute注解的第二种方式 方法中含有model 则直接设置到model对象中
@ModelAttribute()
public void setModel(Model model){
Student student = new Student();
student.setName("路飞");
student.setAge(24);
model.addAttribute("student",student);
}
//ModelAttribute注解的第三种方式
//注解即没有value的属性值作为key,也没有model对象
//这会通过ModleFactory根据返回类型解析出参数名作为key,并将返回对象作为value存放到model中
//TODO 这里没有起作用
@ModelAttribute
public Student setModelTwo(){
Student studentTWO = new Student();
studentTWO.setName("山治");
studentTWO.setAge(24);
return studentTWO;
}
//这里解析参数名为string
@ModelAttribute
public String setModelThree(){
return "山治";
}
//在model中进行了设置可以在该方法中进行获取
@RequestMapping(value = "testModelAttribute",method = RequestMethod.GET)
public String testModelAttribute(Model model,HttpSession session){
Map param = model.asMap();
logger.info("获取modelAttribute第一种方式设置的参数:{}",param.get("name"));
logger.info("获取modelAttribute第二种方式设置的参数:{}",((Student)param.get("student")).getName());
logger.info("获取modelAttribute第三种方式设置的参数:{}",param.get("name"));
String dream = (String) session.getAttribute("dream");
logger.info(dream);
return "/demo";
}
1.3、ResponseBodyAdvice的使用
该接口可以处理返回值为json格式的数据 在该数据返回之前对数据进行处理,如下代码所示:
/**
* 实现自定义的ReponseBodyAdvice接口并配合ControllerAdvice注解(所有处理器都适用)
* 用于将所有处理器中返回值为json格式数据响应之前对其进行修改
*/
@ControllerAdvice
public class StudentReponseBodyAdvice implements ResponseBodyAdvice {
/**
* 用于判断该Advice 是否其作用
* @param returnType 对应的方法包含其参数对象
* @param converterType 类型转换对象 .MappingJackson2HttpMessageConverter
* @return
*/
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
/**
* 在请求被处理完返回数据之前可以对数据的方法
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType, Class selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
System.out.println("TestResponseBodyAdvice==>beforeBodyWrite:" + body.toString() + ","
+ returnType);
//转换数据
if(body instanceof Student){
Student student = (Student) body;
student.setName("文斯莫克·山治");
return student;
}
return body;
}
}
请求示例及结果:默认返回name为“山治”,但是因为有ResponseBodyAdvice 的处理名字变为了“文斯莫克·山治”。
@ResponseBody
@RequestMapping(value = "query/{id}",method = RequestMethod.GET)
public Student queryStudent(@PathVariable("id") Integer id){
Student student = new Student();
student.setName("山治");
student.setAge(24);
return student;
}
1.4、@SessionAttributes的使用
@SessionAttributes 只能作用在类上,作用是将指定的Model中的键值对添加至session中,这样同一个会话中可以使用该对应的属性。下面是该注解的使用示例。
@
具体是将属性值设置到session中是依靠SessionAttributeHandler的属性SessionAttributeStore对象来实现的,大家不要被这个类的英文名字所欺骗认为它是存储sesion级别属性的集合,其实该对象确切的说是操作request对象获取session并设置属性值的工具类。
1.5、@ControllerAdviceBean的使用
在前面的知识点的补充中我们已经了解到了该注解的用法,主要使用声明一些全局性的行为方法,比如搭配上面的@InitBinder、@ModelAttributes使其全局有效,或者搭配@ExceptionHandler实现全局异常处理。
2、handleInternal方法分析
2.1、checkRequest()方法
请求的检查checkRequest() 主要是支持请求方法类型,和是否必须存在session的校验。可通过xml进行配置。
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<!-- 设置支持的方法 只有满足这里列举的请求 才会进行处理否则抛出异常-->
<property name="supportedMethods">
<set>
<value>POST</value>
</set>
</property>
<!-- 设置请求必须包含session 否则抛出异常 -->
<property name="requireSession" value="true" />
<property name="messageConverters">
<list>
<ref bean="stringHttpMessageConverter"/>
</list>
</property>
</bean>
2.2、invokeHandlerMethod()方法
第二部分是针对session的是否同步请求 调用核心方法invokeHandlerMethod,同步session与否两者的区别在于同一个会话用户服务器只能处理一个请求,其他的请求只能等待串行执行。
2.3、响应信息的缓存处理
第三部分则是针对请求完后后对于reponse设置相关的本地缓存的请求头信息,如果设置缓存请求头信息则浏览器第一次请求完成后会将资源存放到本地缓存,再次请求如果缓存没有过期则直接使用本地缓存。 RequestMappingHandlerAdapter默认不支持本地缓存 我们可以通过如下示例来实现走本地缓存
- 重写RequestMappingHandlerAdapter 的getLastModifiedInternal使其返回一个正数
public class MyRequestHandlerMappingAdapter extends RequestMappingHandlerAdapter {
@Override
protected long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod) {
return 100L;
}
}
- 在xml配置上面编写的自定义适配器中配置cacheSecondsForSessionAttributeHandlers 缓存属性
<bean class="com.xiu.HandlerAdapter.MyRequestHandlerMappingAdapter">
<!-- 设置缓存的秒数 该属性好像只适用于有sessionAttributes属性对象存在的情况 但是他也提供了CacheControl设置缓存属性-->
<property name="cacheSecondsForSessionAttributeHandlers" value="1000"/>
</bean>
如上步骤完成,便可以将响应结果缓存,同时再次相同的请求过来则使用浏览器自身的缓存。
2.4、HandlerInternal()方法
下面是对处理handler请求的HandlerInternale方法进行简要的分析,在该方法只是对handler解析调用的处理的入口方法核心的处理落实是交由invokeHandlerMethod()方法中。下面我们来分析该方法。
/**
* 该方法是我们针对获取到的Handler对象进行转换解析处理的入口方法
* 主要包含请求request的相关校验校验
* 解析、转换、执行相关的handler的invokeHandlerMethod 并返回对应的数据视图对象。
* 对应执行结束的响应对象response的设置 比如SessionAttributes信息的缓存控制
*/
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
//如果设置supportMethods、requireSession属性需要对其进行校验
//如果处理的请求方式不是supportMethods集合支持的抛出异常
//或者requireSession为true但是request不包含session同样抛出异常
checkRequest(request);
// 针对线程不安全的session 提供两种方式 一种是同步 从而保证该会话的用户串行执行请求
// 一种是非同步处理请求 不管同步与否最终调用核心方法invokeHandlerMethod 调用HandlerMethod
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
}
//为响应资源设置对应的缓存时间,指定时间内可以直接使用本地缓存
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
3、invokeHandlerMethod方法分析
/**
* 解析、转换、执行、返回数据视图的核心方法
*/
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
//先将请求响应对象包装成ServletWebRequest
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
//获取该方法的所有使用@InitBinder注解修饰的方法
//包装成WebDataBinderFactory 在handlerMethod执行前调用(包含类作用范围的和全局作用范围的)
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
//获取该方法的所有使用@ModelAttribute注解修饰的方法 包装成ModelFactory(包含类作用范围的和全局作用范围的)
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
//上面获取@InitBinder/@ModelAttribute注解修饰的方法雷同,都是先接卸handlerMethod对应的类
//中类范围的注解修饰的方法(解析完成后放入对应的缓存),接着获取@ControllerAdvice修饰的全局范围的使用如上注解修饰的方法
//将对应的方法包装成InvocableHandlerMethod用于后面的执行,并将其包装成WebDataBinderFactory/ModelFactory对象。
//两者的区别在于ModelFactory中不仅仅包含@ModelAttribute注解修饰的方法还包含@SessionAttribute注解修饰的model处理
//同时也将上面解析出来的WebDataBinderFactory存放到modelFactory中。
//将HandlerMethod方法包装成ServletInvocableHandlerMethod(该对象是InvocableHandlerMethod子类)
//设置相关的组件 比如设置参数解析组件argumentResolvers和返回结果处理组件returnValueHandlers
//绑定组件binderFactory和parameterNameDiscoverer
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
//创建数据模型和视图的容器对象 见名知义其包含我们所常见的数据和视图对象
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
//对于重定向相关的参数保存需要依赖flashMap对象,如果一个请求是重定向请求 则input_flash_map保存重定向入参
//如果一个请求需要进行重定向 则参数会存放到output_flash_map中
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
//调用@ModelAttribute注解修饰的方法将对应的属性存放到mavContainer 的model中
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
//异步请求处理 此处再是先省略
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
//通过反射执行相关的handlerMethod 涉及参数解析,返回值的处理、
//invocableMethod已经包含了进行参数解析和返回值处理的组件对象
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
//主要处理sessionAttribute 以及对model中的属性进行属性编辑器的转换处理@InitBinder
//对于view对象进行逻辑视图到物理视图的转换以及重定向参数的设置
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}