SpringBoot启动过程分析之SpringApplication的初始化

前言

SpringApplication是一个类,提供一些便利的功能,引导Spring的程序进行启动

Spring Boot 的入口类

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

做过 Spring Boot 项目的都知道,上面是 Spring Boot 最简单通用的入口类。入口类的要求是最顶层包下面第一个含有 main 方法的类,使用注解 @SpringBootApplication 来启用 Spring Boot 特性,使用 SpringApplication.run 方法来启动 Spring Boot 项目。

来看一下这个类的run方法调用关系源码:

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
}
	
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
}
	

这里有2个核心1个是SpringApplication的创建,另一个是其run方法的调用。本文重点研究SpringApplication的创建

一、SpringApplication源码概括

public class SpringApplication {

	//默认情况下将用于非web环境的应用程序上下文的类名.
	public static final String DEFAULT_CONTEXT_CLASS ="org.springframework.context.annotation.AnnotationConfigApplicationContext";

    //默认情况下将用于web环境的应用程序上下文的类名
	public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS ="org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

	//应用程序上下文的类名,默认情况下将用于被动web环境.
	public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS ="org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";

	//默认横幅位置。“banner.txt”
	public static final String BANNER_LOCATION_PROPERTY_VALUE =SpringApplicationBannerPrinter.DEFAULT_BANNER_LOCATION;

	//横幅位置属性键 “”spring.banner.location.
	public static final String BANNER_LOCATION_PROPERTY = SpringApplicationBannerPrinter.BANNER_LOCATION_PROPERTY;

	private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";

	private Set<Class<?>> primarySources;

	private Set<String> sources = new LinkedHashSet<>();

	private Class<?> mainApplicationClass;

	private Banner.Mode bannerMode = Banner.Mode.CONSOLE;

	private boolean logStartupInfo = true;

	private boolean addCommandLineProperties = true;

	private boolean addConversionService = true;

	private Banner banner;

	private ResourceLoader resourceLoader;

	private BeanNameGenerator beanNameGenerator;

	private ConfigurableEnvironment environment;

	private Class<? extends ConfigurableApplicationContext> applicationContextClass;

	private WebApplicationType webApplicationType;

	private boolean headless = true;

	private boolean registerShutdownHook = true;

	private List<ApplicationContextInitializer<?>> initializers;

	private List<ApplicationListener<?>> listeners;

	private Map<String, Object> defaultProperties;

	private Set<String> additionalProfiles = new HashSet<>();

	private boolean allowBeanDefinitionOverriding;

	private boolean isCustomEnvironment = false;

	private boolean lazyInitialization = false;
	
	public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}
	
   public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
			// 1、资源初始化资源加载器为 null
		    this.resourceLoader = resourceLoader;
		
		    // 2、断言主要加载资源类不能为 null,否则报错
		    Assert.notNull(primarySources, "PrimarySources must not be null");
		
		    // 3、初始化主要加载资源类集合并去重
		    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		
		    // 4、推断当前 WEB 应用类型
		    this.webApplicationType = deduceWebApplicationType();
		
		    // 5、设置应用上线文初始化器
		    setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class));      
		
		    // 6、设置监听器
		    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		
		    // 7、推断主入口应用类
		    this.mainApplicationClass = deduceMainApplicationClass();
	}

二. 推断当前 WEB 应用类型

这个就是根据类路径下是否有对应项目类型的类推断出不同的应用类型。

private WebApplicationType deduceWebApplicationType() {

    //如果“org.springframework.web.reactive.DispatcherHandler"存在,且"org.springframework.web.servlet.DispatcherServlet"不存在
    //,当前程序类型就是响应式 WEB 项目,启动时会嵌入响应式web服务器
    if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    //如果javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext
    //有一个不存在,当前程序类型就是非 WEB 项目,如果是这个那么项目不会启动时嵌入web服务器
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }//当前程序类型就是SERVLET WEB 项目,启动时会嵌入servlet web服务器
    return WebApplicationType.SERVLET;
}

