Spring MVC源码分析 - 请求和参数

从最原始的需求说起:
如果是我们自己设计这样一个Web框架,应该考虑什么问题?
如果是我,我觉得最少应该解决如下的问题:
1、我们只配置了一个DispatcherServlet,所有被这个Servlet拦截到的请求,都会交给Spring MVC处理,
那么第一步就是要把请求映射到具体Controller的方法去处理。
2、找到对应的处理方法之后,是怎么样把请求端的请求转化成服务识别的参数?

本文将从各个疑问点出发,力求以简单的方式,分析Spring MVC在处理请求和参数分析两个方面做出扼要的叙述。

  • 第一个疑问

可以细化为如下几个部分:
1、Spring MVC怎么知道我有哪些Controller?
2、Spring MVC怎样处理我提供的Controller?
3、Spring MVC是怎样将请求路径和具体的Controller映射起来的?

*从Spring3.1之开始,SpringMVC提供了「 <mvc:annotation-driven/>」来标注,表示启用注解驱动。
我们不需要继承Spring的接口和类,只需要添加注解提供映射路径,就可以将客户端的请求映射到类和方法上。*

如果想知道这个注解做了些什么事情,可以查看它对应的解析器:「 AnnotationDrivenBeanDefinitionParser」。抽出来主要关注的两行:

RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
...
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);

这两行,分别加载了「RequestMappingHandlerMapping」和「RequestMappingHandlerAdapter」两个类。

SpringMVC加载过程中,有一步为:
initHandlerMappings(context);
它是在处理请求时,直接获取已经加载完毕的「HandlerMapping」对象,
此处加载的「RequestMappingHandlerMapping」对象,也是其中一个。

「RequestMappingHandlerMapping」主要是为带有Controller注解的类解析类级别和方法级别的「@RequestMapping」注解配置。
它是「InitializingBean」的实现类,中间经过了实现和几次抽象继承。它主要是使用父类的afterPropertiesSet方法来初始化。

//方法路径:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping<T>.initHandlerMethods
/** * Scan beans in the ApplicationContext, detect and register handler methods. * @see #isHandler(Class) * @see #getMappingForMethod(Method, Class) * @see #handlerMethodsInitialized(Map) */
    protected void initHandlerMethods() {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for request mappings in application context: " + getApplicationContext());
        }

        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                getApplicationContext().getBeanNamesForType(Object.class));

        for (String beanName : beanNames) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) &&
                    isHandler(getApplicationContext().getType(beanName))){
                detectHandlerMethods(beanName);
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }

获取到上下文中已经加载的类对象,这里是指所有的对象。可以很清楚地看到它后面的处理逻辑,遍历所有的bean对象,查看是不是符合
isHandler方法过滤,如果符合,就查找beanName对应的Bean中的提供Spring MVC服务方法。
这个isHandler在「RequestMappingHandlerMapping」中实现:

/** 查看是不是有Controller或者RequestMapping注解 */
    @Override
    protected boolean isHandler(Class<?> beanType) {
        return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
                (AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
    }

而后会将类对象传递到detectHandlerMethods方法中,去方法和创建路径的映射,保存在对象:
final Map

/** * Look for handler methods in a handler. * @param handler the bean name of a handler or a handler instance */
    protected void detectHandlerMethods(final Object handler) {
        Class<?> handlerType =
                (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass());

        // Avoid repeated calls to getMappingForMethod which would rebuild RequestMappingInfo instances
        final Map<Method, T> mappings = new IdentityHashMap<Method, T>();
        final Class<?> userType = ClassUtils.getUserClass(handlerType);

        Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
            @Override
            public boolean matches(Method method) {
                T mapping = getMappingForMethod(method, userType);
                if (mapping != null) {
                    mappings.put(method, mapping);
                    return true;
                }
                else {
                    return false;
                }
            }
        });

        for (Method method : methods) {
            registerHandlerMethod(handler, method, mappings.get(method));
        }
    }

