spring boot 2.0 源码分析(三)

 2019-11-02 21:19  阅读(726)
文章分类:Spring boot

通过上一章的源码分析,我们知道了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源码的实现。

在此我向大家推荐一个架构学习交流QQ群:725633148里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多!

原文出处:https://www.cnblogs.com/lizongshen/p/9136535.html


来源:http://ddrv.cn

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> spring boot 2.0 源码分析(三)

相关推荐