Spring源码分析(四)SpringMVC初始化

前言

Spring MVC 是一个模型 - 视图 - 控制器(MVC)的Web框架。在具体使用时,我们通过Controller注解标明类是一个控制器,通过RequestMapping指明了请求地址。当我们的请求到来时,Spring就定位到类的方法。这一切看起来都这么完美,但是Spring在底下到底是怎么做的呢?
本章是解析SpringMVC的第一部分,先来看它初始化的时候做了哪些工作。

1、请求处理流程

在分析之前先来看一个SpringMVC请求处理最简单的流程图。


Spring源码分析(四)SpringMVC初始化_第1张图片
SpringMVC简易流程图

2、初始化

在完成所有Bean的实例化后,Spring又加载了一系列策略方法,用于SpringMVC。

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

我们重点关注两个,initHandlerMappings和initHandlerAdapters。

2.1 initHandlerMappings

初始化handlerMappings,就是加载处理器映射,它的加载有三种方式。

  • 配置文件具体指明

  • 配置,这种方式的话,Spring在解析标签的时候会加载两个handlerMappings,分别是RequestMappingHandlerMapping和BeanNameUrlHandlerMapping。

  • 什么都不配。如果什么都没有配置,Spring会默认加载/org/springframework/web/servlet/DispatcherServlet.properties文件,此文件里面配置了handlerMappings和handlerAdapters。

2.2 initHandlerAdapters

它的加载也有三种方式,和上面一样。或者配置,或者具体指明Adapters,或者什么也不配,走Spring默认方式。

这里笔者推荐。它在解析的时候,就已经加载了HandlerMapping和HandlerAdapter。

new RootBeanDefinition(RequestMappingHandlerMapping.class);
new RootBeanDefinition(BeanNameUrlHandlerMapping.class);
new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
new RootBeanDefinition(RequestMappingHandlerAdapter.class);
new RootBeanDefinition(HttpRequestHandlerAdapter.class);
new RootBeanDefinition(SimpleControllerHandlerAdapter.class);

并默认添加了RequestMappingHandlerAdapter适配器的消息转换器。

handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);

messageConverters默认包含7种类型。

MappingJacksonHttpMessageConverter
ByteArrayHttpMessageConverter
StringHttpMessageConverter
......

3、实例化RequestMappingHandlerMapping

上面我们看到已经加载了RequestMappingHandlerMapping处理类,有意思的是,这个类的父类实现了InitializingBean接口。我们马上就会想到,在实例化之后就会调用afterPropertiesSet方法。

3.1 、initHandlerMethods

此方法会遍历所有的Bean实例,过滤包含Controller注解和RequestMapping注解的类,然后查找类上的方法,获取方法上的URL。最后把URL和方法的映射注册到容器。

protected void initHandlerMethods() {
    //这里的beanNames就是所有的Bean
    String[] beanNames = (getApplicationContext().getBeanNamesForType(Object.class));

    for (String beanName : beanNames) {
        if (isHandler(getApplicationContext().getType(beanName))){
            detectHandlerMethods(beanName);
        }
    }
}
//根据beanName找到它的Class对象,判断类是否包含下面这个注解
protected boolean isHandler(Class beanType) {
    return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
            (AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
}

protected void detectHandlerMethods(final Object handler) {
    Class handlerType = handler.getClass());
    final Map mappings = new IdentityHashMap();
    final Class userType = ClassUtils.getUserClass(handlerType);
    
    //获取userType对象上的所有方法 返回JDK中的Method对象集合
    Set methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
        @Override
        public boolean matches(Method method) {
            //method封装成RequestMappingInfo对象
            //对象包含了url和HTTP请求的其他信息 比如params、method、headers等
            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));
    }
}

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
    //把beanName和Method对象封装成HandlerMethod对象。
    //HandlerMethod对象包含了beanName、beanFactory、Method对象和方法参数集合
    HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
    //注册映射关系
    this.handlerMethods.put(mapping, newHandlerMethod);
    Set patterns = getMappingPathPatterns(mapping);
    for (String pattern : patterns) {
        if (!getPathMatcher().isPattern(pattern)) {
            this.urlMap.add(pattern, mapping);
        }
    }
}

主要有两个缓存比较重要,handlerMethods和urlMap。
这里以indexController为例,看一下里面的数据是什么样的。

@Controller
public class IndexController {
    @RequestMapping("/index")
    public @ResponseBody String index(String id) {
        System.out.println("--------------------");
        return "Hello! My name is ACAL!";
    }
}

