04-HandlerAdapter

2022-07-18

04-HandlerAdapter

springmvc之HandlerAdapter

一. 概述

​ 前面HandlerMapping的作用是根据request找到Handler。通过分析我们也知道Handler可能有很多种,我们自定义的类,方法,还有springmvc给我们内置的一些。每种Handler的调用方式都不尽相同,因此就需要HandlerAdapter。

​ 顾名思义,HandlerAdapter是一种适配器,对于某种类型的Handler,都有一种对应的Adapter,用来完成对Handler的调用。

// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

DispatchServlet中HandlerAdapter主要就是这两行代码。第一句是根据Handler找到对应的适配器,第二句是适配器调用Handler

HandlerAdapter接口

image-20210608155942776

这个接口一共有三个方法:

  • supports(): 判断是否适用于某个Handler
  • handle:调用具体的handler
  • getLastModified():获取资源的最后一次修改时间

我们经常使用的是RequestMappingHandlerAdapter,下面就对这种Adapter做具体分析

二. 源码分析

2.1 概述

RequestMappingHandlerAdapter是整个mvc中最复杂的组件。继承自AbstractHandlerMethodAdapter

AbstractHandlerMethodAdapter重写了接口中的三个方法,实现了一些简单的判断,并提供了模板方法给子类

@Override
public final boolean supports(Object handler) {
  return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

只需要handler是HandlerMethod子类即可,supportsInternal是一个模板方法,由子类实现

@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
  throws Exception {

  return handleInternal(request, response, (HandlerMethod) handler);
}

简单的将handler强转为HandlerMethod类型,代表该适配器只处理这一种类型的

@Override
public final long getLastModified(HttpServletRequest request, Object handler) {
  return getLastModifiedInternal(request, (HandlerMethod) handler);
}

这个方法和上面的一样的道理。

RequestMappingHandlerAdapter对上面三个模板方法又进行了重写。

它的supportsInternal直接返回true,也就是不增加判断Handler的条件,只需要满足父类中的HandlerMethod类型的要求就可以了;getLastModifiedInternal直接返回-1;最重要的就是handleInternal方法,就是这个方法实际使用Handler处理请求。

处理请求可以分为以下三步:

  1. 备好处理器需要的参数
  2. 使用处理器请求参数
  3. 处理返回值,也就是将不同类型的返回值统一处理成ModelAndView类型

所绑定的参数的来源有6个地方:

①request中相关的参数,主要包括url中的参数、post过来的参数以及请求头中的值;

②cookie中的参数;

③session中的参数;

④设置到FlashMap中的参数,这种主要用于redirect的参数传递;

⑤SessionAttributes传递的参数;

⑥通过相应的注释了@ModelAttribute的方法进行设置的参数。

前三类参数通过request管理,后三类通过Model管理。前三类在request中获取,不需要做过多准备工作。第四类参数是直接在RequestMappingHandlerAdapter中进行处理的,在请求前将之前保存的设置到model,请求处理完成后如果需要则将model中的值设置到FlashMap。后两类参数使用ModelFactory管理。

2.2 创建初始化

该类实现了InitializingBean接口,因此会自动调用afterPropertiesSet()

@Override
public void afterPropertiesSet() {
  // Do this first, it may add ResponseBody advice beans
  initControllerAdviceCache();

  if (this.argumentResolvers == null) {
    List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
    this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
  }
  if (this.initBinderArgumentResolvers == null) {
    List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
    this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
  }
  if (this.returnValueHandlers == null) {
    List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
    this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
  }
}

这里主要进行了如下几个操作:

  1. 对加了@ControllerAdvice注解的类进行初始化,这里配置了全局的一些增强handler
  2. 设置argumentResolvers,用于给处理器方法和注释了@ModelAttribute的方法设置参数
  3. 设置initBinderArgumentResolvers,用于给注释了@initBinder的方法设置参数
  4. 设置returnValueHandlers,用于将处理器的返回值处理成ModelAndView的类
private void initControllerAdviceCache() {
  if (getApplicationContext() == null) {
    return;
  }

  List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());

  List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();

  for (ControllerAdviceBean adviceBean : adviceBeans) {
    Class<?> beanType = adviceBean.getBeanType();
    if (beanType == null) {
      throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
    }
    //查找注释了@ModelAttribute方法但没有注释@RequestMapping的方法
    Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
    if (!attrMethods.isEmpty()) {
      this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
    }
    //查找注释了@InitBinder的方法
    Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
    if (!binderMethods.isEmpty()) {
      this.initBinderAdviceCache.put(adviceBean, binderMethods);
    }
    if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
      requestResponseBodyAdviceBeans.add(adviceBean);
    }
  }

  if (!requestResponseBodyAdviceBeans.isEmpty()) {
    this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
  }
}

这里是先加载保存全局的@ModelAttribute,@InitBinder方法,并且如果bean是RequestBodyAdvice类型的,也相应的保存起来

2.3 使用

整个的使用逻辑都在handleInternal()的实现上

@Override
protected ModelAndView handleInternal(HttpServletRequest request,
                                      HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

  ModelAndView mav;
  checkRequest(request);

  // Execute invokeHandlerMethod in synchronized block if required.
  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;
}

处理流程分为以下几步:

  1. 检查请求方式是否被支持;检查session是否需要
  2. 具体执行业务方法invokeHandlerMethod
  3. 对response缓存做一些操作(不做分析)

最核心的逻辑就是invokeHandlerMethod

@Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        try {
            WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
            ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

            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();
            mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
            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);
            }

            invocableMethod.invokeAndHandle(webRequest, mavContainer);
            if (asyncManager.isConcurrentHandlingStarted()) {
                return null;
            }

            return getModelAndView(mavContainer, modelFactory, webRequest);
        }
        finally {
            webRequest.requestCompleted();
        }
    }

这段方法很复杂,也涉及了很多组件,总的执行流程如下

  1. 依次创建ServletWebRequestWebDataBinderFactoryModelFactoryServletInvocableHandlerMethodModelAndViewContainer
  2. 对异步请求做一些处理
  3. 调用业务Handler方法本身
  4. 返回modelAndView

