跟我一起阅读SpringBoot源码(一)——SpringApplication的初始化

前言

一直以来都没有系统化的研究过源码,除了先前单独搭建Mybatis的一个demo研究了下MyBatis的源码,基本上就是遇到问题的时候去看下源码把问题解决了,然后就没然后了。当时研究MyBatis看得还起劲,了解了Mybatis初始化的时候干了什么,然后我们调用接口的时候他去匹配Mapper的逻辑,不过这么久过去了,还记得的就很少了。所以最近闲来没事儿干脆看看SpringBoot源码,并且写博客记录下,先不管能看多少,管他的写博客又不要钱。看多少写多少,后面回来再看博客还可以帮助下回忆。
注:其实后面可能研究的不单单是SpringBoot的源码,众所周知,SpringBoot只是一个盒子,把其他框架和中间件什么的进行整合了而已。还有,本人英语不太好,再阅读注释的时候又什么理解错误的地方还望指正。

搭建一个简单的SpringBoot demo

直接搭个很简单的demo来帮助阅读源码,建个Maven项目,引入依赖:

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

这里我把jdbc加进来了,所以再application.yml中配置下数据源吧:

spring:
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Hongkong
    username: root
    password: 123456

最后写个启动类:

@RestController
@SpringBootApplication
public class MyApplication {
    @Autowired
    JdbcTemplate jdbcTemplate;

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class,args);
    }

    @GetMapping("list")
    public String list(){
        String sql = "select * from user";
        List<User> userList = jdbcTemplate.query(sql, BeanPropertyRowMapper.newInstance(User.class));
        return JSONUtil.toJsonStr(userList);
    }
}

这样,一个springBoot项目就写好了,还顺便写了个接口去查下user信息,访问下http://localhost:8080/list看下效果:
在这里插入图片描述

开始研究

接下来我们开始研究SpringBoot的源码:

程序入口

public static void main(String[] args) {
        SpringApplication.run(MyApplication.class,args);
    }

众所周知,java代码的入口就是main方法,main里面就一行代码,SpringApplication.run
那我们先看看这个run方法吧。

	/**
	 * Static helper that can be used to run a {@link SpringApplication} from the
	 * specified source using default settings.
	 * 	大意就是:这个一个静态方法,可以用来以默认的配置和指定的source来启动SpringApplication
	 * @param primarySource the primary source to load    加载的主要source
	 * @param args the application arguments (usually passed from a Java main method) 应用程序参数(通常从Java主方法传递)
	 * @return the running {@link ApplicationContext}
	 */
	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}

从注释我们知道,这个方法就是默认配置启动SpringApplication用的,然后可以指定source,和输入一些参数,并且返回一个ConfigurableApplicationContext,那就是所我们项目启动后可以拿到一个ConfigurableApplicationContext,至于这个Context有什么,能干什么,我暂时不清楚,以前也没用过,先搁置。
接着追下去:

/**
	 * Static helper that can be used to run a {@link SpringApplication} from the
	 * specified sources using default settings and user supplied arguments.
	 * @param primarySources the primary sources to load
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return the running {@link ApplicationContext}
	 */
	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return `new SpringApplication(primarySources).run(args)`;
	}

诶,和上一个run的区别就是参数是数组,也就是说我们启动项目的时候可以指定多个sources,至于这个sources干什么用的,先不管,继续new SpringApplication(primarySources).run(args),这里慢点,先new了一个实例然后run with args,我们先看new的过程:

初始化SpringApplication实例

/**
	 * 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...)}.
	 * 创建一个新的SpringApplication实例,application contenxt将通过指定的sources来加载实例(beans);
	 *  SpringApplication class-level 文档有更详细描述,实例可以在调用run()之前定制化。
	 * @param primarySources the primary bean sources
	 * @see #run(Class, String[])
	 * @see #SpringApplication(ResourceLoader, Class...)
	 * @see #setSources(Set)
	 */
	public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}

