Spring Boot启动过程
上篇《Spring Boot系列——5分钟构建一个应用》介绍了如何快速创建一个Spring Boot项目并运行。虽然步骤少流程简单,为开发者省去了很多重复性的配置工作,但是其底层实现并没有这么简单。
这篇,我们就通过入口类TutorialApplication看看Spring Boot是如何启动的。
注解
写过Spring Boot都知道需要有一个入口类,就是本例子中的TutorialApplication,而这个类上面必不可上的需要有一个@SpringBootApplication注解。
点击进入该注解,我们可以发现其是一个复合注解,包括@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan。
/** * Indicates a {@link Configuration configuration} class that declares one or more * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience * annotation that is equivalent to declaring {@code @Configuration}, * {@code @EnableAutoConfiguration} and {@code @ComponentScan}. * * @author Phillip Webb * @author Stephane Nicoll * @since 1.2.0 */@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })public @interface SpringBootApplication { /** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */ @AliasFor(annotation = EnableAutoConfiguration.class) Class<?>[] exclude() default {}; /** * Exclude specific auto-configuration class names such that they will never be * applied. * @return the class names to exclude * @since 1.3.0 */ @AliasFor(annotation = EnableAutoConfiguration.class) String[] excludeName() default {}; /** * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses} * for a type-safe alternative to String-based package names. * @return base packages to scan * @since 1.3.0 */ @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") String[] scanBasePackages() default {}; /** * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to * scan for annotated components. The package of each class specified will be scanned. * <p> * Consider creating a special no-op marker class or interface in each package that * serves no purpose other than being referenced by this attribute. * @return base packages to scan * @since 1.3.0 */ @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") Class<?>[] scanBasePackageClasses() default {}; }
@SpringBootConfiguration
该注解底层其实就是@Configuration注解。熟悉Spring的发展里程碑就知道这是Java Config的配置形式。
通过该注解修饰,表示该类是一个配置类。
@EnableAutoConfiguration
该注解其实也是一个复合注解。
/** * Enable auto-configuration of the Spring Application Context, attempting to guess and * configure beans that you are likely to need. Auto-configuration classes are usually * applied based on your classpath and what beans you have defined. For example, if you * have {@code tomcat-embedded.jar} on your classpath you are likely to want a * {@link TomcatServletWebServerFactory} (unless you have defined your own * {@link ServletWebServerFactory} bean). * <p> * When using {@link SpringBootApplication}, the auto-configuration of the context is * automatically enabled and adding this annotation has therefore no additional effect. * <p> * Auto-configuration tries to be as intelligent as possible and will back-away as you * define more of your own configuration. You can always manually {@link #exclude()} any * configuration that you never want to apply (use {@link #excludeName()} if you don't * have access to them). You can also exclude them via the * {@code spring.autoconfigure.exclude} property. Auto-configuration is always applied * after user-defined beans have been registered. * <p> * The package of the class that is annotated with {@code @EnableAutoConfiguration}, * usually via {@code @SpringBootApplication}, has specific significance and is often used * as a 'default'. For example, it will be used when scanning for {@code @Entity} classes. * It is generally recommended that you place {@code @EnableAutoConfiguration} (if you're * not using {@code @SpringBootApplication}) in a root package so that all sub-packages * and classes can be searched. * <p> * Auto-configuration classes are regular Spring {@link Configuration} beans. They are * located using the {@link SpringFactoriesLoader} mechanism (keyed against this class). * Generally auto-configuration beans are {@link Conditional @Conditional} beans (most * often using {@link ConditionalOnClass @ConditionalOnClass} and * {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations). * * @author Phillip Webb * @author Stephane Nicoll * @see ConditionalOnBean * @see ConditionalOnMissingBean * @see ConditionalOnClass * @see AutoConfigureAfter * @see SpringBootApplication */@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; /** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */ Class<?>[] exclude() default {}; /** * Exclude specific auto-configuration class names such that they will never be * applied. * @return the class names to exclude * @since 1.3.0 */ String[] excludeName() default {}; }
其实现也是通过类似@Import的方式注入AutoConfigurationImportSelector类,并借助该类将所有符合条件的Configuration注解修饰的配置类加载到Spring Boot容器中。从classpath中搜索所有的META-INF/spring.factories配置文件,将其中org.springframework.boot.autoconfigure.EnableAutoConfiguration对应配置项通过反射的形式实例化为标注了@Configuration和javaconfig形式的IOC容器配置类,然后汇总为一个并加载到ioc容器中。
@ComponentScan
这个注解就不需要多介绍了吧,其作用自动扫描加载符合条件的bean。
SpringApplication
从项目的入口第一个碰到的就是SpringApplication类。
@SpringBootApplicationpublic class TutorialApplication { public static void main(String[] args) { SpringApplication.run(TutorialApplication.class, args); } }
进入该类的静态方法run,可以看到其在构造SpringApplication对象
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
进入SpringApplication构造方法,可以看到
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 = deduceWebApplicationType(); setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
主要做了如下几件事:
加载Source,这里只有只有Application
推断WebApplicationType,该枚举有三种类型NONE、SERVLET、REACTIVE。
设置初始化器变量setInitializers,初始化后得到6个初始化变量,这些类在上面提到的spring.factories中可以找到
设置监听器,与上面setInitializers实现类似,最终得到如下10个listeners
最后推断带有main函数的所在类,即入口类,这里就是TutorialApplication
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; }
run方法
看完SpringApplication是如何初始化后,我们来看下这个后面的run方法具体做了哪些事。
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
StopWatch,这是一个spring-core中的工具类,用来给程序运行计时(对于经常遇到需要计算某方法或接口耗时的场景,这个比System.currentTimeMillis好用)
configureHeadlessProperty配置,设置系统属性 java.awt.headless,这里设置为 true,表示运行在服务器端,在没有显示器和鼠标键盘的模式下工作,模拟输入输出设备功能。
遍历listeners并启动
封装入参args为AppliationArguments对象
打印banner(就是我们启动时看到的spring标识)
后面就是初始化上下文并加载上下文,具体实现就不进去看了
原文出处:https://www.cnblogs.com/bigdataZJ/p/springboot-start.html