源码分析spring-mvc启动流程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码分析spring-mvc启动流程相关的知识,希望对你有一定的参考价值。

参考技术A spring-mvc 的启动流程

1、request 请求到达  dispatchServlet-> doService()->doDispatch()  开始处理请求

2、根据doDispatch() 再去调用getHandler() 目的是获取包含 处理器Handler和处理器拦截器 AdapterIntercepers 的处理器拦截链 HandlerExecutionChain

        2.1 getHandler(HttpServletRequest request) 通过HandlerMappping对象获取HandlerExecutionChain

3、再通过getHandlerAdapter() 在拦截链中获取handler对应的处理器适配器  handleAdapter

4、ha.handler(processedRequest, response, mappedHandler.getHandler()) 通过handlerAdapter来调用具体的处理器 完成对请求的处理

        4.1 hanler 调用 SimpleServletHandlerAdapter.service()->HttpServlet.service()->HttpServlet.service()->doGet(req, resp);

        在这里我们发现不只是调用doGet()请求,而是根据请求方法来决定调用doGet()还是doPost(),或者其他

         这里发现请求有如下:doGet(req, resp);doHead(req, resp);doPost(req, resp);

          doPut(req, resp);doDelete(req, resp);doOptions(req,resp);doTrace(req,resp);

5、根据handler返回的ModleAndView来决定是否渲染试图  ModleAndView 将modle 和 view 封装在一起

6、ViewResolver:视图解析器    负责将处理结果生成view 试图

            具体操作:ViewResolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View  进行渲染将处理结果通过页面展示给用户

7、view 是spring-mvc 的封装对象,是一个接口

springmvc框架提供了很多的View视图类型,包括:jspview,pdfview,jstlView、freemarkerView、pdfView等。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。

在阅读源码的过程中,发现在启动流程中,用到了适配器模式。。。。

适配器模式:大致对它的理解是这样的,假如现在有一个三方系统M,我们现在有个系统A需要和他交互,那么需要双方定义通信协议。那么问题来了,如果后期我们有很多系统都想于这个M来交互,那么都需要和M去定义通信协议,对于M来说,M会答应么?

解决方案:这个时候,我们来找一个C,来兼容所有的系统,而最后和M交互的只有C,其他系统想要和M交互,那么只需要和C交互在这里C就相当于我们的适配器,也及就是说适配各种系统的调用

源码分析|SpringBoot启动流程


解析开始前先给出一张图,这是springboot项目启动时,我们可以扩展的接口,以及接口调用的时机,熟悉了这个流程,我们就可以方便快速的自定义我们自己的启动逻辑。如下:

  • SpringApplicationRunListener的各个方法被调用在SpringApplication.run()的各个时刻

  • ApplicationContextInitializer---用于编程形式的应用上下文初始化,在刷新ConfigurableApplicationContext#refresh()之前调用

  • ApplicationListener监听器

  • CommandLineRunner

这是一个简单的SpringCloud项目,这是EurekaServer模块,正好看到这里,以此来分析一下SpringBoot的启动流程,可能会和单纯的SpringBoot项目不同,因为SpringCloud会实现各种自定义扩展接口以注入各种驱动其自身运行对象。

首先看一下启动入口,依然是一个main方法,调用SpringApplication的静态run方法,传入的是EurekaServerApplication类对象和入参。

/** * @author wangcheng */@EnableEurekaServer@SpringBootApplicationpublic class EurekaServerApplication {
public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); }
}

经过一系列的run重载方法,最终到达SpringApplication的构造方法中。

/** * Create a new {@link SpringApplication} instance. The application context will load * beans from the specified primary sources (see {@link SpringApplication class-level} * documentation for details. The instance can be customized before calling * {@link #run(String...)}. * @param resourceLoader the resource loader to use * @param primarySources the primary bean sources * @see #run(Class, String[]) * @see #setSources(Set) */@SuppressWarnings({ "unchecked", "rawtypes" })public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));   // ①根据类路径下的存在某些特殊的类来判断是哪种类型的应用 this.webApplicationType = WebApplicationType.deduceFromClasspath(); // ②使用SpringFactoriesLoader.loadFactoryNames方法从各个jar包的/META-INF/spring.factories配置文件中 // 找到实现了ApplicationContextInitializer上下文初始化接口的类,并实例化它们。 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));   // 同上,实例化ApplicationListener.class setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // ③推断该应用程序的主类,也就是EurekaServerApplication类,怎么实现呢,很巧妙。 // 首先获取到当前程序的堆栈元素数组,依次遍历,找到运行了main方法的哪个类的名称,再反射获取主类对象。 this.mainApplicationClass = deduceMainApplicationClass();}

①确定应用类型的代码贴出来看一下

static WebApplicationType deduceFromClasspath() { if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) { return WebApplicationType.REACTIVE; } for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } return WebApplicationType.SERVLET;}

spring的SPI,很多地方会用到,有兴趣的同学可以看一下JAVA的SPI和Dubbo的SPI,各有千秋。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances;}

③确定主类的代码

