• spring boot 2.0 源码分析(三)


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

    作者:DSeven
    出处:http://www.cnblogs.com/lizongshen/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。

  • 相关阅读:
    Java中判断两个列表是否相等
    chrome:插件、跨域、调试....
    mac 开发环境采坑
    webpack升级踩坑
    js-使用装饰器去抖
    React setState 笔试题,下面的代码输出什么?
    react 解决:容器组件更新,导致内容组件重绘
    centos使用ngnix代理https
    javascript 理解继承
    js 数据监听--对象的变化
  • 原文地址:https://www.cnblogs.com/lizongshen/p/9136535.html
Copyright © 2020-2023  润新知