spring boot的一些简单源码随笔

目录

 

目的 

代码分析

流程总结 

springboot 的属性配置

SpringApplication中的prepareEnvironment() 方法

SpringApplication的prepareEnvironment()方法

spring profile


banner 的设置方式一共分为以下几种方式

  1. 兜底banner   
  2. 图片banner 
  3.  txt banner 
  4. 关闭banner

目的 

我们来查看源码分析banner的基本实现,从原理上把banner的配置弄明白,以后就不用死记硬背banner的配置了

代码分析

首先进到SpringApplication  的run() 方法,printBanner()这是我们banner的获取逻辑也是我们重点分析的地方。

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的获取逻辑也是我们重点分析的地方
			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;
	}

 如果banner为关闭模式则直接返回null  配置方式是在配置文件中使用spring.main.banner-mod=off进行关闭,下面判断是打印到控制台还是打印到日志,这里打印到日志和打印到控制台的逻辑基本上是一致的,故我们择一去探究即可。

private Banner printBanner(ConfigurableEnvironment environment) {
        //如果banner为关闭模式则直接返回null  配置方式是在配置文件中使用spring.main.banner-mod=off进行关闭
		if (this.bannerMode == Banner.Mode.OFF) {
			return null;
		}
//创建资源加载器
		ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
				: new DefaultResourceLoader(getClassLoader());
//创建对象
		SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
//是否打印到日志
		if (this.bannerMode == Mode.LOG) {
			return bannerPrinter.print(environment, this.mainApplicationClass, logger);
		}
//打印到控制台,这里打印到日志和打印到控制台的逻辑基本上是一致的,故我们择一去探究即可
		return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
	}

print() 这段代码简单明了,无非就是 获取banner   打印banner  将banner作为参数返回,我们了解了如何获取banner,我们自然而然的也就知道怎么配置banner了

//这段代码简单明了,无非就是 获取banner   打印banner  将banner作为参数返回,我们了解了如何获取banner,我们自然而然的也就知道怎么配置banner了

public Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
        
		Banner banner = getBanner(environment);
		banner.printBanner(environment, sourceClass, out);
		return new PrintedBanner(banner, sourceClass);
	}
private Banner getBanner(Environment environment) {
        //构造banner实例
		Banners banners = new Banners();
        //获取图片banner并添加到队列中
		banners.addIfNotNull(getImageBanner(environment));
        //获取textbanner 并添加到队列中
		banners.addIfNotNull(getTextBanner(environment));
    //返回封装所有banner的代理banner
		if (banners.hasAtLeastOneBanner()) {
			return banners;
		}
.        //如果没有设置banner返回兜底的banner
		if (this.fallbackBanner != null) {
			return this.fallbackBanner;
		}
        //否则返回默认的banner
		return DEFAULT_BANNER;
	}
//逻辑简单明了获取BANNER_IMAGE_LOCATION_PROPERTY 属性的value属性值,属性名称为spring.banner.image.location,根据value加载资源并构造banner 如果没有设置  BANNER_IMAGE_LOCATION_PROPERTY 属性则 查找classpath下banner.xxx的资源如果有则返回,xxx是什么呢, 具体如下{ "gif", "jpg", "png" };
private Banner getImageBanner(Environment environment) {
		String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY);
		if (StringUtils.hasLength(location)) {
			Resource resource = this.resourceLoader.getResource(location);
			return resource.exists() ? new ImageBanner(resource) : null;
		}
		for (String ext : IMAGE_EXTENSION) {
			Resource resource = this.resourceLoader.getResource("banner." + ext);
			if (resource.exists()) {
				return new ImageBanner(resource);
			}
		}
		return null;
	}
/text 的也简单明了,如果设置了BANNER_LOCATION_PROPERTY 则获取该value属性值,如果没有设置则有默认值 banner.txt,之后根据 location 进行资源的加载并返回
private Banner getTextBanner(Environment environment) {
		String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
		Resource resource = this.resourceLoader.getResource(location);
		if (resource.exists()) {
			return new ResourceBanner(resource);
		}
		return null;
	}

到此banner的获取基本上就分析返程了,

流程总结 

  1. 首先获取imagebanner    1 是否配置有spring.banner.image.location属性,如果有使用该属性值获取banner  2 根据banner.xxx 后缀名称可以是"gif", "jpg", "png"获取banner
  2. 获取text banner  1是否配置有spring.banner.location 属性 如果有根据该路径获取banner 2如果没有根据banner.txt 获取banner
  3. 否则           如果text  或 image 类型都查找不到banner  查看我们兜底的banner有没有设置,如果有使用兜底banner 如果没有使用SpringBootBanner

 

