spring boot 源码翻看

1、初始化的数据处理

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
		return new SpringApplication(sources).run(args);
	}

我们传过去的类和jvm启动参数最终以这个形式运行,做了两步,新建spring应用对象,然后run

2 新建spring应用对象

private void initialize(Object[] sources) {
	if (sources != null && sources.length > 0) {
		this.sources.addAll(Arrays.asList(sources));
	}
    // 这个类里定义了一个环境类数组,是两个字符串,"javax.servlet.Servlet",
	// 和"org.springframework.web.context.ConfigurableWebApplicationContext"
    // 这个deduceWebEnvironment的作用就是判断字符串代表的类能否通过classLoader加载
    // 返回布尔值
	this.webEnvironment = deduceWebEnvironment();
    // 将boot下spring.factories文件中的ApplicationContextInitializer接口的实现类实例化放到成员属性 initializers 中
	setInitializers((Collection) getSpringFactoriesInstances(
			ApplicationContextInitializer.class));
    // 将boot下同上文件夹中监听器实现类放到监听器成员属性中
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 返回启动类
	this.mainApplicationClass = deduceMainApplicationClass();
}

2.1 几个有趣的地方

2.1.1 断言异常

Assert.notNull(name, "Name must not be null");

public static void notNull(Object object, String message) {
	if (object == null) {
		throw new IllegalArgumentException(message);
	}
}

 这段代码的作用是判空,如果是空则抛出一个参数不合法的异常,message是第二参数
 常规都是用stringutil.isnotNull,这里用了异常觉得很有意思

在effective java中提到,不要滥用异常,异常的使用是非正常流程的情况下,也就是说遇到异常通常就是业务不能继续走下去了。

那么这里定义的异常断言,应该也是合情合理的,平时我们开发用了stringUtil去判空,如果为空可以处理的话使用没有问题,如果已经不能处理需要抛出异常给客户端的话,不妨使用这种断言异常。

2.1.2 map初始化容量

private static final Map, Class> primitiveTypeToWrapperMap = new IdentityHashMap, Class>(8);

private static final Map> primitiveTypeNameMap = new HashMap>(32);

private static final Map> commonClassCache = new HashMap>(32);

在翻看springboot源码过程中看到了他定义的hashmap都初始化了容量,那么就在想这个是不是对性能提升有优化,于是百度搜了下,确实有,详情参考 https://blog.csdn.net/a397525088/article/details/81112074

2.1.3 spring.factories加载和类加载机制

在初始化的第二句可以看到是

setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class));

一个初始化的内容,代码如下

private  Collection getSpringFactoriesInstances(Class type,
			Class[] parameterTypes, Object... args) {
    // 获取类加载器
	ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	// Use names and ensure unique to protect against duplicates
    // 获取工厂名
	Set names = new LinkedHashSet(
			SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 获取工厂实例
	List instances = createSpringFactoriesInstances(type, parameterTypes,
			classLoader, args, names);
    // 排个序 返回
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

这里要说的是获取工厂名,这段代码所在的jar包是spring-boot-1.5.14,调用的方法在spring-core-4.3.18里,是读取

meta-inf下名为spring.factorie的配置文件,一开始我以为是找我自己项目的metf-inf文件,没找到,后来就找spring-core里meta-inf中的文件,也没有这个叫factories的文件,最后在spring-boot里才找到,他调用的方法是

classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)

classLoader是从boot传到core里的加载器,所以最后确定ClassLoader里加载资源文件的根目录是他这个加载器所处的项目里..

据说这个是SPI机制,我就不去考究了继续撸源码。。

贴上配置文件里的数据 2.0.3版本的 应该都是有东西的类

#是描述

\结尾的那一行是接口

下面跟着的是实现接口的各个类

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor

# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer

# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter

2.1.4 堆栈

private Class deduceMainApplicationClass() {
	try {
		StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
		for (StackTraceElement stackTraceElement : stackTrace) {
			if ("main".equals(stackTraceElement.getMethodName())) {
				return Class.forName(stackTraceElement.getClassName());
			}
		}
	}
	catch (ClassNotFoundException ex) {
		// Swallow and continue
	}
	return null;
}

最后一行方法的详细代码就是上面这一段

new RuntimeException().getStackTrace();

这个方法可以获得当前调用的堆栈,一直遍历直到找到方法名为main的类,实例化返回,这里不知道为什么不将这个变量名直接传递过来而要使用这种遍历的方式。比如我这里返回的就是

com.example.demo.SmartPlatApplication

一直到这里,初始化的工作就完成了,后面是run的代码。

总结一下初始化的工作主要做的就是加载servlet类和config类,将启动类和监听器类加载到成员变量中,然后run。

3、run代码

// 2.03版本
public ConfigurableApplicationContext run(String... args) {
        // 计时器 计算启动时间
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
        // 用一个空的list初始化异常报告信息
		Collection exceptionReporters = new ArrayList<>();
        // 方法体内只有一行代码,启用headless包,swing方面使用
		configureHeadlessProperty();
        // 初始化实现SpringApplicationRunListener接口的监听器
        // 如果我们要实现这个接口,需要把对应类加载到factories中
        // 这个监听器在下面代码多个地方都有用
		SpringApplicationRunListeners listeners = getRunListeners(args);   
        // 运行加载好的监听器类的starting方法
		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;
	}

spring boot 源码翻看_第1张图片

在这里写的程序参数就会被读到如上数组中。

3.1几个有趣的地方

3.1.1 包装类,非public类,不用默认构造函数

类的默认修饰符和构造函数的覆盖关系虽然在学习java的前几天就了解到,但是实际应用中却很难找到真正使用的人或真正使用的地方,这里代码学习可以提供一个使用的场景供参考

在run代码的第6行,将factories中的所有实现SpringApplicationRunListener接口的类加载出来,但是返回的并不是一个普通的list,数组,或者单独一个对象,而是一个包装类。我觉得这应该是设计模式里的那个模式

看完这个代码的实际实现在后续研究设计模式时容易叫醒记忆方便学习

先看这个类的成员属性和构造函数

// 这里的类没有加public修饰符,表示使用默认修饰符default
// 那么这个类只可以在包内引用
// 代表两个含义:1封装,外部不可使用该包
// 2对于框架的使用者来说,这个类和你们没关系
class SpringApplicationRunListeners{
    // 和我平时使用的不同,这里log不是初始化静态log并且传递当前类的,而是在构造函数中传递过来
    private final Log log;

    private final List listeners;
    // 构造函数,也是外部调用他构造函数的唯一方式,不能通过默认构造函数去new
    SpringApplicationRunListeners(Log log,
	    	Collection listeners) {
	    this.log = log;
	    this.listeners = new ArrayList<>(listeners);
    }
}

看一下类的方法接口

    
    public void starting() {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.starting();
		}
	}

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

这里类做的只是循环遍历包含的元素,调用相应的方法,我觉得这可能是模式里一种

你可能感兴趣的:(java,spring,boot,源码解读)