「RequestMappingHandlerMapping」同样实现了一个getMappingForMethod方法,用来创建「RequestMappingInfo」对象,这个对象将类的请求路径、方法的请求路径、请求类型、请求支持的数据格式封装在一起。
最后,就是调用registerHandlerMethod方法。将Controller对象、方法对象、请求路径,注册在一起。
这个方法定义在「AbstractHandlerMethodMapping」之中。

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
        HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
        HandlerMethod oldHandlerMethod = this.handlerMethods.get(mapping);
        if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) {
            throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + newHandlerMethod.getBean() +
                    "' bean method \n" + newHandlerMethod + "\nto " + mapping + ": There is already '" +
                    oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped.");
        }

        this.handlerMethods.put(mapping, newHandlerMethod);
        if (logger.isInfoEnabled()) {
            logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod);
        }

        Set<String> patterns = getMappingPathPatterns(mapping);
        for (String pattern : patterns) {
            if (!getPathMatcher().isPattern(pattern)) {
                this.urlMap.add(pattern, mapping);
            }
        }

        if (this.namingStrategy != null) {
            String name = this.namingStrategy.getName(newHandlerMethod, mapping);
            updateNameMap(name, newHandlerMethod);
        }
    }

简单来说,这里做了两件事:
将RequestMappingInfo对象和处理方法映射成Map对象键值对;
将请求路径和RequestMappingInfo对象映射城Map键值对;

所以,这里会将请求路径相同的方法给覆盖了,一个请求路径最多一个处理方法。

所以从上面的描述,可以得知:
「RequestMappingHandlerMapping」,是解析Controller和「RequestMapping」注解的,
它将Controller和方法的请求路径和处理方法,做了跳转映射,首先从请求路径映射到「RequestMappingInfo」,然后可以从「RequestMappingInfo」映射到处理方法。
追根溯源,可以找到「HandlerMapping」接口的说明,「HandlerMapping」是SpringMVC提供的一个基础抽象类,它规范了请求和处理对象之间的转化。它只提供一个名为getHandler的方法。

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

从Servlet请求中获取到处理的Handler。
到这里,应该可以解答之前的第一个问题了。

  • 第二个疑问
    找到对应的处理方法之后,是怎么样把请求端的请求转化成服务识别的参数?
    同样,可以分解为如下的几个问题:
    1、SpringMVC使用什么进行参数解析?
    2、SpringMVC的是如何处理「HttpServletRequest」对象的?
为什么特意提到了「HttpServletRequest」,在DispatcherServlet中,能接收到的就只是「HttpServletRequest」请求。
SpringMVC如果需要解析参数,一定得从这个对象里面去查找。HTTP请求的本质其实还是字符串,当进入SpringMVC之后,这个参数解析的过程其实没有暴露给开发者,那就意味着SpringMVC需要自己提供很多的转换功能。

看看「RequestMappingHandlerAdapter」,其实从它的类说明就可以了解一些:

/**
 * An {@link AbstractHandlerMethodAdapter} that supports {@link HandlerMethod}s
 * with the signature -- method argument and return types, defined in
 * {@code @RequestMapping}.
 *
 * <p>Support for custom argument and return value types can be added via
 * {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers}.
 * Or alternatively to re-configure all argument and return value types use
 * {@link #setArgumentResolvers} and {@link #setReturnValueHandlers(List)}.
 *
 * @author Rossen Stoyanchev
 * @since 3.1
 * @see HandlerMethodArgumentResolver
 * @see HandlerMethodReturnValueHandler
 */

它是处理「RequestMapping」所解析HandlerMethod中参数和返回值。
它是「AbstractHandlerMethodAdapter」类的一个子类。它还允许设置customArgumentResolvers类注入用户自定义的参数解析器,
returnValueHandlers注入用户自定义的返回值解析器。
同样,从方法签名上,可以知道,这个类也是会在容器启动的时候进行初始化的,

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

首先,会去加载3.2版本才出现的Controller增强器,它提供参数绑定,异常等方面的辅助支持,暂不做分析。
接下来出现三个if判断,分别初始化的默认的参数解析器,initBinder参数解析器,返回值处理器。
值得一提的是,在SpringMVC处理列表对象的时候,一般都会提供一个包装的对象,然后在这个包装的对象里面进行处理。
比如此处的「HandlerMethodArgumentResolverComposite」就把解析器给封装了起来,然后把具体的逻辑写在里面,
更让人想不到的是,它本身也是「HandlerMethodArgumentResolver」的实现类。

重点看第一个,「HandlerMethodArgumentResolver」接口,它是一个将参数从request中转化为方法参数的规范。
将这个接口声明了所有参数解析器的实现方法:

boolean supportsParameter(MethodParameter parameter);

