SpringMVC源码解读 --- 处理器适配器 - 2 源码解读前置知识HandlerMethodArgumentResolver与HandlerMethodReturnV接口的使用及其子类源码梳理

       在梳理处理器适配器(HandlerAdapter)开始前,我们先梳理其中要用到的一些内容,以便在梳理HandlerAdapter的时候能更关注这个接口本身的逻辑处理部分。同时这样的话,也能将独立的知识点提取出来,更方便理解。

      一、HandlerMethodArgumentResolver接口的使用与SpringMVC对其的实现

       这里简单用一个demo来说明。常见的就是例如我们使用JWT进行token验证,然后对token的内容进行解码,将token解析的内容再赋值到一个对象中,供我们去使用一些信息,为了demo较一的功能描叙,我们就在这里简单使用json,不进行加密的内容。

  xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.2.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    <context:component-scan base-package="web" />
     <!--开启注解 -->
    <!--<mvc:default-servlet-handler/>-->
    <mvc:annotation-driven/>

    <mvc:annotation-driven>
        <mvc:argument-resolvers>
            <bean class="web.config.HeaderHandlerMethodArgumentResolver"/>
        </mvc:argument-resolvers>
    </mvc:annotation-driven>
</bean>

controller类:

@RestController
public class SpringController{
    @RequestMapping(value = "parameterResolver",method = RequestMethod.GET)
    public String parameterResolver(@HeaderAnnotation StudentVo studentVo)
    {
        return "parameterResolver";
    }
}

 自定义的HandlerMethodArgumentResolver

public class HeaderHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver{
    public boolean supportsParameter(MethodParameter methodParameter) {
        if (methodParameter.hasParameterAnnotation(HeaderAnnotation.class))
        {
            return true;
        }
        return false;
    }

    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {

        String studentVoString = nativeWebRequest.getHeader("token-student");
        StudentVo studentVo = JSON.parseObject(studentVoString, StudentVo.class);
        return studentVo;
    }
}

请求:

在请求头中加"token-student"就可以了,然后我们就能在方法中获取到赋值后的StudentVo对象了:

   1、HandlerMethodArgumentResolver接口的分析

     HandlerMethodArgumentResolver  接口本身

public interface HandlerMethodArgumentResolver {
    boolean supportsParameter(MethodParameter var1);

    Object resolveArgument(MethodParameter var1, ModelAndViewContainer var2, NativeWebRequest var3, WebDataBinderFactory var4) throws Exception;
}

      然后是我们自定义的:

public class HeaderHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver{
    public boolean supportsParameter(MethodParameter methodParameter) {
        if (methodParameter.hasParameterAnnotation(HeaderAnnotation.class))
        {
            return true;
        }
        return false;
    }

    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {

        String studentVoString = nativeWebRequest.getHeader("token-student");
        StudentVo studentVo = JSON.parseObject(studentVoString, StudentVo.class);
        return studentVo;
    }
}

   1、MethodParameter类

   这个MethodParameter在前面文章关于DispatcherServletinit方法有提到,其就是用来描叙对应方法的参数的:

public class MethodParameter {
    private final Method method;
    private final Constructor<?> constructor;
    private final int parameterIndex;
    private int nestingLevel;
    Map<Integer, Integer> typeIndexesPerLevel;
    private volatile Class<?> containingClass;
    private volatile Class<?> parameterType;
    private volatile Type genericParameterType;
    private volatile Annotation[] parameterAnnotations;
    private volatile ParameterNameDiscoverer parameterNameDiscoverer;
    private volatile String parameterName;

这里的几个参数parameterIndex表示当前参数是在第几个位置(方法能有多个参数),parameterType表示参数的类型,parameterAnnotations表示参数前面有加什么注解。

    2、supportsParameter方法

   所以我们自定义的该方法就是用来判断在该参数前面有没有加@HeaderAnnotation注解,如果有加HeaderAnnotation注解,就返回true表示,表示前面这个参数能用这个HandlerMethodArgumentResolver去解析。

  3、resolveArgument方法

   下面就是用这个方法去返回转换为目标参数的类型与值:

public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
    String studentVoString = nativeWebRequest.getHeader("token-student");
    StudentVo studentVo = JSON.parseObject(studentVoString, StudentVo.class);
    return studentVo;
}

     可以看到这个方法有入参methodParameter、modelAndViewContainer、nativeWebRequest、webDataBinderFactory,就表示你能有这些入参,通过这些入参去完成该参数的赋值。

   2、SpringMVC对于这个接口是怎样实现的

   现在我们来看下SpringMVC一般是怎样处理HandlerMethodArgumentResolver接口的。

       1、几个主要的抽象实现

   1、AbstractNamedValueMethodArgumentResolver