下面具体分析每个组件的作用

2.4 ServletWebRequest

首先创建了一个ServletWebRequest对象

ServletWebRequest webRequest = new ServletWebRequest(request, response);

​ 这是一个对HttpServletRequest对象的适配器,相当于将原始的request和response包装了一层。在ArgumentResolver解析参数时使用的request就是这个webRequest,当然如果我们的处理器需要HttpServletRequest类型的参数,ArgumentResolver会给我们设置原始的request

2.5 WebDataBinderFactory

WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
  Class<?> handlerType = handlerMethod.getBeanType();
  Set<Method> methods = this.initBinderCache.get(handlerType);
  if (methods == null) {
    methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
    this.initBinderCache.put(handlerType, methods);
  }
  List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
  // Global methods first
  this.initBinderAdviceCache.forEach((clazz, methodSet) -> {
    if (clazz.isApplicableToBeanType(handlerType)) {
      Object bean = clazz.resolveBean();
      for (Method method : methodSet) {
        initBinderMethods.add(createInitBinderMethod(bean, method));
      }
    }
  });
  for (Method method : methods) {
    Object bean = handlerMethod.getBean();
    initBinderMethods.add(createInitBinderMethod(bean, method));
  }
  return createDataBinderFactory(initBinderMethods);
}

​ WebDataBinderFactory的作用从名字就可以看出是用来创建WebDataBinder的,**WebDataBinder用于参数绑定,主要功能就是实现参数跟String之间的类型转换,**ArgumentResolver在进行参数解析的过程中会用到WebDataBinder,另外ModelFactory在更新Model时也会用到它。

  1. 首先从缓存中尝试获取该方法所在类中加了@InitBinder注解的方法,保存在methods中
  2. 其次获取之前@ControllerAdvice中全局的@InitBinder,把对应的方法转为InvocableHandlerMethod类型,保存起来
  3. 然后加入本类中的@InitBinder
  4. 最后创建ServletRequestDataBinderFactory并返回

InvocableHandlerMethod也是一种HandlerMethod,创建过程如下

private InvocableHandlerMethod createInitBinderMethod(Object bean, Method method) {
  InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(bean, method);
  if (this.initBinderArgumentResolvers != null) {
    binderMethod.setHandlerMethodArgumentResolvers(this.initBinderArgumentResolvers);
  }
  binderMethod.setDataBinderFactory(new DefaultDataBinderFactory(this.webBindingInitializer));
  binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
  return binderMethod;
}

相当于给HandlerMethod设置了参数解析器用于后续的参数解析

image-20210609160643410

具体的使用在后续的分析中再一探究竟,这里只是创建出来

2.6 ModelFactory

2.6.1 创建

ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
        SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
        Class<?> handlerType = handlerMethod.getBeanType();
        Set<Method> methods = this.modelAttributeCache.get(handlerType);
        if (methods == null) {
            methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
            this.modelAttributeCache.put(handlerType, methods);
        }
        List<InvocableHandlerMethod> attrMethods = new ArrayList<>();
        // Global methods first
        this.modelAttributeAdviceCache.forEach((clazz, methodSet) -> {
            if (clazz.isApplicableToBeanType(handlerType)) {
                Object bean = clazz.resolveBean();
                for (Method method : methodSet) {
                    attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
                }
            }
        });
        for (Method method : methods) {
            Object bean = handlerMethod.getBean();
            attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
        }
        return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
    }

ModelFactory是用来处理Model的,主要包含两个功能:

①在处理器具体处理之前对Model进行初始化;

②在处理完请求后对Model参数进行更新。

整个的创建流程和上面的DataBinderFactory大同小异。

  1. 获取SessionAttributesHandler,用来管理@SessionAttributes

    image-20210609163619893

  2. 获取本类中添加了@ModelAttribute但没有添加@RequestMapping注解的方法

  3. 将全局中满足上述条件的方法转为InvocableHandlerMethod并保存

  4. 保存本类满足条件的方法

  5. 创建并返回ModelFactory

2.6.2 初始化Model

public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod)
            throws Exception {

        Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
        container.mergeAttributes(sessionAttributes);
        invokeModelAttributeMethods(request, container);

        for (String name : findSessionAttributeArguments(handlerMethod)) {
            if (!container.containsAttribute(name)) {
                Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
                if (value == null) {
                    throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);
                }
                container.addAttribute(name, value);
            }
        }
    }

这里填充了两个参数来源:

  1. @ModelAttribute标注的方法的所产生的属性

    private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container)
      throws Exception {
    
      while (!this.modelMethods.isEmpty()) {
        InvocableHandlerMethod modelMethod = getNextModelMethod(container).getHandlerMethod();
        ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class);
        Assert.state(ann != null, "No ModelAttribute annotation");
        if (container.containsAttribute(ann.name())) {
          if (!ann.binding()) {
            container.setBindingDisabled(ann.name());
          }
          continue;
        }
    
        Object returnValue = modelMethod.invokeForRequest(request, container);
        if (!modelMethod.isVoid()){
          String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType());
          if (!ann.binding()) {
            container.setBindingDisabled(returnValueName);
          }
          if (!container.containsAttribute(returnValueName)) {
            container.addAttribute(returnValueName, returnValue);
          }
        }
      }
    }
    

    在这里,我们前面在创建ModelFactory时保存好的ModelAttribute方法得以执行,并且返回值作为属性值添加到mav容器中

    小技巧:

    由此可知ModelAttribute执行的时机。即在业务方法执行之前统一执行,其中全局的ModelAttribute优先执行,本Handler中的其次执行

  2. @SessionAttributes所标注的属性

    对于这种属性的处理,分为如下几步:

    • 先把@SessionAttributes中保存的属性并且有值的复制到mav中(这种是如果之前已经有方法保存了,可以直接拿到)

    • 遍历所有加了@ModelAttribute的参数并且是@SessionAttributes中存在的属性(这种主要是第一次type类型的属性不会被加入,只有确定的对应类型的参数在这里会被加进去,并且后面会被放入knownAttributeNames,以后就不用这里找了)

      这里也很好理解,@SessionAttributes指定的是type,自然就不知道对应的名字是啥,只能知道类型。只有第一次找到加了@ModelAttribute注解的参数,并且正好是对应的类型才行。

    • 如果容器中不存在,尝试去从sessionStore中获取,获取不到抛出异常,获取到则添加到容器中

