通过上一章的源码分析, 我们知道了 spring boot 里面的 listeners 到底是什么 (META-INF/spring.factories 定义的资源的实例), 以及它是创建和启动的, 今天我们继续深入分析一下 SpringApplication 实例变量中的 run 函数中的其他内容. 还是先把 run 函数的代码贴出来:
- /**
- * Run the Spring application, creating and refreshing a new
- * {@link ApplicationContext}.
- * @param args the application arguments (usually passed from a Java main method)
- * @return a running {@link ApplicationContext}
- */
- 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;
- }
在 listeners 启动了以后, 我们来看一下 ApplicationArguments applicationArguments
= new DefaultApplicationArguments(args); 在 DefaultApplicationArguments 的构造函数里, 我们跟踪过去发现其最终调用的 SimpleCommandLineArgsParser.parse 函数:
- public CommandLineArgs parse(String... args) {
- CommandLineArgs commandLineArgs = new CommandLineArgs();
- String[] var3 = args;
- int var4 = args.length;
- for(int var5 = 0; var5 <var4; ++var5) {
- String arg = var3[var5];
- if(arg.startsWith("--")) {
- String optionText = arg.substring(2, arg.length());
- String optionValue = null;
- String optionName;
- if(optionText.contains("=")) {
- optionName = optionText.substring(0, optionText.indexOf(61));
- optionValue = optionText.substring(optionText.indexOf(61) + 1,
- optionText.length());
- } else {
- optionName = optionText;
- }
- if(optionName.isEmpty() || optionValue != null && optionValue.isEmpty()) {
- throw new IllegalArgumentException("Invalid argument syntax:" + arg);
- }
- commandLineArgs.addOptionArg(optionName, optionValue);
- } else {
- commandLineArgs.addNonOptionArg(arg);
- }
- }
- return commandLineArgs;
- }
从这段代码中我们看到 DefaultApplicationArguments 其实是读取了命令行的参数.
小发现: 通过分析这个函数的定义, 你是不是想起了 spring boot 启动的时候, 用命令行参数自定义端口号的情景?
java -jar MySpringBoot.jar --server.port=8000
接着往下看: ConfigurableEnvironment environment = this.prepareEnvironment(listeners, ex);
通过这行代码我们可以看到 spring boot 把前面创建出来的 listeners 和命令行参数, 传递到 prepareEnvironment 函数中来准备运行环境. 来看一下 prepareEnvironment 函数的真面目:
- private ConfigurableEnvironment prepareEnvironment(
- SpringApplicationRunListeners listeners,
- ApplicationArguments applicationArguments) {
- // Create and configure the environment
- ConfigurableEnvironment environment = getOrCreateEnvironment();
- configureEnvironment(environment, applicationArguments.getSourceArgs());
- listeners.environmentPrepared(environment);
- bindToSpringApplication(environment);
- if (this.webApplicationType == WebApplicationType.NONE) {
- environment = new EnvironmentConverter(getClassLoader())
- .convertToStandardEnvironmentIfNecessary(environment);
- }
- ConfigurationPropertySources.attach(environment);
- return environment;
- }
在这里我们看到了环境是通过 getOrCreateEnvironment 创建出来的, 再深挖一下 getOrCreateEnvironment 的源码:
- private ConfigurableEnvironment getOrCreateEnvironment() {
- if (this.environment != null) {
- return this.environment;
- }
- if (this.webApplicationType == WebApplicationType.SERVLET) {
- return new StandardServletEnvironment();
- }
- return new StandardEnvironment();
- }
通过这段代码我们看到了如果 environment 已经存在, 则直接返回当前的环境.
小思考: 在什么情况下会出现 environment 已经存在的情况? 提示: 我们前面讲过, 可以自己初始化 SpringApplication, 然后调用 run 函数, 在初始化 SpringApplication 和调用 run 函数之间, 是不是可以发生点什么?
下面的代码判断了 webApplicationType 是不是 SERVLET, 如果是, 则创建 Servlet 的环境, 否则创建基本环境. 我们来挖一挖 webApplicationType 是在哪里初始化的:
- private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
- + "web.reactive.DispatcherHandler";
- private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
- + "web.servlet.DispatcherServlet";
- /**
- * Create a new {@link SpringApplication} instance. The application context will load
- * beans from the specified primary sources (see {@link SpringApplication class-level}
- * documentation for details. The instance can be customized before calling
- * {@link #run(String...)}.
- * @param resourceLoader the resource loader to use
- * @param primarySources the primary bean sources
- * @see #run(Class, String[])
- * @see #setSources(Set)
- */
- @SuppressWarnings({ "unchecked", "rawtypes" })
- 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();
- }
- private WebApplicationType deduceWebApplicationType() {
- if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
- && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
- return WebApplicationType.REACTIVE;
- }
- for (String className : WEB_ENVIRONMENT_CLASSES) {
- if (!ClassUtils.isPresent(className, null)) {
- return WebApplicationType.NONE;
- }
- }
- return WebApplicationType.SERVLET;
- }
通过这段代码, 我们发现了原来 spring boot 是通过检查当前环境中是否存在
org.springframework.web.servlet.DispatcherServlet 类来判断当前是否是 web 环境的.
接着往下看, 获得了 ConfigurableEnvironment 环境以后, 通过后面的代码对环境进行 "微调".
通过 this.configureIgnoreBeanInfo(environment); 如果 System 中的 spring.beaninfo.ignore 属性为空, 就把当前环境中的属性覆盖上去:
- private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
- if(System.getProperty("spring.beaninfo.ignore") == null) {
- Boolean ignore = (Boolean)environment.getProperty("spring.beaninfo.ignore",
- Boolean.class, Boolean.TRUE);
- System.setProperty("spring.beaninfo.ignore", ignore.toString());
- }
- }
通过 Banner printedBanner = this.printBanner(environment); 这行代码打印出 spring boot 的 Banner. 还记得 spring boot 启动的时候, 在控制台显示的那个图片吗? 这里不作深究, 继续往下看:
context = this.createApplicationContext(); 创建了应用上下文:
- public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
- + "annotation.AnnotationConfigApplicationContext";
- public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
- + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
- public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
- + "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
- protected ConfigurableApplicationContext createApplicationContext() {
- Class<?> contextClass = this.applicationContextClass;
- if (contextClass == null) {
- try {
- switch (this.webApplicationType) {
- case SERVLET:
- contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
- break;
- case REACTIVE:
- contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
- break;
- default:
- contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
- }
- }
- catch (ClassNotFoundException ex) {
- throw new IllegalStateException(
- "Unable create a default ApplicationContext,"
- + "please specify an ApplicationContextClass",
- ex);
- }
- }
- return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
- }
通过这里我们看到, spring boot 是根据不同的 webApplicationType 的类型, 来创建不同的 ApplicationContext 的.
总结: 通过上面的各种深挖, 我们知道了 spring boot 2.0 中的环境是如何区分普通环境和 web 环境的, 以及如何准备运行时环境和应用上下文. 时间不早了, 今天就跟大家分享到这里, 下一篇文章会继续跟大家分享 spring boot 2.0 源码的实现.
来源: https://www.cnblogs.com/lizongshen/p/9136535.html