
Spring Boot作为目前主流JavaWeb框架具有如下优点:
- 快速创建独立运行的Spring项目以及与主流框架集成
- 使用嵌入式Servlet容器,应用不需要打包成war
- starts自动依赖与版本控制
- 无需大量配置,简化开发
- 准生产化境的运行时应用监控
- 与云计算集成
而Spring Boot内部是如何实现的这些优点的呢,我们一步一步揭开Spring Boot的面纱。
准备一个Spring Boot项目现在搭建一个Spring Boot项目是如此的简单快捷,不会经历以前Spring的配置炼狱。
Cc同学使用开发工具是IDEA,新建项目,选择Spring Initializr,一路默认,勾选组件楼主勾选了Spring Web,为了方便演示Web环境的启动。
完成创建,稍等片刻,我们就得到了一个Spring Boot项目。我这里使用的Spring Boot版本是2.6.1
打上断点直接Debug运行,步入run方法内部。
这里我建议 点一下 Download Sources,这样IDEA会帮我们下载好源码,更加方便调试。
可以看到这里实际上是先创建SpringApplication的实例,然后调用run方法,这篇文章我们先讲解SpringApplication的构造函数中做了什么工作。
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 = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
//设置初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//推断主类
this.mainApplicationClass = deduceMainApplicationClass();
}
deduceFromClasspath()方法内部就是根据Classpath下是否存在提前定义好的这些类来推断当前应用类型,WebApplicationType一共有三种属性 NONE,SERVLET,REACTIVE。
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
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;
}
}
//默认是SERVLERT
return WebApplicationType.SERVLET;
}
getSpringFactoriesInstances()
这里要着重讲一下getSpringFactoriesInstances()方法,在设置初始化器和监听器时都用到了这个方法,而我们的监听器和初始化器都是从哪来的我们就要详细看一下这个方法,后续这个方法用到的地方比较多
//参数是一个Class privateCollection getSpringFactoriesInstances(Class type) { //调用下方方法 return getSpringFactoriesInstances(type, new Class>[] {}); } private Collection getSpringFactoriesInstances(Class type, Class>[] parameterTypes, Object... args) { //获取类加载器 ClassLoader classLoader = getClassLoader(); // Use names and ensure unique to protect against duplicates //官方这里的注释是使用全类名并确保唯一,以防止重复 //这里主要是获取参数传进来的Class类型对应的全类名 Set names = new linkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); //通过反射获得上面类的实例 List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); //根据order接口排序 AnnotationAwareOrderComparator.sort(instances); return instances; }
跟进一下 SpringFactoriesLoader.loadFactoryNames(type, classLoader) 方法;
//SpringFactoriesLoader内的静态变量 public static final String FACTORIES_RESOURCE_LOCATION = "meta-INF/spring.factories"; static final Map>> cache = new ConcurrentReferenceHashMap<>(); public static List loadFactoryNames(Class> factoryType, @Nullable ClassLoader classLoader) { ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } //获取传进来的Class的全类名 //示例: org.springframework.context.ApplicationListener String factoryTypeName = factoryType.getName(); //可以看到下方的loadSpringFactories()返回值是一个Map,这里通过factoryTypeName在Map里寻找,如果没有则返回空集合,如果可以找到的话返回一个List 的集合 return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); } private static Map > loadSpringFactories(ClassLoader classLoader) { //这里通过类加载器类型去缓存中获取,这里的cache是一开始上方定义的是一个Map类型 //key值就是类加载器,value是一个Map > Map > result = cache.get(classLoader); if (result != null) { //如果缓存不为null则直接返回 return result; } result = new HashMap<>(); try { //获取"meta-INF/spring.factories"的资源位置 Enumeration urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); while (urls.hasMoreElements()) { //这里以我电脑得到的url做个示例: //jar:file:/C:/Users/lemon/.m2/repository/org/springframework/boot/spring-boot/2.6.1/spring-boot-2.6.1.jar!/meta-INF/spring.factories URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); //获取Properties对象解析内容 Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); for (String factoryImplementationName : factoryImplementationNames) { result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()) .add(factoryImplementationName.trim()); } } } // Replace all lists with unmodifiable lists containing unique elements result.replaceAll((factoryType, implementations) -> implementations.stream().distinct() .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))); //将结果放入缓存中,下次直接查询缓存 cache.put(classLoader, result); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } return result; }
spring.factories的内容示例:
cache示例:(其实存放的就是spring.factories的内容)
这时我们在看这行代码
loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName,Collections.emptyList());
实际就是在Map里获取对应的配置类名。
最后一步,推断主类。
private Class> deduceMainApplicationClass() {
try {
//获取方法调用栈
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
//找到main方法
//equals非常好的一个习惯就是使用字符串调用equals而非使用变量,这样会避免空指针异常
if ("main".equals(stackTraceElement.getMethodName())) {
//返回main方法所在的Class
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
至此,SpringBoot启动流程的第一节,SpringApplication构造函数分析就到这里了,接下来就是真正的run方法,有不理解或讲错的,同学可以下方留言一起探讨进步。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)