2.6.3 更新Model

modelFactory.updateModel(webRequest, mavContainer);
public void updateModel(NativeWebRequest request, ModelAndViewContainer container) throws Exception {
        ModelMap defaultModel = container.getDefaultModel();
        if (container.getSessionStatus().isComplete()){
            this.sessionAttributesHandler.cleanupAttributes(request);
        }
        else {
            this.sessionAttributesHandler.storeAttributes(request, defaultModel);
        }
        if (!container.isRequestHandled() && container.getModel() == defaultModel) {
            updateBindingResult(request, defaultModel);
        }
    }

如果已经设置了清除标记sessionStatus,那么将SessionAttribute内容清除

否则,将属性内容更新保存。

最后一步是如果请求还没有结束,并且model为defaultModel,那么设置BindingResult

private void updateBindingResult(NativeWebRequest request, ModelMap model) throws Exception {
  List<String> keyNames = new ArrayList<>(model.keySet());
  for (String name : keyNames) {
    Object value = model.get(name);
    if (value != null && isBindingCandidate(name, value)) {
      String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + name;
      if (!model.containsAttribute(bindingResultKey)) {
        WebDataBinder dataBinder = this.dataBinderFactory.createBinder(request, value, name);
        model.put(bindingResultKey, dataBinder.getBindingResult());
      }
    }
  }
}

model中有的属性,并且满足isBindingCandidate,那么加上前缀org.springframework.validation.BindingResult.,创建WebDataBinder对象,将BinderResult设置到Model中,方便下次使用。

@ResponseBody注解不会走这里,因为到这里请求已经完成了,即isRequestHandled为true

2.7 ServletInvocableHandlerMethod

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);

实际请求的处理就是通过它来执行的,参数绑定、处理请求以及返回值处理都在它里边完成。

这里创建过程很简单,首先new一个对象,然后设置参数解析器,返回值处理器,dataBinderFactory,参数名称Discover

2.7.1 HandlerMethod

image-20210610201125524

其他的都很好理解,主要是MethodParameter,主要用来封装方法参数

image-20210610201444792

其中比较重要的是parameterIndexExecutable

parameterIndex是参数的下标,从0开始

Executable是Method的父类,相当于Method

2.7.2 InvocableHandlerMethod

该类比HandlerMethod多了3个属性

  • dataBinderFactory:WebDataBinderFactory类型,可以创建WebDataBinder,用于参数解析器ArgumentResolver中。
  • argumentResolvers:HandlerMethodArgumentResolverComposite类型,用于解析参数。
  • parameterNameDiscoverer:ParameterNameDiscoverer类型,用来获取参数名,用于MethodParameter中。

这个类中的核心是调用Method

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
                               Object... providedArgs) throws Exception {

  Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
  if (logger.isTraceEnabled()) {
    logger.trace("Arguments: " + Arrays.toString(args));
  }
  return doInvoke(args);
}

首先获取方法上所有的参数值

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        MethodParameter[] parameters = getMethodParameters();
        if (ObjectUtils.isEmpty(parameters)) {
            return EMPTY_ARGS;
        }

        Object[] args = new Object[parameters.length];
        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) {
                continue;
            }
            if (!this.resolvers.supportsParameter(parameter)) {
                throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
            }
            try {
                args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
            }
            catch (Exception ex) {
                //...省略异常处理
            }
        }
        return args;
    }
  1. 获取方法上的参数MethodParameter,这些属性已经在创建的时候被封装好了
  2. 设置parameterNameDiscoverer,该类用来获取参数名
  3. 调用适配的参数解析器解析参数,获取返回值并保存