public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
   @Nullable
   private final ConfigurableBeanFactory configurableBeanFactory;
   @Nullable
   private final BeanExpressionContext expressionContext;
   private final Map<MethodParameter, NamedValueInfo> namedValueInfoCache = new ConcurrentHashMap<>(256);
     .........

   @Override
   @Nullable
   public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
         NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
      NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
      MethodParameter nestedParameter = parameter.nestedIfOptional();

      Object resolvedName = resolveStringValue(namedValueInfo.name);
        ........
      Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
          ............

      if (binderFactory != null) {
         WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
         try {
            arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
         }
            ...........

      handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
      return arg;
   }

   private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
      NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
      if (namedValueInfo == null) {
         namedValueInfo = createNamedValueInfo(parameter);
         namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
         this.namedValueInfoCache.put(parameter, namedValueInfo);
      }
      return namedValueInfo;
   }
   protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
      @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
   }
   ..........
   @Nullable
   private Object resolveStringValue(String value) {
      if (this.configurableBeanFactory == null) {
         return value;
      }
      String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
      BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
      if (exprResolver == null || this.expressionContext == null) {
         return value;
      }
      return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
   }

   @Nullable
   protected abstract Object resolveName(String name, MethodParameter parameter, NativeWebRequest request)
         throws Exception;

      ...........
   protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
         @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
   }

   protected static class NamedValueInfo {

      private final String name;

      private final boolean required;
        ...........
   }
}

   可以看到这里其对于resolveArgument方法有初步实现,其getNamedValueInfo方法就是就是将你加的注解里面的内容提取出来设置到NamedValueInfo中,例如@ReqeustBody、@RequestParam使用时,设置的value="xxx",required = true。

    获取到name后再调用resolveName方法去解析获取到对应的参数值(下文会用springMVC是怎样解析@PathVariable去具体看值的设置),然后如果这个值是需要进行类型转换的话,就再调用binder.convertIfNecessary进行类型转换。

     2、AbstractMessageConverterMethodArgumentResolver

public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {

   private static final Set<HttpMethod> SUPPORTED_METHODS =
         EnumSet.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH);

   protected final List<HttpMessageConverter<?>> messageConverters;
   protected final List<MediaType> allSupportedMediaTypes;
   private final RequestResponseBodyAdviceChain advice;

   public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters) {
      this(converters, null);
   }

   public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters,
         @Nullable List<Object> requestResponseBodyAdvice) {
      this.messageConverters = converters;
      this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters);
      this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);
   }
     .........
   @Nullable
   protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
         Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

      HttpInputMessage inputMessage = createInputMessage(webRequest);
      return readWithMessageConverters(inputMessage, parameter, paramType);
   }

   @Nullable
   protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
         Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

         ...............
      return body;
   }
    ...........
   private static class EmptyBodyCheckingHttpInputMessage implements HttpInputMessage {
      private final HttpHeaders headers;
      @Nullable
      private final InputStream body;

      public EmptyBodyCheckingHttpInputMessage(HttpInputMessage inputMessage) throws IOException {
         this.headers = inputMessage.getHeaders();
         InputStream inputStream = inputMessage.getBody();
           ...........
      }
      @Override
      public HttpHeaders getHeaders() {
         return this.headers;
      }
      @Override
      public InputStream getBody() {
         return (this.body != null ? this.body : StreamUtils.emptyInput());
      }

      public boolean hasBody() {
         return (this.body != null);
      }
   }

  这个抽象类并没有去简单实现resolveArgument方法,但这里是有实现readWithMessageConverters方法

protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
      Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

   HttpInputMessage inputMessage = createInputMessage(webRequest);
   return readWithMessageConverters(inputMessage, parameter, paramType);
}

    这个方法就是通过reqeust去创建HttpInputMessage,我们看下这个接口:

public interface HttpInputMessage extends HttpMessage {
   InputStream getBody() throws IOException;
}
public interface HttpMessage {
   HttpHeaders getHeaders();
}

      这个接口,主要是两个方法:getHeaders、getBody,所以HttpInputMessage就是通过reqeust,获取request的请求体,以及请求头,然后应该就能通过请求头去听取请求体中的内容了。其再调用readWithMessageConverters方法:

@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
      Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
   MediaType contentType;
   boolean noContentType = false;
   try {
      contentType = inputMessage.getHeaders().getContentType();
   }
    ..........
   Class<?> contextClass = parameter.getContainingClass();
   Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
   if (targetClass == null) {
      ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
      targetClass = (Class<T>) resolvableType.resolve();
   }
    ........
   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);
         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;
         }
      }
   }
    ...........
   return body;
}

     这里主要有两个点:首先是获取contentType,然后再遍历HttpMessageConverter,再去判断HttpMessageConverter的子类GenericHttpMessageConverter,再调用其的canRead传入contentType(所以这里如果在头部的content-type不同,就是使用不同的GenericHttpMessageConverter),看是不是使用当前GenericHttpMessageConverter去读取body中的内容,如果是就调用其read方法去读取(与HandlerMethodArgumentResolver的使用类似)body。

    这里还有一个点,在调用genericConverter方法之前与之后会调用getAdvice().beforeBodyRead:

@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter,
      Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {

   for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
      if (advice.supports(parameter, targetType, converterType)) {
         request = advice.beforeBodyRead(request, parameter, targetType, converterType);
      }
   }
   return request;
}
public interface RequestBodyAdvice {

   boolean supports(MethodParameter methodParameter, Type targetType,
         Class<? extends HttpMessageConverter<?>> converterType);
   HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
         Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException;
   Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
         Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
   @Nullable
   Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,
         Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
}

     接口的作用通过我们以前在梳理讲通过BeanFactory获取bean的时候有讲过,就是看这个接口的入参、出参、与调用时机就差不多能猜到其是什么作用,这里就不再具体分析了,可以看下其他博文。

    2、这两种抽象实现的差别

    通过前面的分析以及源码中的注解来理解:

      AbstractNamedValueMethodArgumentResolver体现的请求的方法参数的内容是需要在Request parameters, request headers, and path variables are examples of named(请求头、parameters、请求路径中)这些地方获取的值。而AbstractMessageConverterMethodArgumentResolver 是表示方法参数的值是需要从 from the body of a request(请求体中)。

    3、对应抽象类的的几个具体实现

      1、PathVariable注解(PathVariableMethodArgumentResolver)

     PathVariable注解是用PathVariableMethodArgumentResolver类去处理,,其继承AbstractNamedValueMethodArgumentResolver类,同时这个类还有继承其他的类:

             

  这里我们简单来看下主要的两个方法:

      supportsParameter方法

@Override
public boolean supportsParameter(MethodParameter parameter) {
   if (!parameter.hasParameterAnnotation(PathVariable.class)) {
      return false;
   }
   if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
      PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
      return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
   }
   return true;
}

可以看到这个方法与我们前面写的类似(我就是模仿这里对应写的自定义类),主要就是判断参数前面有没有PathVariable注解。

   resolveArgument方法

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

   NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
   MethodParameter nestedParameter = parameter.nestedIfOptional();

   Object resolvedName = resolveStringValue(namedValueInfo.name);
            .........
   Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
       ..........
   if (binderFactory != null) {
      WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
      try {
         arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
      }
       ........
   handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

   return arg;
}
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
   Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
         HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
   return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}

   可以看到这里就是通过MethodParameter 去构建这个注解的描叙:

               

 然后是调用resolveName方法去获取参数的具体值,其在PathVariableMethodArgumentResolver的实现就是去request中获取对应的value:

protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
   Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
         HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
   return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}

(spring在前面进行地址解析的时候就会将该值以URI_TEMPLATE_VARIABLES_ATTRIBUTE为key设置到request中)

然后如果有需要的话再通过convertIfNecessary方法去解析类型转换。

   同时SpringMVC对该接口还有很多其他实现,这里就不再一一梳理了

              

  2、RequestBody注解(RequestResponseBodyMethodProcessor) 

      

           supportsParameter方法

@Override
public boolean supportsParameter(MethodParameter parameter) {
   return parameter.hasParameterAnnotation(RequestBody.class);
}

    resolveArgument方法

@Override
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);
       .........
   }
   return adaptArgumentIfNecessary(arg, parameter);
}

    这里调用了readWithMessageConverters去获取参数值

@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
      Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

   HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
   ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
   Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
   if (arg == null) {
      if (checkRequired(parameter)) {
         throw new HttpMessageNotReadableException("Required request body is missing: " +
               parameter.getExecutable().toGenericString());
      }
   }
   return arg;
}

   然后再调用了父类的readWithMessageConverters方法。

     3、GenericHttpMessageConverter接口的对应实现

   我们再来补充在AbstractMessageConverterMethodArgumentResolver中readWithMessageConverters方法调用的GenericHttpMessageConverter接口的实现。

      

   可以看到这里的关键词:Jackson、ResourceRegion,Jackson就是与Json相关的,例如我们的@RequestBody就是将请求体中的json格式内容转换为对应的java对象(context-type是application/json),然后ResourceRegion就是表示的Resource资源:

public class ResourceRegion {
   private final Resource resource;
   private final long position;
   private final long count;
    .........
}

                     

我们以RequestBody注解为例,看其获取的是GenericHttpMessageConverter的哪个子类:

@RequestMapping(value = "parameterBody",method = RequestMethod.POST)
public String parameterBody(@RequestBody StudentVo studentVo)
{
    return "parameterResolver";
}

再将{"name":"Fev","age":25}放到请求体中:

  

我们可以看到其canRead方法:

   

public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
   if (!canRead(mediaType)) {
      return false;
   }
   JavaType javaType = getJavaType(type, contextClass);
   AtomicReference<Throwable> causeRef = new AtomicReference<>();
   if (this.objectMapper.canDeserialize(javaType, causeRef)) {
      return true;
   }
   return false;
}

首先是判断mediaType,当前请求头是不是用该类解析:

public AbstractJsonHttpMessageConverter() {
   super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
   setDefaultCharset(DEFAULT_CHARSET);
}

如果是就通过objectMapper.canDeserialize看能不能转换,如果能再调用read读取body

public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
    JavaType javaType = this.getJavaType(type, contextClass);
    return this.readJavaType(javaType, inputMessage);
}
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) {
    ........
      return this.objectMapper.readValue(inputMessage.getBody(), javaType);
     .......
}

              

 

    二、HandlerMethodReturnValueHandler接口的使用与SpringMVC对其的实现

   1、这个与前面HandlerMethodArgumentResolver一样,但需要改下xml文件以及自定义一个HandlerMethodReturnValueHandler的实现类。

    xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.2.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    <context:component-scan base-package="web" />
    <mvc:annotation-driven/>
    <mvc:annotation-driven>
        <mvc:argument-resolvers>
            <bean class="web.config.HeaderHandlerMethodArgumentResolver"/>
        </mvc:argument-resolvers>
        <mvc:return-value-handlers>
            <bean></bean>
        </mvc:return-value-handlers>
    </mvc:annotation-driven>
</beans>

controller:

@Controller
public class SpringController{
   @RequestMapping(value = "parameterResponse",method = RequestMethod.POST)
   public StudentVo parameterResponse()
   {
       StudentVo studentVo = new StudentVo();
       studentVo.setAge(25);
       studentVo.setName("Fev");
       return studentVo;
   }
}

自定义HandlerMethodResultValueHandler

public class HeaderHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
    public boolean supportsReturnType(MethodParameter methodParameter) {
        if (methodParameter.getParameterType().isAssignableFrom(StudentVo.class))
        {
            return true;
        }
        return false;
    }

    public void handleReturnValue(Object o, MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest) throws Exception {
        if (StudentVo.class.isAssignableFrom(o.getClass()))
        {
            StudentVo studentVo = (StudentVo)o;
            String jsonObject = JSON.toJSONString(studentVo);
            ResponseFacade responseFacade = nativeWebRequest.getNativeResponse(ResponseFacade.class);
            responseFacade.setHeader("token-student",jsonObject);
            //表示不需要再进行视图解析直接返回,为什么要设置这个到关于处理器适配器的源码梳理就明白了
            modelAndViewContainer.setRequestHandled(Boolean.TRUE);
        }
    }
}

   可以看到这个demo就是对前面的HandlerMethodArgumentResolver的逆化操作,然后请求

                        

可以看到在返回的请求头中就有了token-student。

 1、HandlerMethodReturnValueHandler接口的分析  

public interface HandlerMethodReturnValueHandler {

   boolean supportsReturnType(MethodParameter returnType);

   void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
         ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}

  然后这里的入参MethodParameter 这些在前面讲HandlerMethodArgumentResolver有简答分析,这里就不再赘叙了。

2、SpringMVC对于这个接口是怎样实现的

  1、AbstractMessageConverterMethodProcessor

  关于HandlerMethodReturnValueHandler的抽象类,SpringMVC就这个AbstractMessageConverterMethodProcessor,其他的都是直接去实现的HandlerMethodReturnValueHandler接口,然后这个AbstractMessageConverterMethodProcessor接口,在前面讲HandlerMethodArgumentResolver的时候有梳理过,不过是梳理的HandlerMethodArgumentResolver部分,现在来梳理HandlerMethodReturnValueHandler部分

   

 前面是read,现在是write:

protected <T> void writeWithMessageConverters(T value, MethodParameter returnType, NativeWebRequest webRequest)
      throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

   ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
   ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
   writeWithMessageConverters(value, returnType, inputMessage, outputMessage);
}

然后这个writeWithMessageConverters方法,与前面readWithMessageConverters类似,就不再具体展开,这里再来看下其使用的GenericHttpMessageConverter接口。

public interface GenericHttpMessageConverter<T> extends HttpMessageConverter<T> {

   boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType);

   T read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
         throws IOException, HttpMessageNotReadableException;

   boolean canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType);

   void write(T t, @Nullable Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
         throws IOException, HttpMessageNotWritableException;
}

可以看到这个接口有四个方法发布定义读与写。然后在这里,这个writeWithMessageConverters方法就是使用的canWrite、write。

3、对应抽象类的的具体实现

  @ResponseBody(RequestResponseBodyMethodProcessor)

  这个RequestResponseBodyMethodProcessor类我们前面同样梳理的是其的read,这个类就是用来处理@RequestBody、@ResponseBody的,一个入参、一个出参。

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
     ..............
   @Override
   public boolean supportsParameter(MethodParameter parameter) {
      return parameter.hasParameterAnnotation(RequestBody.class);
   }

   @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);
      writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
   }
    ........
}

  这里的一些细节与前面类似,就不再一一分析了。

 2、ModelAndView(ModelAndViewMethodReturnValueHandler)

   我们再来看一个直接实现HandlerMethodReturnValueHandler接口的类,这个类就是用来处理返回类是ModelAndView的:

public class ModelAndViewMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
    ...........
   @Override
   public boolean supportsReturnType(MethodParameter returnType) {
      return ModelAndView.class.isAssignableFrom(returnType.getParameterType());
   }

   @Override
   public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
         ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
      if (returnValue == null) {
         mavContainer.setRequestHandled(true);
         return;
      }
      ModelAndView mav = (ModelAndView) returnValue;
      if (mav.isReference()) {
         String viewName = mav.getViewName();
         mavContainer.setViewName(viewName);
         if (viewName != null && isRedirectViewName(viewName)) {
            mavContainer.setRedirectModelScenario(true);
         }
      }
      else {
         View view = mav.getView();
         mavContainer.setView(view);
         if (view instanceof SmartView) {
            if (((SmartView) view).isRedirectView()) {
               mavContainer.setRedirectModelScenario(true);
            }
         }
      }
      mavContainer.setStatus(mav.getStatus());
      mavContainer.addAllAttributes(mav.getModel());
   }
    .......
   protected boolean isRedirectViewName(String viewName) {
      if (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName)) {
         return true;
      }
      return viewName.startsWith("redirect:");
   }
}
public boolean isReference() {
   return (this.view instanceof String);
}

  这里handleReturnValue主要是三个操作,判断当前ModelAndView是不是直接返回的视图view,还是只是一个viewName,再分别设置到mavContainer中,然后判断需不需要进行重定向,需要的话就进行对应设置。

    再补充该接口的实现类:

                  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于配置文件的Spring MVC应用中,实现登录功能需要编写以下源代码: 1. LoginController.java: @Controller public class LoginController { @RequestMapping(value = "/login", method = RequestMethod.GET) public String showLoginForm() { return "login"; } @RequestMapping(value = "/login", method = RequestMethod.POST) public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model) { if (username.equals("admin") && password.equals("123456")) { model.addAttribute("username", username); return "success"; } else { model.addAttribute("error", "用户名或密码错误"); return "login"; } } } 2. login.jsp: <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>登录页面</title> </head> <body> <h1>登录</h1> <form method="POST" action="/login"> <label for="username">用户名:</label> <input type="text" id="username" name="username"> <label for="password">密码:</label> <input type="password" id="password" name="password"> <input type="submit" value="登录"> </form> <c:if test="${not empty error}"> <p>${error}</p> </c:if> </body> </html> 3. success.jsp: <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>登录成功</title> </head> <body> <h1>登录成功</h1> <p>欢迎,${username}!</p> </body> </html> 以上代码实现了一个简单的登录功能。用户输入用户名和密码后,点击登录按钮提交表单,后台LoginController中的login方法根据用户名和密码进行验证。如果验证通过,将用户名存入Model中,并返回success视图,显示登录成功页面,页面中会显示欢迎用户名。如果验证不通过,将错误信息存入Model中,并返回login视图,显示登录页面,并提示用户名或密码错误。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值