SpringBoot启动流程简析(一)
2017-12-13 21:11
751 查看
我想很多人已经在项目中使用SpringBoot做项目开发的工作了,创建SpringBoot和启动SpringBoot应用都会较简单一点,下面我以SpringBoot官网上的Demo来简单的分析一些SpringBoot的启动流程,我们的启动主类代码如下:
我们先来看一下SpringBootApplication这个注解上的注解:
@Inherited这个注解的意思是这个注解所在的类的子类可以继承这个注解。
@EnableAutoConfiguration这个注解的意思是开启自动配置。SpringBoot的自动配置功能是SpringBoot的四大神器之一。
@ComponentScan扫描包路径。
@SpringBootConfiguration这个注解的意思是使用SpringBootConfiguration这个注解相当用使用@Configuration这个注解(使用这个注解的类相当于beans)。
好了,言归正传,下面进入到我们的重点。我们首先进入到SpringApplication的run方法中看一下这个方法的内容:
在调用run方法启动SpringBoot容器的时候还有一点需要注意的是,调用run方法的时候会返回一个Spring上下文 ConfigurableApplicationContext的实例。
上面这个run方法,干了两件事创建SpringApplication对象和调用另一个重载的run方法。我们先去看看SpringApplication的构造函数的内容:
我们先看看看1)处的deduceWebEnvironment方法
这里判断是不是web开发环境也很简单,就是看类路径下是能加载到javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext这两个两类,如果能加载到则是web环境,否则非web环境。
我们接着看2)的代码:
在上面的代码中最重要的就是SpringFactoriesLoader.loadFactoryNames(type, classLoader)这一句话。
loadFactoryNames的第一个方法是要加载的类的类型,第二个参数是类加载器。loadFactoryNames方法的内容如下所示:
这里我们以org.springframework.context.ApplicationContextInitializer为例,看一下META-INF/spring.factories中键为ApplicationContextInitializer的配置情况:
从上图中我们发现在SpringBoot和SpringBootAutoConfigure中都有键为org.springframework.context.ApplicationContextInitializer的类存在并且值没有相同的,所以这里会加载到6个类型为org.springframework.context.ApplicationContextInitializer的实例。
通过我们的调试会发现,确实是加载到了六个org.springframework.context.ApplicationContextInitializer的实例。这几个上下文初始类,我们在后面再介绍。
对于3)的过程和2)处的过程一样,请参考上面的步骤。下面我们来看4)处的内容:
今天我们就先分析到这里,这篇文章中主要说了在启动SpringBoot的过程中创建SpringApplication的实例,并调用它的初始化方法来判断当前环境是不是web环境,获取主应用类,存放传入的sources类,加载org.springframework.context.ApplicationContextInitializer和org.springframework.context.ApplicationListener类型的对象。
@SpringBootApplication public class SpringBootAnalysisApplication { public static void main(String[] args) { SpringApplication.run(SpringBootAnalysisApplication.class, args); } }
我们先来看一下SpringBootApplication这个注解上的注解:
@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) })
@Inherited这个注解的意思是这个注解所在的类的子类可以继承这个注解。
@EnableAutoConfiguration这个注解的意思是开启自动配置。SpringBoot的自动配置功能是SpringBoot的四大神器之一。
@ComponentScan扫描包路径。
@SpringBootConfiguration这个注解的意思是使用SpringBootConfiguration这个注解相当用使用@Configuration这个注解(使用这个注解的类相当于beans)。
好了,言归正传,下面进入到我们的重点。我们首先进入到SpringApplication的run方法中看一下这个方法的内容:
public static ConfigurableApplicationContext run(Object source, String... args) { //这里我们的第一个参数source的值是:SpringBootAnalysisApplication.class,在重载的run方法中将 //传入的SpringBootAnalysisApplication.class封装成了数组,也就是说我们可以调用重载的run方法,传入一个Object[],第二个参数是一个可变参数,是我们传入的启动参数。 return run(new Object[] { source }, args); }
在调用run方法启动SpringBoot容器的时候还有一点需要注意的是,调用run方法的时候会返回一个Spring上下文 ConfigurableApplicationContext的实例。
public static ConfigurableApplicationContext run(Object[] sources, String[] args) { return new SpringApplication(sources).run(args); }
上面这个run方法,干了两件事创建SpringApplication对象和调用另一个重载的run方法。我们先去看看SpringApplication的构造函数的内容:
public SpringApplication(Object... sources) { //调用initialize方法进行一些初始化的动作。 initialize(sources); } private void initialize(Object[] sources) { if (sources != null && sources.length > 0) { //如果传入的sources有值的话,将Object[]对象转换为List。这里的sources是 //private final Set<Object> sources = new LinkedHashSet<Object>(); //是一个Set集合。 this.sources.addAll(Arrays.asList(sources)); } //判断是否是web环境 1) this.webEnvironment = deduceWebEnvironment(); //加载ApplicationContextInitializer类型的对象 2) setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); //加载ApplicationListener类型的对象 3) setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //寻找启动主类 4) this.mainApplicationClass = deduceMainApplicationClass(); }
我们先看看看1)处的deduceWebEnvironment方法
private boolean deduceWebEnvironment() { //private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet", // "org.springframework.web.context.ConfigurableWebApplicationContext" }; //WEB_ENVIRONMENT_CLASSES是一个数组内容如上 for (String className : WEB_ENVIRONMENT_CLASSES) { //如果加载不到任何一个类就返回false if (!ClassUtils.isPresent(className, null)) { return false; } } return true; }
这里判断是不是web开发环境也很简单,就是看类路径下是能加载到javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext这两个两类,如果能加载到则是web环境,否则非web环境。
我们接着看2)的代码:
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) { return getSpringFactoriesInstances(type, new Class<?>[] {}); } private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { //线程上下文加载器 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<String>( //关键代码 SpringFactoriesLoader.loadFactoryNames(type, classLoader)); //根据上一步获取到的类,创建实例对象,这里没什么多说的 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); //排序也没有什么多说的 AnnotationAwareOrderComparator.sort(instances); return instances; }
在上面的代码中最重要的就是SpringFactoriesLoader.loadFactoryNames(type, classLoader)这一句话。
loadFactoryNames的第一个方法是要加载的类的类型,第二个参数是类加载器。loadFactoryNames方法的内容如下所示:
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { //获取要加载的类的全限定名这里是org.springframework.context.ApplicationContextInitializer String factoryClassName = factoryClass.getName(); try { //加载资源 从加载的是哪个资源呢?META-INF/spring.factories这个文件 //注意这里会加载所有类路径下的/META-INF/spring.factories,在Spring的相关jar包中基本上都有这个文件存在,当然也可以在自己的工程中自定义。 Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); List<String> result = new ArrayList<String>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); //获取键为前面获取到的全限定类名的值,多个值用 , 分割 String factoryClassNames = properties.getProperty(factoryClassName); //用, 分割上面获取到的值 result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames))); } return result; } }
这里我们以org.springframework.context.ApplicationContextInitializer为例,看一下META-INF/spring.factories中键为ApplicationContextInitializer的配置情况:
从上图中我们发现在SpringBoot和SpringBootAutoConfigure中都有键为org.springframework.context.ApplicationContextInitializer的类存在并且值没有相同的,所以这里会加载到6个类型为org.springframework.context.ApplicationContextInitializer的实例。
通过我们的调试会发现,确实是加载到了六个org.springframework.context.ApplicationContextInitializer的实例。这几个上下文初始类,我们在后面再介绍。
对于3)的过程和2)处的过程一样,请参考上面的步骤。下面我们来看4)处的内容:
private Class<?> deduceMainApplicationClass() { try { //获取运行时方法调用栈的信息 StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { //找到方法调用链上方法名为main的类 if ("main".equals(stackTraceElement.getMethodName())) { //返回main方法所在的类对象这里是 com.zkn.springboot.analysis.SpringBootAnalysisApplication return Class.forName(stackTraceElement.getClassName()); } } } return null; }
今天我们就先分析到这里,这篇文章中主要说了在启动SpringBoot的过程中创建SpringApplication的实例,并调用它的初始化方法来判断当前环境是不是web环境,获取主应用类,存放传入的sources类,加载org.springframework.context.ApplicationContextInitializer和org.springframework.context.ApplicationListener类型的对象。
相关文章推荐
- SpringBoot启动流程简析(四)
- Spring Boot启动流程详解(一)
- spring boot容器启动流程
- spring boot 运行流程简析
- Spring Boot启动流程详解(一)
- SpringBoot启动流程解析
- 【深入SpringBoot 1.3.5 第一章】Boot应用的启动流程
- Spring Boot启动流程分析
- Spring Boot源码分析之启动流程
- 【Spring Boot】SpringBoot-启动流程分析
- Spring Boot Web启动流程
- SpringBoot应用启动流程
- Spring boot启动运行流程
- SpringBoot——run启动流程
- 【深入SpringBoot 1.3.5 第一章】Boot应用的启动流程
- Spring Boot启动流程
- Spring Boot 启动流程详解(二)
- spring boot 启动流程
- Spring Boot启动流程详解
- spring boot自动配置与启动流程分析