重点就在于参数解析器的解析:

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                              NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

  HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
  if (resolver == null) {
    throw new IllegalArgumentException("Unsupported parameter type [" +
                                       parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
  }
  return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

这里的逻辑是找到第一个适配的参数解析器(通过参数解析器的supportsParameter方法),然后解析参数并返回参数值

解析完参数后,调用执行

protected Object doInvoke(Object... args) throws Exception {
        ReflectionUtils.makeAccessible(getBridgedMethod());
        try {
            return getBridgedMethod().invoke(getBean(), args);
        }
        //...省略一些异常判断
    }

这个方法很简单,就是将私有方法也设置为可访问,然后直接反射调用

小知识:

实际上前面说过的注释了@InitBinder的方法和注释了@ModelAttribute的方法就是封装成了InvocableHandlerMethod对象,然后直接执行的。

2.7.3 ServletInvocableHandlerMethod

该类在父类的基础上,又增加3个功能

①对@ResponseStatus注释的支持;

②对返回值的处理;

③对异步处理结果的处理。

第一个用的不多,不做分析。

对返回值的处理使用到了HandlerMethodReturnValueHandler

异步处理不做分析

2.8 ModelAndViewContainer

ModelAndViewContainer承担着整个请求过程中数据的传递工作。

image-20210609164628252

  • view:视图,Object类型的,可以是实际视图也可以是String类型的逻辑视图。
  • defaultModel:默认使用的Model
  • redirectModel:redirect类型的Model。
  • sessionStatus:用于设置SessionAttribute使用完的标志
  • ignoreDefaultModelOnRedirect:如果为true则在处理器返回redirect视图时一定不使用defaultModel
  • redirectModelScenario:处理器返回redirect视图的标志
  • requestHandled:请求是否已经处理完成的标志

首先来看看defaultModel和redirectModel

public ModelMap getModel() {
  if (useDefaultModel()) {
    return this.defaultModel;
  }
  else {
    if (this.redirectModel == null) {
      this.redirectModel = new ModelMap();
    }
    return this.redirectModel;
  }
}
private boolean useDefaultModel() {
  return (!this.redirectModelScenario || (this.redirectModel == null && !this.ignoreDefaultModelOnRedirect));
}

由此可以看出:

返回defaultModel的情况:①处理器返回的不是redirect视图;②处理器返回的是redirect视图但是redirectModel为null,而且ignoreDefaultModelOnRedirect也是false。

返回redirectModel的情况:①处理器返回redirect视图,并且redirectModel不为null;②处理器返回的是redirect视图,并且ignoreDefaultModelOnRedirect为true。

ignoreDefaultModelOnRedirect可以在RequestMappingHandlerAdapter中设置。

判断处理器返回的是不是redirect视图的标志设置在redirectModelScenario中,它是在ReturnValueHandler中设置的ReturnValueHandler如果判断到是redirect视图就会将redirectModelScenario设置为true。也就是说在ReturnValueHandler处理前ModelAndViewContainer的getModel返回的一定是defaultModel,处理后才可能是redirectModel。

requestHandled用于标示请求是否已经全部处理完,如果是就不再往下处理,直接返回。这里的全部处理完主要指已经返回response,比如,在处理器返回值有@ResponseBody注释或者返回值为HttpEntity类型等情况都会将requestHandled设置为true。

2.9 SessionAttributesHandler和SessionAttributeStore

​ SessionAttributesHandler用来处理@SessionAttributes注释的参数,其中包含判断某个参数是否可以被处理以及批量对多个参数进行处理等功能。

public class SessionAttributesHandler {

  private final Set<String> attributeNames = new HashSet<>();

  private final Set<Class<?>> attributeTypes = new HashSet<>();

  private final Set<String> knownAttributeNames = Collections.newSetFromMap(new ConcurrentHashMap<>(4));

  private final SessionAttributeStore sessionAttributeStore;
}
  • attributeNames:存储@SessionAttributes注释里value对应的值,也就是参数名
  • attributeTypes:存储@SessionAttributes注释里types对应的值,也就是参数类型
  • knownAttributeNames:存储所有已知可以被当前处理器处理的属性名
public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) {
        Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null");
        this.sessionAttributeStore = sessionAttributeStore;

        SessionAttributes ann = AnnotatedElementUtils.findMergedAnnotation(handlerType, SessionAttributes.class);
        if (ann != null) {
            Collections.addAll(this.attributeNames, ann.names());
            Collections.addAll(this.attributeTypes, ann.types());
        }
        this.knownAttributeNames.addAll(this.attributeNames);
    }

需要注意的是,SessionAttributesHandler在初始化的时候,只会将本类上有的使用了names保存的属性放到knownAttributeNames中,而type不会放,其他类的也不会放。

type上的寻找见上面MaVc的分析。

2.10 HandlerMethodArgumentResolver

这个组件主要使用来解析参数的,也就是说通过一系列的操作,给我们Handler上指定的参数赋值,以便于最后能够通过反射调用方法本身。

每个Resolver对应一种类型的参数

public interface HandlerMethodArgumentResolver {

    boolean supportsParameter(MethodParameter parameter);

    @Nullable
    Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

总接口定义了两个方法

  • supportsParamter:该resolver是否支持这种参数
  • resolveArgument:具体用来解析参数的逻辑

HandlerMethodArgumentResolver的命名一般有两种方式

  • xxxMethodArgumentResolver:用来解析参数
  • XXXMethodProcessor:除了可以解析参数,还可以处理相应类型的返回值

另外,还有个Adapter,它也不是直接解析参数的,而是用来兼容WebArgumentResolver类型的参数解析器的适配器

下面依次介绍不同的resolver所起的作用

  1. AbstractMessageConverterMethodArgumentResolver: 使用HttpMessageConverter解析RequestBody类型参数的基类
  2. AbstractMessageConverterMethodProcessor:定义相关工具,不参与解析
  3. HttpEntityMethodProcessor:解析HttpEntity和RequestEntity类型的参数。
  4. RequestResponseBodyMethodProcessor:解析注释@RequestBody类型的参数
  5. RequestPartMethodArgumentResolver:解析注释了@RequestPart、MultipartFile类型以及javax.servlet.http.Part类型的参数
  6. AbstractNamedValueMethodArgumentResolver: 解析namedValue类型的参数(有name的参数,如cookie、requestParam、requestHeader、pathVariable等)的基类,主要功能有:①获取name;②resolveDefaultValue、handleMissingValue、handleNullValue;③调用模板方法resolveName、handleResolvedValue具体解析。
  7. AbstractCookieValueMethodArgumentResolver: 解析注释了@CookieValue的参数的基类
  8. ServletCookieValueMethodArgumentResolver:实现resolveName方法,具体解析cookieValue。
  9. ExpressionValueMethodArgumentResolver:解析注释@Value表达式的参数,主要设置了beanFactory,并用它完成具体解析,解析过程在父类完成
  10. MatrixVariableMethodArgumentResolver:□MatrixVariableMethodArgumentResolver:解析注释@MatrixVariable而且不是Map类型的参数(Map类型使用MatrixVariableMapMethodArgumentResolver解析)
  11. PathVariableMethodArgumentResolver:解析注释@PathVariable而且不是Map类型的参数(Map类型则使用PathVariableMapMethodArgumentResolver解析)
  12. RequestHeaderMethodArgumentResolver:解析注释了@RequestHeader而且不是Map类型的参数(Map类型则使用RequestHeaderMapMethodArgumentResolver解析)
  13. RequestParamMethodArgumentResolver:可以解析注释了@RequestParam的参数、MultipartFile类型的参数和没有注释的通用类型(如int、long等)的参数。如果是注释了@RequestParam的Map类型的参数,则注释必须有name值(否则使用RequestParamMapMethodArgumentResolver解析)
  14. ErrorsMethodArgumentResolver:解析Errors类型的参数(一般是Errors或Binding-Result),当一个参数绑定出现异常时会自动将异常设置到其相邻的下一个Errors类型的参数,设置方法就是使用了这个解析器,内部是直接从model中获取的。
  15. MapMethodProcessor:解析Map型参数(包括ModelMap类型),直接返回mav-Container中的model。
  16. ModelAttributeMethodProcessor:解析注释了@ModelAttribute的参数,如果其中的annotationNotRequired属性为true还可以解析没有注释的非通用类型的参数(RequestParamMethodArgumentResolver解析没有注释的通用类型的参数)。
  17. RedirectAttributesMethodArgumentResolver:解析RedirectAttributes类型的参数,新建RedirectAttributesModelMap类型的RedirectAttributes并设置到mavContainer中,然后返回给我们的参数。

参数解析器在执行的时候,是有顺序的,默认下自定义的参数解析器在最后。也就是说,只有在默认的参数解析器无法解析的时候,才会去找我们自定义的。

下面分析几个参数解析器,看看其实现步骤

2.10.1 RequestResponseBodyMethodProcessor

这个解析器是用来解析注释了@RequestBody注解的参数

@Override
public boolean supportsParameter(MethodParameter parameter) {
  return parameter.hasParameterAnnotation(RequestBody.class);
}
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                              NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