三、 设置应用上线文初始化器

ApplicationContextInitializer 的作用是什么?源码如下。

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
    void initialize(C applicationContext);
}

用来初始化指定的 Spring 应用上下文,如注册属性资源、激活 Profiles 等。

来看下 setInitializers 方法源码,其实就是初始化一个 ApplicationContextInitializer 应用上下文初始化器实例的集合。

//setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class));    

public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
    this.initializers = new ArrayList<>();
    this.initializers.addAll(initializers);
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // 读取META-INF/spring.factories文件中的类放到set集合中,使用名称并确保唯一以防止重复,下面
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    //使用反射创建他们,放到集合中
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    //排序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

根据类路径下的 META-INF/spring.factories 文件解析并获取 ApplicationContextInitializer 接口的所有配置的类路径名称。

spring-boot-autoconfigure-2.0.3.RELEASE.jar!/META-INF/spring.factories 的初始化器相关配置内容如下:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

四. 设置监听器

ApplicationListener 的作用是什么?源码如下。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    /**
     * Handle an application event.
     * @param event the event to respond to
     */
    void onApplicationEvent(E event);

}

这个接口继承了 JDK 的 java.util.EventListener 接口,实现了观察者模式,它一般用来定义感兴趣的事件类型,事件类型限定于 ApplicationEvent的子类,这同样继承了 JDK 的 java.util.EventObject 接口。

//setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
		this.listeners = new ArrayList<>(listeners);
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

设置监听器和设置初始化器调用的方法是一样的,只是传入的类型不一样,设置监听器的接口类型为: getSpringFactoriesInstances,对应的 spring-boot-autoconfigure-2.0.3.RELEASE.jar!/META-INF/spring.factories 文件配置内容请见下方。

Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

五.推断主入口应用类

this.mainApplicationClass = deduceMainApplicationClass();
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;
	}

这个推断入口应用类的方式有点特别,通过构造一个运行时异常,再遍历异常栈中的方法名,获取方法名为 main 的栈帧,从来得到入口类的名字再返回该类。



public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }
    try {
        Enumeration<URL> urls = (classLoader != null ?classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                //根据键获取值,并去2边空格
                String factoryTypeName = ((String) entry.getKey()).trim();
                //逗号分割字符串,并放到集合中
                List<String> factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                //放到Map<K, List<V>>中
                result.add(factoryTypeName, factoryImplementationName.trim());
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("");
    }
}


private <T> List<T> createSpringFactoriesInstances(Class<T> type,Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
    List<T> instances = new ArrayList<>(names.size());
    for (String name : names) {
        try {
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            Assert.isAssignable(type, instanceClass);
            Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
            T instance = (T) BeanUtils.instantiateClass(constructor, args);
            instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException("");
        }
    }
    return instances;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot项目中,可以使用初始化代码来实现项目启动时的一些初始化操作。下面以示例代码进行解释: 1. 创建一个类,命名为ApplicationRunnerImpl实现ApplicationRunner接口,该接口继承了CommandLineRunner接口,用于在Spring Boot项目启动之后执行特定的代码。 ```java import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; @Component public class ApplicationRunnerImpl implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { // 这里写入初始化代码,例如加载配置、数据库连接等操作 System.out.println("Spring Boot项目启动初始化代码执行!"); } } ``` 2. 在启动类中,使用@SpringBootApplication注解启动Spring Boot应用程序。 ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } ``` 在上述示例代码中,当Spring Boot项目启动之后,ApplicationRunnerImpl类中的run方法会被执行,可以在此方法中编写一些初始化的代码。比如加载配置文件、初始化数据库连接等。上面的例子中,run方法内只打印了一条信息。实际应用中,可以根据需要编写具体的初始化逻辑。 当代码执行时,控制台会打印出"Spring Boot项目启动初始化代码执行!"的信息,表示初始化代码成功执行。 这种方式非常适用于需要在项目启动时执行一些初始化操作的场景,可以方便地集成到Spring Boot框架中,实现项目的自动初始化

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值