说SpringApplication 的class-level有详细说明,注释太多,就不贴了,可以自己去看,上面一大串注释看得头都大了,不过还有大致看了下,收获如下:

  • 大多数情况下我们通过直接调用run的方式启动应用:
    就这种方式SpringApplication.run(MyApplication.class, args);
  • 为了更多的配置一个SpringApplication实例,我们可以在实例化后自己启动:
SpringApplication application = new SpringApplication(MyApplication.class);
// ... customize application settings here
application.run(args)
  • 还有其他种种,就不列举在这里了,遇到再说。

接着走:

/**
	 * 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;   //前面传过来是null
		Assert.notNull(primarySources, "PrimarySources must not be null"); //必须制定sources,用了断言,get
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); 
		//WebApplicationType是一个枚举,从方法名上大致猜是通过classpath来设置应用的类型吧。
		this.webApplicationType = WebApplicationType.deduceFromClasspath(); 
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

WebApplicationType

还是看看WebApplicationType 类:

/**
 * An enumeration of possible types of web application.
 *
 * @author Andy Wilkinson
 * @author Brian Clozel
 * @since 2.0.0
 */
public enum WebApplicationType {

	/**
	 * The application should not run as a web application and should not start an
	 * embedded web server.
	 *  应用不是web应用也不是嵌入的web服务
	 */
	NONE,

	/**
	 * The application should run as a servlet-based web application and should start an
	 * embedded servlet web server.
	 *  该应用程序应作为基于Servlet的Web应用程序运行,将启动一个嵌入的servlet web服务
	 */
	SERVLET,

	/**
	 * The application should run as a reactive web application and should start an
	 * embedded reactive web server.
	 * 这里没懂,reactive是什么意思?反应性的,百度了下都感觉和spring cloud相关,云?微服务?
	 */
	REACTIVE;

	private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" };

	private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";

	private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";

	private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

	private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";

	private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

	static WebApplicationType deduceFromClasspath() {
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

	static WebApplicationType deduceFromApplicationContext(Class<?> applicationContextClass) {
		if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
			return WebApplicationType.SERVLET;
		}
		if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
			return WebApplicationType.REACTIVE;
		}
		return WebApplicationType.NONE;
	}

	private static boolean isAssignable(String target, Class<?> type) {
		try {
			return ClassUtils.resolveClassName(target, null).isAssignableFrom(type);
		}
		catch (Throwable ex) {
			return false;
		}
	}

}

这里没看懂REACTIVE是什么意思,像看看我们现在配置这个的类型是什么,debug下:
debug先解决ClassLoader的问题,我们看到穿classLoader的地方一直传的null,所以追了下代码,有个getDefaultClassLoader方法,看看:
在这里插入图片描述
也就是说我们springBoot也可以说spring用的默认ClassLoader是AppClassLoader,接着看刚才的问题,
在这里插入图片描述
至此,我们知道了,我们现在的应用是一个servlet引用。接着看:

ApplicationContextInitializer

/**
	 * Sets the {@link ApplicationContextInitializer} that will be applied to the Spring
	 * {@link ApplicationContext}.
	 * @param initializers the initializers to set
	 */
	public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
		this.initializers = new ArrayList<>(initializers);
	}

设置一些初始化的东西(初始化器??翻译出来的),参数是集合,集合元素继承了ApplicationContextInitializer,我们先看看ApplicationContextInitializer是什么,在看输入参数。