  parameter = parameter.nestedIfOptional();
  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);
}

readWithMessageConverters是从request中获取body,然后通过合适的MessageConvert转化为参数类型的对象

这也是这个解析器处理的核心逻辑

Class<?> contextClass = parameter.getContainingClass();
Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);

for (HttpMessageConverter<?> converter : this.messageConverters) {
  Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
  GenericHttpMessageConverter<?> genericConverter =
    (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
  if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
      (targetClass != null && converter.canRead(targetClass, contentType))) {
    if (message.hasBody()) {
      HttpInputMessage msgToUse =
        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;
  }
}

contextClass即Handler所在的类,targetClass即需要转换成的目标对象,contentType是request请求的contentType

遍历所有的HttpMessageConverter组件,通过这三个参数判断该组件是否能够解析读取该message

如果可以,那么如果message有body,即request有body,执行@RequestBodyAdvice增强逻辑,并且读取数据到body,并返回;如果没有body,直接handleEmptyBody

2.11 HandlerMethodReturnValueHandler

方法返回值处理器

用来处理方法返回值,具体的逻辑在invokeAndHandle方法中

try {
  this.returnValueHandlers.handleReturnValue(
    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
                              ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

  HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
  if (handler == null) {
    throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
  }
  handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

he参数解析器一样,这里也是选取一个返回值处理器进行处理,开发中最常用的是

RequestResponseBodyMethodProcessor,这个即是参数解析器,也能处理返回值,下面看看处理返回值的代码

@Override
public boolean supportsReturnType(MethodParameter returnType) {
  return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
          returnType.hasMethodAnnotation(ResponseBody.class));
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
                              ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
  throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

  mavContainer.setRequestHandled(true);
  ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
  ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

  // Try even with null return value. ResponseBodyAdvice could get involved.
  writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
  1. 将请求完成标志设置为true
  2. 将返回对象作为json数据直接写到response中

三. 使用技巧

3.1 @ControllerAdvice

@ControllerAdvice,从名字可以窥见这是一个提供增强的注解,主要提供以下三种功能

  1. 全局异常处理
  2. 全局数据绑定
  3. 全局数据预处理

从上面的分析可以看出,这个类里面标识的处理都会优先执行,下面来具体看看其中的作用

3.1.1 @ModelAttribute

@ModelAttribute顾名思义,是用来数据绑定的。可以用在方法上,也可以用在参数上。这里具体分析配合@ControllerAdvice的使用

@ControllerAdvice
public class BaseController {

    @ModelAttribute("nowDate")
    public Date modelAttribute() {
        System.out.println("先执行@ControllerAdvice中注释了@ModelAttribute的方法");
        //返回一个model属性,{"nowDate": new Date()},当前时间,那么所有Controller中的方法都可以从model中取出
        return new Date();
    }
}

按照如上声明了一个加了@ModelAttribute("nowDate")的方法,那么springmvc会自动把返回值注入到该属性名中,放到model中。并且由前面的源码分析,该类是优先执行的,这样在controller的其他地方可以直接使用这个参数。

@RequestMapping("/test4")
@ResponseBody
public String test4(@RequestParam("date") Date date, ModelMap modelMap) {
  System.out.println(date);
  System.out.println(modelMap.getAttribute("nowDate"));
  return "success";
}

这里可以取到nowDate的值。

3.1.2 @InitBinder

这个注解用来全局的数据初始化,数据预处理

@RequestMapping("/test12")
@ResponseBody
public ResponseResult<String> test12(Student student, User user) {
  System.out.println(student);
  System.out.println(user);
  return ResponseResult.ok("success");
}

如上的请求,Student和User中都有name属性,那么在请求的时候就会分不清传的name是哪个类的

默认情况下,会给两个都赋值,如下

image-20210609115729716

Student(card=null, name=mhn)
User(id=null, name=mhn, age=null)

如果要区分清楚,可以利用这个注解,如下

@InitBinder("s")
public void initBinderStudent(WebDataBinder binder) {
  binder.setFieldDefaultPrefix("s.");
}

@InitBinder("u")
public void initBinderUser(WebDataBinder binder) {
  binder.setFieldDefaultPrefix("u.");
}

@RequestMapping("/test12")
@ResponseBody
public ResponseResult<String> test12(@ModelAttribute("s") Student student, @ModelAttribute("u") User user) {
  System.out.println(student);
  System.out.println(user);
  return ResponseResult.ok("success");
}

image-20210609143054808

除此之外,还可以用于日期的预处理,把字符串日期转为date类型

public static final String DATE_FORMATTER = "yyyy-MM-dd";
@InitBinder
public void initBinder(WebDataBinder binder) {
    List<SimpleDateFormat> dateFormatter = new ArrayList<>();
    dateFormatter.add(new SimpleDateFormat(DATE_FORMATTER));
    System.out.println("initBinder被执行了");
    binder.registerCustomEditor(java.util.Date.class, new DateTimeEditor(dateFormatter, true));
}
public class DateTimeEditor extends PropertyEditorSupport {

    private List<SimpleDateFormat> formatters;
    private boolean allowEmpty;

    public DateTimeEditor(List<SimpleDateFormat> formatters, boolean allowEmpty) {
        this.formatters = formatters;
        this.allowEmpty = allowEmpty;
    }

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        if (allowEmpty && text == null) {
            setValue(null);
        }else {
            for (int i = 0; i < formatters.size(); i++) {
                try {
                    setValue(formatters.get(i).parse(text));
                    break;
                } catch (ParseException e) {
                    if (i == formatters.size() - 1) {
                        throw new IllegalArgumentException();
                    }
                }
            }
        }
    }
}

3.1.3 @ExceptionHandler

可以实现全局的异常处理

@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseResult handleException(Exception ex, HttpServletRequest request) throws Exception {
  ResponseResult result = new ResponseResult();
  if (ex instanceof ServiceException) {
    ServiceException e = (ServiceException) ex;
    result.setCode(e.getCode());
    result.setMsg(e.getMessage());
  }else {
    result.setCode(MessageCode.SYSTEM_INTERNAL_ERROR.getCode());
    result.setMsg(MessageCode.SYSTEM_INTERNAL_ERROR.getMessage());
  }
  log.error("system error:", ex);
  return result;
}

3.2 @SessionAttributes

该注解用在处理器类上,用于在多个请求之间传递参数,类似于session,其实这些值也是保存在session中的

@SessionAttributes(value = {"bookName", "description", "default"}, types = Integer.class)
public class SourceController{
    @RequestMapping("/test1")
    @ResponseBody
    public String test1(Model model, HttpServletRequest request, HttpServletResponse response) {
        model.addAttribute("bookName", "springMvc源码");
        model.addAttribute("description", "还不错的一本书");
        model.addAttribute("price", 69);
        return "success";
    }
    @RequestMapping(name = "Mahaonan", value = "/test2")
    @ResponseBody
    public String test2(HttpServletRequest request, String test, @ModelAttribute("bookName") String bookName, ModelMap model, SessionStatus status) {
        System.out.println(test);
        System.out.println(bookName);
        System.out.println(model.getAttribute("bookName"));
        System.out.println(model.getAttribute("description"));
        System.out.println(model.getAttribute("price"));
        status.setComplete();
        return "success";
    }
}

如上所示:在类上加了注解,指定需要传递哪些参数。

test1方法把参数设置到了model中;test2既可以利用@ModelAttribute取出来,也可以从model中取出来

SessionStatus.setComplete()则会清除这些参数

注意事项

  1. 与@ResponseBody注解的冲突

    通过源码的分析可以知道,属性设置到session中是由下面的方法执行的。这个方法的调用是在执行完业务方法之后。@ResponseBody注解会直接返回内容,不会渲染视图。因此会与该注解冲突,如果这时候调用,会出现session无法创建的问题

    modelFactory.updateModel(webRequest, mavContainer); 
    

    解决方案

    可以写一个拦截器,手动在处理业务之前手动创建session

    public class CreateSessionHandlerInterceptor implements HandlerInterceptor {
    
      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        return true;
      }
    }
    
  2. 跨controller访问问题

    由于,springmvc在查找这些属性的时候,是按类为单位保存查找的,因此如果跨controller就会找不到这些属性

    private final Map<Class<?>, SessionAttributesHandler> sessionAttributesHandlerCache = new ConcurrentHashMap<>(64);
    
    Class<?> handlerType = handlerMethod.getBeanType();
            SessionAttributesHandler sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType);
    

    解决方案:

    在另一个控制器也指定该注解,并设置同样的属性value

    原因:

    //原因如下:虽然sessionAttrHandler是以类为单位保存的,但是实际上取值还是从session中取的,只是说knownAttributeNames在另一个控制器中不共享
    public Map<String, Object> retrieveAttributes(WebRequest request) {
            Map<String, Object> attributes = new HashMap<>();
            for (String name : this.knownAttributeNames) {
                Object value = this.sessionAttributeStore.retrieveAttribute(request, name);
                if (value != null) {
                    attributes.put(name, value);
                }
            }
            return attributes;
        }
    //因此只要在另一个类中也指定同样的value或者type,使knownAttributeNames有值,那么还是能取到其他controller保存的值的
    