springboot 的属性配置

首先我们先明确一下springBoot中都定义了那些配置方式,这里我们直接粘贴处官网给出的列表清单

  1. Devtools global settings properties on your home directory (~/.spring-boot-devtools.properties when devtools is active).
  2. @TestPropertySource annotations on your tests.
  3. properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.
  4. Command line arguments.
  5. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
  6. ServletConfig init parameters.
  7. ServletContext init parameters.
  8. JNDI attributes from java:comp/env.
  9. Java System properties (System.getProperties()).
  10. OS environment variables.
  11. A RandomValuePropertySource that has properties only in random.*.
  12. Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants).
  13. Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants).
  14. Application properties outside of your packaged jar (application.properties and YAML variants).
  15. Application properties packaged inside your jar (application.properties and YAML variants).
  16. @PropertySource annotations on your @Configuration classes. Please note that such property sources are not added to the Environment until the application context is being refreshed. This is too late to configure certain properties such as logging.* and spring.main.* which are read before refresh begins.
  17. Default properties (specified by setting SpringApplication.setDefaultProperties).

一共17中属性配置方式,下面我们从代码角度分析,这些属性是从哪里加载进入environment的

 

SpringApplication中的prepareEnvironment() 方法

springapplication 的run 方法中,我们忽略掉与本次脉络无关的代码直接看下面的代码,进入prepareEnvironment,首先创建Environment 的实例,并依次根据如下逻辑将 6,7,8,9,10 属性配置填充到enviroment 中。

//springapplication 的run 方法中,我们忽略掉与本次脉络无关的代码直接看下面的代码
public ConfigurableApplicationContext run(String... args) {
	。。。。
//我们进入该方法
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			。。。。
	}
//environment 的核心处理方法就是在这里了	
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// 首先创建Environment 的实例
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		ConfigurationPropertySources.attach(environment);
		listeners.environmentPrepared(environment);
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
					deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}
//进入该方法逻辑也很简单就是查找当前是什么环境,如果是web环境就构建StandardServletEnvironment,web环境配置还多一些我们直接分析这个
private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
		switch (this.webApplicationType) {
		case SERVLET:
			return new StandardServletEnvironment();
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();
		}
	}
//在实例化该类对象的时候首先会依次调用父类的构造函数,我们直接看AbstractEnvironment的构造函数
public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
    public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
    public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
    public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";

    public StandardServletEnvironment() {
    }

    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
        propertySources.addLast(new StubPropertySource("servletContextInitParams"));
        if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
            propertySources.addLast(new JndiPropertySource("jndiProperties"));
        }

        super.customizePropertySources(propertySources);
    }

   
    }
}
public abstract class AbstractEnvironment implements ConfigurableEnvironment {

	//我们看到该构造函数会回调customizePropertySources函数
	public AbstractEnvironment() {
		customizePropertySources(this.propertySources);
	}
}
//视野回到 StandardServletEnvironment类的该函数,这是在父类构造函数中进行的回调我们看到进行了servletConfigInitParams,servletContextInitParams,Jndi相关属性的填充工作,之后调用父类的customizePropertySource() 函数
protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
        propertySources.addLast(new StubPropertySource("servletContextInitParams"));
        if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
            propertySources.addLast(new JndiPropertySource("jndiProperties"));
        }

        super.customizePropertySources(propertySources);
    }
//这里又依次加载了	systemEnvironment   systemProperties的属性配置
protected void customizePropertySources(MutablePropertySources propertySources) {
		propertySources.addLast(
				new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
		propertySources.addLast(
				new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
	}

到此为止,enviroment 中一共有了  6,7,8,9,10 属性配置

 

SpringApplication的prepareEnvironment()方法

我们将视野回到SpringApplication 类的 prepareEnvironment()方法中去进入configureEnvironment()中

	protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
		if (this.addConversionService) {
			ConversionService conversionService = ApplicationConversionService.getSharedInstance();
			environment.setConversionService((ConfigurableConversionService) conversionService);
		}
        //该方法将defaultProperties和 commandLineArgs 设置进入enviroment 中
		configurePropertySources(environment, args);
        
		configureProfiles(environment, args);
	}


protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
		MutablePropertySources sources = environment.getPropertySources();
		if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
			sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
		}
		if (this.addCommandLineProperties && args.length > 0) {
			String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
			if (sources.contains(name)) {
				PropertySource<?> source = sources.get(name);
				CompositePropertySource composite = new CompositePropertySource(name);
				composite.addPropertySource(
						new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
				composite.addPropertySource(source);
				sources.replace(name, composite);
			}
			else {
				sources.addFirst(new SimpleCommandLinePropertySource(args));
			}
		}
	}

