个人想写《springboot源码解析》这一系列很久了,但是一直角儿心底的知识积累不足,所以一直没有动笔。 所以想找一些小伙伴一起写这一系列,互相纠错交流学习。
如果有小伙伴有兴趣一起把这一系列的讲解写完的话,加下我微信:13670426148,我们一起完成,当交流学习。
后期还想写一系列介绍rpc框架的,不过要再过一阵子了,先把springboot的写完
这系列的教程从 Springboot项目的入口开始,即 SpringApplication.run(Application.class, args) 开始进行讲解。
先贴一下入口类的代码:
@SpringBootApplication //@EnableTransactionManagement @EnableAsync @EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class}) @EnableScheduling @EnableRetry @ComponentScan(basePackages = {"*** ", "***"}) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } 复制代码
其中,入口类的类名是 Application, 这个类的类型将作为参数,传递给 SpringApplication的 run() 方法,还有一些初始化参数,这些都在run()方法的时候会进行处理,可以先记住他们。
现在可以记住 @EnableAutoConfiguration
和 @EnableScheduling
和 @ComponentScan
等注解,记住这些注解,后面将介绍其运行过程。
Application这个类没有继承所有任何类,他真的就是一个 启动类 ,就相当与写算法题时候的那个main函数,而你的计算流程就写在其他类或者方法里面。
SpringApplication用于从java main方法引导和启动Spring应用程序,默认情况下,将执行以下步骤来引导我们的应用程序:
大多数情况下,像SpringApplication.run(ShiroApplication.class, args);这样启动我们的应用,也可以在运行之前创建和自定义SpringApplication实例,具体可以参考注释中示例。
SpringApplication可以从各种不同的源读取bean。 通常建议使用单个@Configuration类来引导,但是我们也可以通过以下方式来设置资源:
public class SpringApplication{ public SpringApplication(ResourceLoader resourceLoader, Object... sources) { this.bannerMode = Mode.CONSOLE; this.logStartupInfo = true; this.addCommandLineProperties = true; this.headless = true; this.registerShutdownHook = true; //todo ------------------------------------------------------- this.additionalProfiles = new HashSet(); //上面的信息都不是主要的,主要的信息在这里,在这里进行 //(1)运行环境 (2) 实例化器 (3)监听器等的初始化过程,下面将详细解析 this.initialize(sources); } public ConfigurableApplicationContext run(String... args) { ******* } } 复制代码
这个 this.initialize(sources) 方法还是在 SpringApplication里面的,所以这个SpringApplication真的是贯穿springboot整个启动过程的一个类,后面还有一个run() 方法。
我们来看 initialize(Object[] sources) 方法的内容
private void initialize(Object[] sources) { //sources里面就是我们的入口类: Application.class if (sources != null && sources.length > 0) { this.sources.addAll(Arrays.asList(sources)); } //这行代码设置SpringApplication的属性webEnvironment,deduceWebEnvironment方法是推断是否是web应用的核心方法 this.webEnvironment = this.deduceWebEnvironment(); //获取所有的实例化器Initializer.class this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); //获取所有的监听器 this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); //这个不解释了,就是我们的Application.class ,我们写的入口类,过程就是从当前的堆栈中找到我们写main方法额类,就是获取我们的入口类了 this.mainApplicationClass = this.deduceMainApplicationClass(); } 复制代码
下面就解释3个部分的具体实现:
(1) 推测运行环境
(2)获取所有的实例化器Initializer.class
又展示了其获取过程
(3)获取所有的监听器Initializer.class
推测运行环境,并赋予个 this.webEnvironment
这个属性, deduceWebEnvironment方法是推断是否是web应用的核心方法。
在后面SpringApplication 的run()方法中创建 ApplicationContext
的时候就是根据webEnvironment这个属性来判断是 AnnotationConfigEmbeddedWebApplicationContext
还是 AnnotationConfigApplicationContext
代码如下:
private boolean deduceWebEnvironment() { String[] var1 = WEB_ENVIRONMENT_CLASSES; int var2 = var1.length; for(int var3 = 0; var3 < var2; ++var3) { String className = var1[var3]; if (!ClassUtils.isPresent(className, (ClassLoader)null)) { return false; } } return true; } WEB_ENVIRONMENT_CLASSES = new String[]{ "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" }; 复制代码
推断过程很简单,不过我不理解为什么这么写,因为我这个是web项目,所以 this.webEnvironment
的值为 true
ClassUtils.isPresent()的过程其实很简单,就是判断 WEB_ENVIRONMENT_CLASSES
里面的两个类能不能被加载,如果能被加载到,则说明是web项目,其中有一个不能被加载到,说明不是。
//看完这个方法真觉得很棒,获取工厂实例 this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) { return this.getSpringFactoriesInstances(type, new Class[0]); } //记住 ty private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { //这个是获取类加载器 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); //type是ApplicationContextInitializer.class,获取类型工厂的名字 Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); //获取工厂实例 List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); //排序,按照@Order的顺序进行排序,没有@Order的话,就按照原本的顺序进行排序,不管他问题不大 AnnotationAwareOrderComparator.sort(instances); return instances; } 复制代码
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); try { //获取所有 jar包下面的 META-INF/spring.factories 的urls Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); ArrayList result = new ArrayList(); while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); //每个spring.factories里的下的内容装载成Properties信息 Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); //下面内容会继续解析 String factoryClassNames = properties.getProperty(factoryClassName); result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames))); } return result; } catch (IOException var8) { throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8); } } 复制代码
还有很多,就不一一列举出来了
(1)就是找到所有的 /MEIT-INF下面的spring.factory
(2)转换成 properties,
(3)properties.getProperty(factoryClassName)
此时 factoryClassName 相当于是一个key获取对应的properties里面是否有 "org.springframework.context.ApplicationContextInitializer"对应的值,有的话,添加到result中
到最后可以得到的有, 即图1.2
再进行 获取工厂实例 操作,步骤很简单,就是用构造器和类名生成指定的 Inializer , 到现在的过程都很简单。
贴个代码,不进行解释了
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) { List<T> instances = new ArrayList(names.size()); Iterator var7 = names.iterator(); while(var7.hasNext()) { String name = (String)var7.next(); try { Class<?> instanceClass = ClassUtils.forName(name, classLoader); Assert.isAssignable(type, instanceClass); Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); T instance = BeanUtils.instantiateClass(constructor, args); instances.add(instance); } catch (Throwable var12) { throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, var12); } } return instances; } 复制代码
类似的上面的步骤,监听器的获得结果如下: 图1.3
(1)还记得SpringApplication.class有那些属性吗
public class SpringApplication{ private List<ApplicationContextInitializer<?>> initializers; //如图1.2这个是拿6个Initializer private WebApplicationType webApplicationType; //这个是true private List<ApplicationListener<?>> listeners; //这和是图1.3的10个Listener private Class<?> mainApplicationClass; //结果就是DemoApplication //另外还有构造方法设置的值 public SpringApplication(ResourceLoader resourceLoader, Object... sources) { this.bannerMode = Mode.CONSOLE; this.logStartupInfo = true; this.addCommandLineProperties = true; this.headless = true; this.registerShutdownHook = true; //todo ------------------------------------------------------- this.additionalProfiles = new HashSet(); //上面的信息都不是主要的,主要的信息在这里,在这里进行 //(1)运行环境 (2) 实例化器 (3)监听器等的初始化过程,下面将详细解析 this.initialize(sources); } } 复制代码
(2) SpringApplication.class 就是一个操作启动过程的类
实例化过程就是加载一些最初始的参数和信息,比如监听器,实例化器,bannerMode,additionalProfiles等信息。其中最主要的还是 监听器和实例化器 , 关于监听器,是springboot启动过程最重要的一部分,其启动过程的机制大概就是, 用一个广播,他广播一些event事件,然后这些监听器(10个),就会根据这些事件,做不同的反应。 监听器模式大家可以先了解一下。