3.3 RedirectAttributes

根据上面对ModelAndViewContainer的分析,可知这个类用于重定向之间的参数传递,下面来看看使用

@RequestMapping("/test5")
public String test5(RedirectAttributes modelMap, Model model) {
  //使用RedirectAttributes的addFlashAttribute方法,可以在重定向之间传递参数
  modelMap.addFlashAttribute("redirect", "redirect");
  model.addAttribute("default", "default");
  return "redirect:test6";
}

@RequestMapping("/test6")
@ResponseBody
public String test6(ModelMap modelMap) {
  System.out.println(modelMap.getAttribute("default"));
  System.out.println(modelMap.getAttribute("redirect"));
  return "success";
}

注意:test5中使用了RedirectAttributes,当返回一个重定向标志时,该modelMap才会生效,否则会使用默认的Model。

test6中用普通的ModelMap接受,因为test6是一个普通的方法,只会使用defaultModel

原理:

对这个参数的解析使用到了RedirectAttributesMethodArgumentResolver

@Override
  public boolean supportsParameter(MethodParameter parameter) {
  return RedirectAttributes.class.isAssignableFrom(parameter.getParameterType());
}
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                              NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

  Assert.state(mavContainer != null, "RedirectAttributes argument only supported on regular handler methods");

  ModelMap redirectAttributes;
  if (binderFactory != null) {
    DataBinder dataBinder = binderFactory.createBinder(webRequest, null, DataBinder.DEFAULT_OBJECT_NAME);
    redirectAttributes = new RedirectAttributesModelMap(dataBinder);
  }
  else {
    redirectAttributes  = new RedirectAttributesModelMap();
  }
  mavContainer.setRedirectModel(redirectAttributes);
  return redirectAttributes;
}

对于RedirectAttributes类型的参数,新建了一个RedirectAttributesModelMap,并且设置到了mavContainer的redirectModel中以供使用。

对于redirectModel的传递在下面的方法中

//@RequestMappingHandlerAdapter  getModelAndView()
if (model instanceof RedirectAttributes) {
  Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
  HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
  if (request != null) {
    RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
  }
}

