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

前言

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

1、请求处理流程

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


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,就是加载处理器映射,它的加载有三种方式。

  • 配置文件具体指明
    <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />

  • 配置<mvc:annotation-driven/>,这种方式的话,Spring在解析标签的时候会加载两个handlerMappings,分别是RequestMappingHandlerMapping和BeanNameUrlHandlerMapping。

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

2.2 initHandlerAdapters

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

这里笔者推荐<mvc:annotation-driven/>。它在解析的时候,就已经加载了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

上面我们看到<mvc:annotation-driven/>已经加载了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<Method, T> mappings = new IdentityHashMap<Method, T>();
    final Class<?> userType = ClassUtils.getUserClass(handlerType);
    
    //获取userType对象上的所有方法 返回JDK中的Method对象集合
    Set<Method> 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<String> 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<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
    }
    if (this.returnValueHandlers == null) {
        List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
    }
    initControllerAdviceCache();
}

4.1、加载参数解析器

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

private List<HandlerMethodArgumentResolver> 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<HandlerMethodReturnValueHandler> 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的请求处理我们大概有了一个轮廓,不过具体是怎么做的呢?下一节我们将看到这一过程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容