springboot源码解析(一)

springboot源码解析

内容

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class SpringbootWebApp {

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

这是一个常见的springboot启动程序,首先进入run方法:

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

方法的第一个参数primarySource就是我们传入的启动类的Class对象,第二个可变参数就是main方法的命令参数

进入第二个重载方法run

	/**
	 * 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);
	}

再进入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...)}.
	 * @param primarySources the primary bean sources
	 * @see #run(Class, String[])
	 * @see #SpringApplication(ResourceLoader, Class...)
	 * @see #setSources(Set)
	 */
	public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}

继续进入下一个方法

	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 = WebApplicationType.deduceFromClasspath();
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

这个方法的第一个参数是空,第二个就是启动类对象,这个方法体做了以下事情:
1、设置SpringApplication对象的resourceLoader,这里设置的是空
2、把启动类对象包装成集合赋值给primarySources属性
3、推断应用类型,这里就是servletApplication类型
4、设置初始化器
5、设置监听器
6、设置main方法所在的类
除了五六两条,其他逻辑都很简单,点进去方法一看就知道了。
先看第五条:

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

先进入到方法setInitializers()里面:

	public void setInitializers(
			Collection<? extends ApplicationContextInitializer<?>> initializers) {
		this.initializers = new ArrayList<>();
		this.initializers.addAll(initializers);
	}

其实很简单,就是把传进来的参数设置到SpringApplication对象的集合属性里面,那么这些initializers是怎么来的呢?这个才是这里的重点。
进入到方法:

getSpringFactoriesInstances(
				ApplicationContextInitializer.class)

这个方法是SpringApplication通用的方法,就是从jar包里的所有spring.factory文件中读取所有的实现了ApplicationContextInitializer接口的类名称,并实例化。具体这个方法逻辑是怎样的呢?直接进入到方法体:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// 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;
	}

请看第二个方法体,它大概做了这么几件事情:
1、获取类加载器
2、从spring.factory文件里面加载对应类型的类的名字集合
3、实例化这些类
4、排序

接下来进入到SpringFactoriesLoader.loadFactoryNames(type, classLoader) 这个方法体内:

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
	}
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()) {
					List<String> factoryClassNames = Arrays.asList(
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
					result.addAll((String) entry.getKey(), factoryClassNames);
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

这两个方法都进入到了类SpringFactoriesLoader里面了
SpringApplication类把这个加载工作全权委托给了SpringFactoriesLoader。
其中这个类有个静态属性就是/** * The location to look for factories. * <p>Can be present in multiple JAR files. */ public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
通过第二个方法可以看出来,它就是通过类加载器加载类路径下面的名称为spring.factories的文件,由于可能有多个这样的文件,因为还可能依赖很多三方包,所以通过加载后得到的是一个url集合,然后遍历这个集合,把每个url指向的文件加载到内存:Properties properties = PropertiesLoaderUtils.loadProperties(resource);包装成了一个properties对象,properties大家就很熟悉了它继承map是一个kv的数据结构,然后把这些kv放入到MultiValueMap中返回。

然后根据加载的类的名称作为key,到这个MultiValueMap中获取到对应类的实例的集合再返回给上一级方法。

所以Set<String> names = new LinkedHashSet<>( SpringFactoriesLoader.loadFactoryNames(type, classLoader));这行代码其实就是从spring.factories文件中获取所有实现了某个接口的类名称字符串的集合。
接下来就是实例化这些类,所以分析List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); 这行代码。
进入方法体:

	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;
	}

这个方法就不用解释,太简单了,其实就是循环上一步得到的那些类的名称集合,然后通过反射实例化,放入另外一个集合中返回。

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 = WebApplicationType.deduceFromClasspath();
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

这个方法的setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); 这行代码就执行完了,一句话概括就是从所有spring.factories文件中获取实现ApplicationContextInitializer接口的类的名称,然后实例化加入到SpringApplication对象的属性initializers集合中。
至于

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

这行代码和上一行代码的逻辑完全一样,只不过它是把所有的
实现ApplicationListener接口的类实例化后加入到了listeners属性中,至此SpringApplication对象就实例化完成了。

总结

如果我们调用SpringApplication类的静态方法run,它会首先执行自身的实例化,然后再调用实例方法run。
本节主要分析了SpringApplication类的实例化源码。

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 = WebApplicationType.deduceFromClasspath();
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

总的来说,实例化过程做了一下工作:

1、把参数传入的资源加载器设置到属性中
2、设置主源类信息,作为后续加载beandefination的入口
3、推断本次程序的容器类型
4、设置初始化器
5、设置监听器
6、推断main方法所在的类

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值