博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
spring boot 2.0 源码分析(三)
阅读量:5321 次
发布时间:2019-06-14

本文共 9957 字,大约阅读时间需要 33 分钟。

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

你可能感兴趣的文章
decimal类型对象里面定义什么类型_分析,什么类型的结婚对象能与你终老?
查看>>
概率论在实际生活的例子_薰风AI知识点:Softmax与交叉熵的数学意义(信息论与概率论视角)...
查看>>
上海行政区划经纬度地图_爬取高德地图POI数据,GIS空间分析及可视化
查看>>
去重 属性_再谈JavaScript数组去重
查看>>
判断两个时间在15分钟内_DLP打印机可以在15分钟内打印出牙模
查看>>
如何提取明细表头_Excel如何提取客户第一次与最后一次出现的记录?字典1秒搞定...
查看>>
净水器多久_净水器滤芯多久换一次最好?
查看>>
删除一个单词_2021考研英语暑假复习经验分享!单词背会了吗?
查看>>
安卓系统录音怎么设置采样率_安卓手机便签敬业签怎么快速修改设置提醒的时间?...
查看>>
考上985能改变命运吗_有985实力,高考考砸只能去211,我该复读吗?复读一定会考上吗?...
查看>>
搭建kafaka_Kafka集群搭建
查看>>
python svm xml_svm+python实现(更新)
查看>>
九龙擒庄指标源码破译_九龙擒庄指标源码破译
查看>>
ant构建项目迁移到gradle_从 Gradle 使用 Ant
查看>>
mysql存储过程_MySql存储过程与函数详解
查看>>
会声会影2019渲染闪退_使用会声会影的五大理由,赶紧来看!
查看>>
js事件点击第二次才触发是为什么_JS防抖和节流傻傻分不清楚
查看>>
php mysql管理_PHP简单mysql管理工具,支持执行多条mysql语句!
查看>>
mysql客户端反向映射entity_Mysql服务端反向读取客户端的任意文件
查看>>
hbase 如何导入到mysql_hbase 的数据 怎么导出到 一个文件或者mysql里面
查看>>