该解析器是否支持此种类型的参数,MethodParameter可以由之前说的「HandlerMethod」对象获取。

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

从解析方法的单个参数。
「ModelAndViewContainer」记录模型对象和视图对象之间的对应关系,暂时不分析这个。
「NativeWebRequest」是对最原始的请求(例如:「HttpServletRequest」、「javax.portlet.ActionRequest」等等)的包装类。
SpringMVC将底层请求对象进行了包装,这样就能尽可能地屏蔽底层请求的差异性,从而使得更具有通用性,最常见的还是
「HttpServletRequest」。

从这两个方法,其实可以猜想一下,Spring MVC处理参数转化的方式:
通过supportsParameter方法决定由哪个「HandlerMethodArgumentResolver」实现类来处理当前参数;
通过resolveArgument方法来做具体参数转换逻辑;

要验证这个想法,看看具体一个实现:「RequestResponseBodyMethodProcessor」

/**
 * Resolves method arguments annotated with {@code @RequestBody} and handles
 * return values from methods annotated with {@code @ResponseBody} by reading
 * and writing to the body of the request or response with an
 * {@link HttpMessageConverter}.
 *
 * <p>An {@code @RequestBody} method argument is also validated if it is
 * annotated with {@code @javax.validation.Valid}. In case of validation
 * failure, {@link MethodArgumentNotValidException} is raised and results
 * in a 400 response status code if {@link DefaultHandlerExceptionResolver}
 * is configured.
 *
 * @author Arjen Poutsma
 * @author Rossen Stoyanchev
 * @since 3.1
 */

从类注释中可以得知,这个解析器,不单能解析参数被标注为@RequestBody的参数,还可以处理返回值添加@ResponseBody注解的方法,还可以处理添加了@Validate的参数校验。
supportsParameter方法很简单,主要看看resolveArgument方法。
其中有一句:

Object argument = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());

这是一个很重要的方法,它将「NativeWebRequest」对象封装的「HttpServletRequest」重新解放出来,再次构造成SpringMVC的消息对象「HttpInputMessage」,再获取到InputStream对象进行参数获取:

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

        final HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        HttpInputMessage inputMessage = new ServletServerHttpRequest(servletRequest);

        InputStream inputStream = inputMessage.getBody();
        if (inputStream == null) {
            return handleEmptyBody(methodParam);
        }
        else if (inputStream.markSupported()) {
            inputStream.mark(1);
            if (inputStream.read() == -1) {
                return handleEmptyBody(methodParam);
            }
            inputStream.reset();
        }
        else {
            final PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream);
            int b = pushbackInputStream.read();
            if (b == -1) {
                return handleEmptyBody(methodParam);
            }
            else {
                pushbackInputStream.unread(b);
            }
            inputMessage = new ServletServerHttpRequest(servletRequest) {
                @Override
                public InputStream getBody() throws IOException {
                    // Form POST should not get here
                    return pushbackInputStream;
                }
            };
        }

        return super.readWithMessageConverters(inputMessage, methodParam, paramType);
    }

SpringMVC内部对 「HttpServletRequest」 进行了封装,这使得实现「ServerHttpRequest」接口的「ServletServerHttpRequest」类,变成了结合了原生HttpServletRequest的功能和Spring MVC定义的操作方法,比如:
InetSocketAddress getLocalAddress();等等

private final HttpServletRequest servletRequest;

这种设计方式,将各种不同类型的请求进行了很好的屏蔽。
「RequestMappingHandlerAdapter」 将会在请求第一次到达「DispatcherServlet 」时在 initHandlerAdapters(context){…}被加载。
最后会调用到invokeHandleMethod方法,完成对方法的调用。

Spring MVC使用「HandlerAdapter」进行参数解析,每个「HandlerAdapter」对象都有一个支持的参数解析器,每个解析器都有着对应的使用场景和解析方式,在解析完参数之后,「HandlerAdapter」的handle方法将触发,并返回ModelAndView对象。

其实「DispatcherServlet 」的doDispatch方法,可以理解为分发给不同的「HandlerMapping 」对象(例如:「RequestMappingHandlerMapping 」)所持有的Handler引用。并且由此找到对应的「HandlerAdapter」对象(例如:「RequestMappingHandlerAdapter」)。

如有纰漏,欢迎指正

-EOF-

    原文作者:Spring MVC
    原文地址: https://blog.csdn.net/sunwei_pyw/article/details/78022644
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