String

小说:银杏树苗多少钱一颗?作者:马开帝更新时间:2019-04-21字数:44276

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

当前文章:http://leetaemin.cn/news/20181045613.html

发布时间:2019-04-21 01:35:57

哪里有批发月季路西法小苗? 你一定不知国内最大,最好,最便宜雪松主产地在哪? 上千万棵现苗,金叶麦冬草海量现货直销中,找低价的来 高羊茅的种植季节是什么时候? 黑小麦种子什么时间种植比较好? 黑麦草与高羊茅有什么区别? 丹参的种子哪里能买到? 百慕大的最佳种植时间是什么时候? 多年生地被植物花卉有哪些? 多年生易种花卉有哪些?

69461 70577 85815 67361 38693 94150 76589 54785 91185 76219 26172 22321 37514 51754 13848 88227 69772 67511 41600 62380 82644 37263 50064

我要说两句: (0人参与)

发布