/**
 * Callback interface for initializing a Spring {@link ConfigurableApplicationContext}
 * prior to being {@linkplain ConfigurableApplicationContext#refresh() refreshed}.
 *
 * <p>Typically used within web applications that require some programmatic initialization
 * of the application context. For example, registering property sources or activating
 * profiles against the {@linkplain ConfigurableApplicationContext#getEnvironment()
 * context's environment}. See {@code ContextLoader} and {@code FrameworkServlet} support
 * for declaring a "contextInitializerClasses" context-param and init-param, respectively.
 *
 * <p>{@code ApplicationContextInitializer} processors are encouraged to detect
 * whether Spring's {@link org.springframework.core.Ordered Ordered} interface has been
 * implemented or if the @{@link org.springframework.core.annotation.Order Order}
 * annotation is present and to sort instances accordingly if so prior to invocation.
 *
 * @author Chris Beams
 * @since 3.1
 * @param <C> the application context type
 * @see org.springframework.web.context.ContextLoader#customizeContext
 * @see org.springframework.web.context.ContextLoader#CONTEXT_INITIALIZER_CLASSES_PARAM
 * @see org.springframework.web.servlet.FrameworkServlet#setContextInitializerClasses
 * @see org.springframework.web.servlet.FrameworkServlet#applyInitializers
 */
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

	/**
	 * Initialize the given application context.
	 * @param applicationContext the application to configure
	 */
	void initialize(C applicationContext);

}

英语不好,看个大意也不知道对不对,ApplicationContextInitializer是一个用于ConfigurableApplicationContext.refresh()执行后的回调接口。应该就是用于动态的改变配置用的吧。就像我们一般写的缓存,然后有改变的时候执行的更新接口。
看下他的实现:
在这里插入图片描述
这么多,先不惹了,看下刚才的入参:((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)获取spring工厂实例??跟进过去,

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();  //应该就是AppClassLoader了
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

debug看了下,加载了7个初始化器。
在这里插入图片描述

loadFactoryNames

先加载工厂名称?还有注释说要确保名称唯一。

/**
	 * Load the fully qualified class names of factory implementations of the
	 * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
	 * class loader.
	 * 用给的ClassLoader从FACTORIES_RESOURCE_LOCATION中加载出工厂的实现类的全类名
	 * @param factoryType the interface or abstract class representing the factory
	 * @param classLoader the ClassLoader to use for loading resources; can be
	 * {@code null} to use the default
	 * @throws IllegalArgumentException if an error occurs while loading factory names
	 * @see #loadFactories
	 */
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

看下loadSpringFactories:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

在这里插入图片描述
在这里插入图片描述

原来是这里来的,spring-boot-2.2.5.RELEASE.jar!/META-INF/spring.factories,去找来看看:
在这里插入图片描述

看完后面的源码后觉得这里有问题,明明初始化器只有5个,加载出来却是7个,然后有看了下,原来URL是两个,另一个加载的配置文件在spring-boot-autoconfigure-2.2.5.RELEASE.jar!/META-INF/spring.factories里面。这样加起来刚好7个。[手动捂脸]
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

太多了,先不管了,反正现在知道了现在已经把这堆名称加载到内存里面了:
在这里插入图片描述
在这里插入图片描述

createSpringFactoriesInstances

好了names设起了,继续createSpringFactoriesInstances:

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
			ClassLoader classLoader, Object[] args, Set<String> names) {
		List<T> instances = new ArrayList<>(names.size());
		for (String name : names) {
			try {
				Class<?> instanceClass = ClassUtils.forName(name, classLoader);
				Assert.isAssignable(type, instanceClass);
				Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
				instances.add(instance);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
			}
		}
		return instances;
	}

这里有看头,通过classLoader+全类名实例化对象:

  • Class<?> instanceClass = ClassUtils.forName(name, classLoader); 先拿到Class。
  • Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); 拿到构造函数对象。
  • T instance = (T) BeanUtils.instantiateClass(constructor, args); 创建实例,

直呼牛批,这是以前没看到过的。

最后AnnotationAwareOrderComparator排个序,这里就不赘述AnnotationAwareOrderComparator,可以自己去看下,就是一个排序,注释巴拉巴拉一堆,什么为了更好的什么。

setListeners

参数(Collection) getSpringFactoriesInstances(ApplicationListener.class)逻辑和上面一样,就不看了,看下返回些什么吧。
在这里插入图片描述
看不懂,反正就是一些监听器的实现。
至此,一个SpringApplication的初始化完成了,接下来看应用如何启动运行吧。

传送门

跟我一起阅读SpringBoot源码(二)——SpringApplication启动

猜你喜欢

转载自blog.csdn.net/qq1049545450/article/details/112957659
今日推荐