private Class<?> deduceMainApplicationClass() { try { StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { if ("main".equals(stackTraceElement.getMethodName())) { return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException ex) { // Swallow and continue } return null;}

接下来到真正重要的部分,SpringApplication的run方法,这里也就是整个Springboot启动的核心,当然一些隐藏的非常重要的流程是在各个扩展接口中实现的,我们稍后再说。看如下代码。

/** * Run the Spring application, creating and refreshing a new * {@link ApplicationContext}. * @param args the application arguments (usually passed from a Java main method) * @return a running {@link ApplicationContext} */public ConfigurableApplicationContext run(String... args) {   // new了一个简单的计时器,用于记录各个任务的时间相关的信息。 StopWatch stopWatch = new StopWatch();   // 启动计时器 stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();   // Headless模式是系统的一种配置模式。在系统可能缺少显示设备、键盘或鼠标这些外设的情况下可以使用该模式。 configureHeadlessProperty();   // 依旧是在spring.factories配置文件中找那些实现了SpringApplicationRunListener接口的类并实例化 SpringApplicationRunListeners listeners = getRunListeners(args); // 调用各个SpringApplicationRunListener实例的staring方法。这里SpringApplicationRunListeners类封装了一组 // SpringApplicationRunListener的实例,统一入口管理和调用,有点门面模式的意思。 listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);      // ④prepareEnvironment,顾名思义,就是准备应用所需要的环境信息 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);      // 忽略一些bean,因为这些bean就是为启动项目而准备的,spring会提前准备好 configureIgnoreBeanInfo(environment);      // 用来进行自定义启动banner图的,可以换成自己项目的名字等 Banner printedBanner = printBanner(environment);      // createApplicationContext创建应用上下文,依旧根据的是应用的类型穿件不同的上下文。       context = createApplicationContext(); // 依旧是在spring.factories配置文件中找那些实现了SpringBootExceptionReporter接口的类并实例化。当启动流程出现异常时调用。 exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context);      // ⑥prepareContext,顾名思义,就是准备应用的上下文信息      prepareContext(context, environment, listeners, applicationArguments, printedBanner);      // refreshContext(context)是IOC的流程。 refreshContext(context); // afterRefresh(context, applicationArguments),留给子类实现,在上下文刷新后回调。 afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } // 发出listeners.started(context)上下文启动事件。 listeners.started(context); // callRunners(context, applicationArguments)内部是调用那些实现了ApplicationRunner和CommandLineRunner接口的实例。用于在容器启动后,执行我们自定义的逻辑。 callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); }
try { // listeners.running(context)发出容器正在运行事件。 listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context;}

④准备运行环境,包括

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // Create and configure the environment   // 获取或创建一个环境实例,这里是根据之前SpringApplicaton对象初始化时确定的应用类型来决定使用哪种环境实例的。 ConfigurableEnvironment environment = getOrCreateEnvironment();   // ⑤configureEnvironment,配置环境。分为配置属性源configurePropertySources和配置活跃环境文件configureProfiles。   // Spring的属性源可以适配各种类型的源,核心接口是PropertySource,有多种实现类如Map,Properties,ServletConfig等相关的属性源。   // 使用了对象适配器模式。configureProfiles就是配置spring.active.profile属性设置的配置。 configureEnvironment(environment, applicationArguments.getSourceArgs()); ConfigurationPropertySources.attach(environment); // 调用SpringApplicationRunListeners.environmentPrepared进行回调通知,最终处理该通知的是各个ApplicationListener实例。   // 这里的listeners中有一些还是比较重要的,如和SpringCloud相关的BootstrapApplicationListener。这个以后再说。 listeners.environmentPrepared(environment); bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment;}

⑤配置各种propertySource和profiles

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { if (this.addConversionService) { ConversionService conversionService = ApplicationConversionService.getSharedInstance(); environment.setConversionService((ConfigurableConversionService) conversionService); }   // 这里面有jvm的环境变量和系统的环境变量,根据应用的类型不同还会有如servlet的web环境变量 configurePropertySources(environment, args); configureProfiles(environment, args);}

prepareContext

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { context.setEnvironment(environment);   // postProcessApplicationContext为上下文设置beanNameGenerator,resourceLoader和conversionService。 postProcessApplicationContext(context); // applyInitializers应用初始化器,就是已开始在spring.factories中找到的那些ApplicationContextInitializer实例,依次调用其initialize方法。 applyInitializers(context); // 通知各个ApplicationListener,上下文已准备好。 listeners.contextPrepared(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // Add boot specific singleton beans // 为上下文注入一些已经创建的实例。 ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } // Load the sources // ⑦获取所有的源并加载,这里的源就是我们初始化SpringApplication时传入的EurekaServerApplication.class, // 当然这里还有其他的source,可支持多种不同的加载类型,依次进入load方法,最终到达BeanDefinitionLoader类, // 此类是各个BeanDefinitionReader实现类的简单门面,如AnnotatedBeanDefinitionReader,XmlBeanDefinitionReader等。 // 下面的load方法就是为了支持各种不同的源而设计的。   // 在spring扫描所有bean时,会以此为起点 Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); load(context, sources.toArray(new Object[0])); listeners.contextLoaded(context);}

⑦load方法,最终,相应的source被注入到上下文中的beanFactory中成为beanDefinition。

private int load(Object source) { Assert.notNull(source, "Source must not be null"); if (source instanceof Class<?>) { return load((Class<?>) source); } if (source instanceof Resource) { return load((Resource) source); } if (source instanceof Package) { return load((Package) source); } if (source instanceof CharSequence) { return load((CharSequence) source); } throw new IllegalArgumentException("Invalid source type " + source.getClass());}

至此,SpringBoot启动流程结束。

这其中还有一些细节,如在各个阶段各个监听器都干了什么,spring的IOC过程等,这些放在其他文字中细讲。


以上是关于源码分析spring-mvc启动流程的主要内容,如果未能解决你的问题,请参考以下文章

Android 启动过程Activity 启动源码分析 ( ActivityThread 流程分析 二 )

[Abp 源码分析] Abp 框架启动流程分析

SpringMVC 启动流程及相关源码分析

React Native 源码分析——启动流程

React Native 源码分析——启动流程

React Native 源码分析——启动流程