最后调用监听器触发environmentPrepared() 事件

	public void environmentPrepared(ConfigurableEnvironment environment) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.environmentPrepared(environment);
		}
	}

会依次调用如下  ApplicationListener的实现类

//我们重点分析第一个监听器的回调接口,属性的填充逻辑基本上再该方法中
0 = {ConfigFileApplicationListener@1707}    
1 = {AnsiOutputApplicationListener@1708} 
2 = {LoggingApplicationListener@1696} 
3 = {ClasspathLoggingApplicationListener@1709} 
4 = {BackgroundPreinitializer@1710} 
5 = {DelegatingApplicationListener@1711} 
6 = {ParentContextCloserApplicationListener@1712} 
7 = {ClearCachesApplicationListener@1713} 
8 = {FileEncodingApplicationListener@1714} 
9 = {LiquibaseServiceLocatorApplicationListener@1715} 

 

这是 applicationlistener 的回调接口	
@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationEnvironmentPreparedEvent) {
            //
			onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
		}
		if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent(event);
		}
	}


	private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
        //从 spring.factories 文件中加载EnvironmentPostProcessor的类并实例化
		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
		postProcessors.add(this);
		AnnotationAwareOrderComparator.sort(postProcessors);
    //依次调用该刚发进行环境的处理,我们留意,本类也是一个EnvironmentPostProcessor 这个类型的的子类所以也会回调该子类的该方法,我们下面紧接着分析
		for (EnvironmentPostProcessor postProcessor : postProcessors) {
			postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
		}
	}

 

配置文件中现有的EnvironmentPostProcessor
CloudFoundryVcapEnvironmentPostProcessor (org.springframework.boot.cloud)
SpringApplicationJsonEnvironmentPostProcessor (org.springframework.boot.env)   
SystemEnvironmentPropertySourceEnvironmentPostProcessor (org.springframework.boot.env)          
ConfigFileApplicationListener (org.springframework.boot.context.config)   
SpringBootTestRandomPortEnvironmentPostProcessor (org.springframework.boot.test.web)

这里我们重点分析两个,第一个 SpringApplicationJsonEnvironmentPostProcessor (org.springframework.boot.env)   ,第二个  ConfigFileApplicationListener (org.springframework.boot.context.config)  .

//将SPRING_APPLICATION_JSON的配置属性添加进环境

public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
		MutablePropertySources propertySources = environment.getPropertySources();
		propertySources.stream().map(JsonPropertyValue::get).filter(Objects::nonNull).findFirst()
				.ifPresent((v) -> processJson(environment, v));
	}

public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
     @Override
	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
           //进入该方法
		addPropertySources(environment, application.getResourceLoader());

	}

}

protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
          //第一个方法进行随机值配置属性的填充
		RandomValuePropertySource.addToEnvironment(environment);
//  第二个方法的代码我们已经粘贴到下面了,这是进行properties 和yml 配置文件属性的填充
		new Loader(environment, resourceLoader).load();
	}

public void load() {
			this.profiles = new LinkedList<>();
			this.processedProfiles = new LinkedList<>();
			this.activatedProfiles = false;
			this.loaded = new LinkedHashMap<>();
			initializeProfiles();
			while (!this.profiles.isEmpty()) {
				Profile profile = this.profiles.poll();
				if (profile != null && !profile.isDefaultProfile()) {
					addProfileToEnvironment(profile.getName());
				}
				load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
				this.processedProfiles.add(profile);
			}
			resetEnvironmentProfiles(this.processedProfiles);
			load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
			addLoadedPropertySources();
		}

到此为止又有5, 11,12,13,14,15添加进入 enviroment 中,关于16  这个是在解析配置的时候进行的加载,到此为止基本上除了1,2,3外,属性配置都已经加载进入了enviroment中

spring profile

我们现在来分析一下spring profile 的加载机制,通过上面的分析,我们应该可以看出一些蛛丝马迹,我们直接到ConfigFileApplicationListener 的addPropertySources()方法中

protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
		RandomValuePropertySource.addToEnvironment(environment);
           //此处进行profile文件属性的具体加载工作
		new Loader(environment, resourceLoader).load();
	}

进入load方法

public void load() {
			this.profiles = new LinkedList<>();
			this.processedProfiles = new LinkedList<>();
			this.activatedProfiles = false;
			this.loaded = new LinkedHashMap<>();
//首先进入该方法
			initializeProfiles();
            //遍历profile
			while (!this.profiles.isEmpty()) {
				Profile profile = this.profiles.poll();
				if (profile != null && !profile.isDefaultProfile()) {
					addProfileToEnvironment(profile.getName());
				}
                //加载配置文件
				load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
				this.processedProfiles.add(profile);
			}
			resetEnvironmentProfiles(this.processedProfiles);
			load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
			addLoadedPropertySources();
		}
	private void initializeProfiles() {
			//首先添加一个null的profile 我们可以把它当做 application.propreties 或application.yml
			this.profiles.add(null);
//查看环境变量中是否存在spring.profiles.active或spring.profiles.include属性如果有的话封装为对象返回
			Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty();
			this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty));
			
			addActiveProfiles(activatedViaProperty);
		
//如果没有spring.profiles.active或spring.profiles.include的profile的话,添加一个defalut profile,名字为default,并设置为默认profile
	if (this.profiles.size() == 1) { // only has null profile
				for (String defaultProfileName : this.environment.getDefaultProfiles()) {
					Profile defaultProfile = new Profile(defaultProfileName, true);
					this.profiles.add(defaultProfile);
				}
			}
		}

那么剩下的问题就是我们在配置文件中使用的spring.profiles.include  和spring.profiles.active 引入的profile 是在哪里加载进来的,我们进入load()方法

private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
//这里会在指定的路径下面去查找指定名称的配置问价进行加载,可以通过spring.config.location和spring.config.additional-location属性修改配置文件位置,默认是classpath:/,classpath:/config/,file:./,file:./config/
			getSearchLocations().forEach((location) -> {
				boolean isFolder = location.endsWith("/");
                //获取要查找的name  可以通过spring.config.name进行配置,默认是application
				Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
				names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
			});
		}

我们进入load方法中

private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
				DocumentConsumer consumer) {
		...
			Set<String> processed = new HashSet<>();
			for (PropertySourceLoader loader : this.propertySourceLoaders) {
				for (String fileExtension : loader.getFileExtensions()) {
					if (processed.add(fileExtension)) {
//进入该方法
						loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
								consumer);
					}
				}
			}
		}
//最终都会调用load方法,我们进入该方法
private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
				Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
			DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
			DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
			if (profile != null) {
				// Try profile-specific file & profile section in profile file (gh-340)
				String profileSpecificFile = prefix + "-" + profile + fileExtension;
				load(loader, profileSpecificFile, profile, defaultFilter, consumer);
				load(loader, profileSpecificFile, profile, profileFilter, consumer);
				// Try profile specific sections in files we've already processed
				for (Profile processedProfile : this.processedProfiles) {
					if (processedProfile != null) {
						String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
						load(loader, previouslyLoaded, profile, profileFilter, consumer);
					}
				}
			}
			// Also try the profile-specific section (if any) of the normal file
			load(loader, prefix + fileExtension, profile, profileFilter, consumer);
		}
private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
				DocumentConsumer consumer) {
			try {
				Resource resource = this.resourceLoader.getResource(location);
			
				String name = "applicationConfig: [" + location + "]";
				List<Document> documents = loadDocuments(loader, name, resource);
				//此处将配置文件中的ActiveProfiles  和 IncludeProfiles分别加入到profiles中进行下次循环的调用
				List<Document> loaded = new ArrayList<>();
				for (Document document : documents) {
					if (filter.match(document)) {
						addActiveProfiles(document.getActiveProfiles());
						addIncludedProfiles(document.getIncludeProfiles());
						loaded.add(document);
					}
				}
				Collections.reverse(loaded);
				if (!loaded.isEmpty()) {
					loaded.forEach((document) -> consumer.accept(profile, document));
					if (this.logger.isDebugEnabled()) {
						StringBuilder description = getDescription("Loaded config file ", location, resource, profile);
						this.logger.debug(description);
					}
				}
			}
			catch (Exception ex) {
				throw new IllegalStateException("Failed to load property " + "source from location '" + location + "'",
						ex);
			}
		}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值