以上面的Controller为例,handlerMethods里面的数据如下:
{{[/index],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}=public java.lang.String com.viewscenes.netsupervisor.controller.IndexController.index(java.lang.String)}
可以看到,它是mapping和HandlerMethod对象的映射。

urlMap里面的数据如下:
{/index=[{[/index],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}]}
它是URL和mapping的映射。看到这,我们可以大胆的预估,浏览器如果请求了/index,先通过urlMap找到对应的mapping,然后通过handlerMethods会找到HandlerMethod对象。HandlerMethod对象包含了Method对象和参数列表,这样不就可以通过invoke方法调用了吗?没错,不过还少一个东西,就是参数怎么匹配的解析的问题。

4、实例化RequestMappingHandlerAdapter

我们来到RequestMappingHandlerAdapter类,惊喜的发现它也实现了InitializingBean接口,那么直接来到afterPropertiesSet方法。我们重点看两个

public void afterPropertiesSet() {
    if (this.argumentResolvers == null) {
        List resolvers = getDefaultArgumentResolvers();
    }
    if (this.returnValueHandlers == null) {
        List handlers = getDefaultReturnValueHandlers();
    }
    initControllerAdviceCache();
}

4.1、加载参数解析器

第一个方法就是加载默认的参数解析器。想象一下,在我们Controller的方法里面,可能会有很多参数,而且参数类型迥然不同,有的参数还可以加上了注解。那么,怎么把数据转换的呢?就是靠这些解析器

private List getDefaultArgumentResolvers() {
    //基于注解的参数解析
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));@RequestParam
    resolvers.add(new PathVariableMethodArgumentResolver()); --@PathVariable
    resolvers.add(new MatrixVariableMethodArgumentResolver()); --@MatrixVariable
    resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters())); --@RequestBody
    resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters())); --@RequestPart
    resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory())); --@RequestHeader
    ...未完
    
    //基于类型的参数解析
    resolvers.add(new ServletRequestMethodArgumentResolver()); --Request
    resolvers.add(new ServletResponseMethodArgumentResolver()); -- Response
    resolvers.add(new HttpEntityMethodProcessor(getMessageConverters())); --HttpEntity
    resolvers.add(new ModelMethodProcessor()); --Model
    resolvers.add(new MapMethodProcessor()); --Map
    ...未完

    // 定义的解析器
    if (getCustomArgumentResolvers() != null) {
        resolvers.addAll(getCustomArgumentResolvers());
    }
    return resolvers;
}

4.2、加载返回值类型解析器

上面说了参数解析,那么在方法返回的时候,类型也是各不相同的。它也是靠各种解析器来支持的。

private List getDefaultReturnValueHandlers() {
    
    //单用途返回值类型
    handlers.add(new ModelAndViewMethodReturnValueHandler()); -- ModelAndView
    handlers.add(new ModelMethodProcessor()); --Model 
    handlers.add(new ViewMethodReturnValueHandler()); --View
    handlers.add(new HttpEntityMethodProcessor(); --HttpEntity
    ...未完

    // 基于注解的返回值类型
    handlers.add(new ModelAttributeMethodProcessor(false)); --@ModelAttribute
    handlers.add(new RequestResponseBodyMethodProcessor()); --@ResponseBody

    // 多用途返回值类型
    handlers.add(new ViewNameMethodReturnValueHandler()); --void或者String类型
    handlers.add(new MapMethodProcessor()); --Map

    // 自定义返回值类型
    if (getCustomReturnValueHandlers() != null) {
        handlers.addAll(getCustomReturnValueHandlers());
    }
    return handlers;
}

5、总结

通过本章节内容,我们看到SpringMVC在初始化的时候,加载了RequestMappingHandlerMapping和RequestMappingHandlerAdapter。

在实例化RequestMappingHandlerMapping的时候,过滤了所有类上有Controller、RequestMapping的Bean,获取他们的方法。注册methodMapping和urlMap。为以后的请求地址匹配做准备。
在实例化RequestMappingHandlerAdapter的时候,又注册了一堆解析器,包括参数解析器和返回值类型解析器。为请求的参数解析和返回类型解析做准备。

有了以上内容的准备,关于SpringMVC的请求处理我们大概有了一个轮廓,不过具体是怎么做的呢?下一节我们将看到这一过程。

你可能感兴趣的:(Spring源码分析(四)SpringMVC初始化)