从model中获取getFlashAttributes,然后添加到了FlashMap中,而上面参数来源的分析可以知道FlashMap正是参数来源之一,会传递给后面的model

3.4 SessionAttributesStore

这个是存储@SessionAttribute注入属性的容器。本质上只是一个媒介,工具,具体存储的容器可以任意指定。

默认采用的session存储,为了在分布式下,这个注解还能有效,我们可以重写这个Store,使用redis

  1. RedisSessionAttributeStore使用redis作为存储媒介,实现了SessionAttributeStore接口
public class RedisSessionAttributeStore implements SessionAttributeStore {

    private RedisCacheClient redisCacheClient;


    public RedisSessionAttributeStore(RedisCacheClient redisCacheClient) {
        this.redisCacheClient = redisCacheClient;
    }

    @Override
    public void storeAttribute(WebRequest request, String attributeName, Object attributeValue) {
        Assert.notNull(attributeName, "Attribute name must not be null");
        redisCacheClient.set(attributeName, attributeValue, 30, TimeUnit.MINUTES);
    }

    @Override
    public Object retrieveAttribute(WebRequest request, String attributeName) {
        Assert.notNull(attributeName, "Attribute name must not be null");
        return redisCacheClient.get(attributeName);
    }

    @Override
    public void cleanupAttribute(WebRequest request, String attributeName) {
        Assert.notNull(attributeName, "Attribute name must not be null");
        redisCacheClient.del(attributeName);
    }
}
  1. 将该store注入RequestMappingHandlerAdapter
public class DelegatingWebMvcConfigurationSub extends DelegatingWebMvcConfiguration {

    @Autowired
    private RedisCacheClient redisCacheClient;

    /**
     * 使用RedisSessionAttributeStore代替默认的sessi2on存储
     * @return
     */
    @Override
    protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
        RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
        RedisSessionAttributeStore sessionAttributeStore = new RedisSessionAttributeStore(redisCacheClient);
        adapter.setSessionAttributeStore(sessionAttributeStore);
        return adapter;
    }
}

由于我们有了自定义的@EnableMvc注解的mvc扩展配置,@EnableMvc本质上是导入了DelegatingWebMvcConfiguration。在springboot中,只能有一个WebMvcConfigurationSupport

所以,我们重写DelegatingWebMvcConfiguration,以便于能够直接继承WebMvcConfigurationSupport,然后在MvcConfig中导入这个类,代替@EnableMvc。这样既可以使用WebMvcConfigurationSupport的功能,也可以使用WebMvcConfigurer的功能

@Configuration
//@EnableWebMvc
@Import(DelegatingWebMvcConfigurationSub.class)
public class MvcConfig implements WebMvcConfigurer {}

这样,@SessionAttribute存储的属性都会保存在redis中

3.5 @InitBinder执行时机

前面我们分析到了InitBinder是在创建WebDataBinderFactory的时候,从容器中按顺序保存好的,那么这些保存好的InvocableHandlerMethod是什么时候执行的呢?

从理论上分析,该注解的作用是参数预处理,那么一定是在参数解析器发挥作用的时候一起执行,下面来看看源码

AbstractNamedValueMethodArgumentResolver中,定义了如下的方法

if (binderFactory != null) {
  WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
  try {
    arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
  }
  catch (ConversionNotSupportedException ex) {
    throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
                                                            namedValueInfo.name, parameter, ex.getCause());
  }
  catch (TypeMismatchException ex) {
    throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
                                                  namedValueInfo.name, parameter, ex.getCause());

  }
}

可以看到,这里是执行@InitBinder的逻辑。@InitBinder对应的HandlerMethod早已保存在binderFactory中,然后在createBinder中具体执行

@Override
public void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception {
  for (InvocableHandlerMethod binderMethod : this.binderMethods) {
    if (isBinderMethodApplicable(binderMethod, dataBinder)) {
      Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);
      if (returnValue != null) {
        throw new IllegalStateException(
          "@InitBinder methods must not return a value (should be void): " + binderMethod);
      }
    }
  }
}

首先判断是否需要执行isBinderMethodApplicable

protected boolean isBinderMethodApplicable(HandlerMethod initBinderMethod, WebDataBinder dataBinder) {
        InitBinder ann = initBinderMethod.getMethodAnnotation(InitBinder.class);
        Assert.state(ann != null, "No InitBinder annotation");
        String[] names = ann.value();
        return (ObjectUtils.isEmpty(names) || ObjectUtils.containsElement(names, dataBinder.getObjectName()));
    }

如果注解中value为空 或者 dataBinder的ObjectName包含value,那么这个@InitBinder需要执行

最后调用invoke执行。这里需要注意,这里invokeForRequest比前面的多了一个参数dataBinder,并且这种方法不能有返回值。

3.6 RequestBodyAdvice

前面在分析@RequestBody时可以窥见这个类的作用,即在从request读取body前后做一些操作

@RequestBody代表将请求body作为一个json对象,转换为相应的java类。由于是从整个body中获取数据当做json串解析,因此一个方法只能有一个@RequestBody注解的参数

下面来看看RequestBodyAdvice的作用

在RequestMappingHandlerAdapter的初始化中,有如下的代码

if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
  requestResponseBodyAdviceBeans.add(adviceBean);
}
if (!requestResponseBodyAdviceBeans.isEmpty()) {
  this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
}

如果bean是RequestBodyAdvice类型的或者ResponseBodyAdvice,那么保存到requestResponseBodyAdvice

那么,我们只需要在@ControllerAdvice中创建这样的Bean即可

@ControllerAdvice
public class MyRequestBodyAdvice implements RequestBodyAdvice {

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        System.out.println("targetType " + targetType);
        return true;
    }
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        System.out.println(parameter.getParameterName());
        System.out.println(parameter.getParameterType());
        System.out.println(converterType);
        return inputMessage;
    }
    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        System.out.println(body);
        return body;
    }
    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }
}

那么这段逻辑就会在@ReponseBody read前后执行(具体见上面参数解析器的分析)

3.7 ResponseBodyAdvice

这个类的作用同理,是对@ReponseBody注释的方法,返回内容前做一些处理

@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice<ResponseResult> {

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public ResponseResult beforeBodyWrite(ResponseResult body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        System.out.println(returnType.getParameterType());
        System.out.println(body);
        return body;
    }
}

使用技巧

  1. 如果我们想要在拦截器的postHandler方法中获取方法的返回值,那么可以在这里将返回值保存到request属性中

    @Override
    public ResponseResult beforeBodyWrite(ResponseResult body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
      System.out.println(returnType.getParameterType());
      System.out.println(body);
      ServletServerHttpRequest req = (ServletServerHttpRequest) request;
      HttpServletRequest servletRequest = req.getServletRequest();
      servletRequest.setAttribute("returnVal", body);
      return body;
    }
    

    拦截器中代码:

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
      Object returnVal = request.getAttribute("returnVal");
      if (returnVal != null && returnVal instanceof ResponseResult) {
        ResponseResult responseResult = (ResponseResult) returnVal;
        System.out.println("在拦截器中获取到了返回值:" + responseResult);
      }
    }
    

3.8 自定义参数解析器

参数解析器需要实现HandlerMethodArgumentResolver接口,下面设计一个基于注解的参数解析器抽象类

public abstract class AbstractAnnotationMethodArgumentResolver<T extends Annotation> implements HandlerMethodArgumentResolver {

    private Class<T> clazz;

    public AbstractAnnotationMethodArgumentResolver(Class<T> clazz) {
        this.clazz = clazz;
    }

    /**
     * 什么样的参数才会被处理
     * @param parameter 方法参数封装的对象
     * @return
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        if (parameter.hasParameterAnnotation(this.clazz)) {
            return true;
        }
        return false;
    }

    /**
     * 处理参数的方法
     * @param parameter 方法参数封装的对象
     * @param mavContainer 上下文容器,处理model和view
     * @param webRequest
     * @param binderFactory
     * @return
     * @throws Exception
     */
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        T annotation = parameter.getParameterAnnotation(this.clazz);
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
        return doResolveArgument(request, response, annotation, parameter);
    }

    protected abstract Object doResolveArgument(HttpServletRequest request, HttpServletResponse response, T annotation, MethodParameter parameter) throws Exception;

}

下面是一个实现了将用户登录token自动解析为UserId的参数解析器

@Slf4j
public class CurrentUserIdMethodArgumentResolver extends AbstractAnnotationMethodArgumentResolver<CurrentUserId> {

    @Setter
    private UserLoginService userLoginService;

    @Setter
    private String tokenParamName = "Authorization";

    @Setter
    private String tokenPrefix = "Mhn_";

    public CurrentUserIdMethodArgumentResolver(UserLoginService userLoginService) {
        super(CurrentUserId.class);
        this.userLoginService = userLoginService;
    }

    @Override
    protected Object doResolveArgument(HttpServletRequest request, HttpServletResponse response, CurrentUserId annotation, MethodParameter parameter) throws Exception {
        String token = request.getHeader(tokenParamName);
        if (StringUtils.isBlack(token)) {
            token = CookieUtils.getCookieValue(request, tokenParamName);
        }
        if (StringUtils.isBlack(token) || !token.startsWith(this.tokenPrefix)) {
            if (annotation.required()) {
                throw new ServiceException(MessageCode.NOT_LOGIN);
            }
            return null;
        }
        token = token.substring(tokenPrefix.length());
        UserLogin userLogin = userLoginService.getByToken(token);
        if (userLogin != null && (userLogin.getExpireTime() == null || userLogin.getExpireTime().after(new Date()))) {
            return userLogin.getUserId();
        }
        if (annotation.required()) {
            log.warn("token {} not found", token);
            throw new ServiceException(MessageCode.NOT_LOGIN);
        }
        return null;
    }
}

3.9 自定义返回值处理器

抽象类

public abstract class AbstractAnnotationMethodReturnValueHandler<T extends Annotation> implements HandlerMethodReturnValueHandler {

    private Class<T> cls;

    public AbstractAnnotationMethodReturnValueHandler(Class<T> cls) {
        this.cls = cls;
    }

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return returnType.hasMethodAnnotation(cls);
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
        doHandleReturnValue(returnValue, returnType, mavContainer, request, response);
    }

    protected abstract void doHandleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, HttpServletRequest request, HttpServletResponse response);
}

实现ResponseBody类似的功能

public class JsonReturnValueHandle extends AbstractAnnotationMethodReturnValueHandler<JsonResult>{


    public JsonReturnValueHandle() {
        super(JsonResult.class);
    }

    @Override
    protected void doHandleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, HttpServletRequest request, HttpServletResponse response) {
        String json = JsonUtils.objectToJson(returnValue);
        mavContainer.setRequestHandled(true);
        try {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(json);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

四. 总结

HandlerAdapter就是一个处理Handler的组件。我们最常用的就是RequestMappingHandlerAdapter。

由于springmvc的灵活性,因此这个组件的设计也很复杂。但是总体的核心思路是一致的,主要分为几下几步:

  1. 对于参数来源的处理
  2. 如何解析参数
  3. 如何调用业务方法
  4. 如何处理返回值,生成ModelAndView

其中参数的来源主要有以下几个:

  1. request中的参数,包括url中的参数,post请求体的参数,请求头的值
  2. cookie中的参数
  3. session中的参数
  4. FlashMap中的参数,用于重定向
  5. SessionAttributes传递的参数
  6. @ModelAttribute方法设置的参数

对于解析参数,则用到了一系列的参数解析器HandlerMethodArgumentResolver

调用业务方法,则是使用的反射技术

处理返回值,用到了HandlerMethodReturnValueHandler

springmvc的强大之处就在于很灵活,对于这其中的任何一阶段,我们都可以介入,去实现一些拓展。

也可以利用springmvc提供给我们的注解,方便快速的开发。


标题:04-HandlerAdapter
作者:mahaonan
地址:https://mahaonan.fun/articles/2022/07/18/1658147016780.html