springboot 启动过程三

用于源码分析的代码:Github
接着上一篇继续debug,这次看run方法里的源码,每次看的源码不贪多,慢慢嚼。还是首先列出自己的问题,带着问题看源码:

待解答的问题

  • 这段源码做了什么?
  • 为什么这么做?
  • 学到了哪些东西?

源码一

可以看出run方法里的代码有很好的可读性,用阿里java开发手册的原话来说,就是代码逻辑分清了红花和绿叶,个性和共性,绿叶逻辑单独出来成为额外方法,使主干代码更加清晰。由于这个run方法是Spring容器启动的主方法,包含的逻辑太多,所以这一节先debug完2.0的逻辑。

public ConfigurableApplicationContext run(String... args) {
    //StopWatch就是一个监控程序启动时间的类,start方法表示开始计时,stop方法表示计时结束
    //用于日志输出启动时间
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		// 设置一个环境变量,该环节变量标识当前程序是在没有显示器、鼠标等显示设备上运行的
		// 目的是为了能直接访问支持在无显示设备下的图形和文字处理对象的API,比如AWT的绘图API
		configureHeadlessProperty();
		// 1.0
		SpringApplicationRunListeners listeners = getRunListeners(args);
		// 2.0
		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;
	}  

源码1.0

获取配置的Listeners,我们继续看源码,来看看是怎么获取的。

	private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger,
		    // 源码1.1
				getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
	}   

源码1.1

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

这个就是从当前的classLoader所负责的路径下的jar包下的META-INF/spring.factories里读取配置SpringApplicationRunListener.class的完整类名,当前项目没有引入别的包含这个配置的jar,所以这里就是spring-boot包下的:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fT72NhgE-1596381806884)(quiver-image-url/91FA134290033C313EC7E12788EBBCFB.jpg =1413x793)]

这里要看下这个类的构造器里的类容,后面会用到这个构造器里传入的数据:

1、代码1.0处,实例化一个默认的多路广播对象initialMulticaster,负责将事件广播到监听这个事件的Listener

2、代码2.0处,将application里的监听器都放到这个多路广播里,application里的监听器是什么时候存储进来的呢? 在上一篇里有讲到,看这里
public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
		// 1.0
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
		// 2.0
		for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
	}

源码2.0

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

因为这里只有一个系统默认配置的EventPublishingRunListener,我们先看下SpringApplicationRunListener.class的接口定义,可以很清晰的看到在Spring启动过程中会发布7种事件:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BAWNfUPd-1596381806890)(quiver-image-url/A565215FC7CA7D26AFD0045DBBA976E6.jpg =1474x973)]

我们也可以按照规范来扩展自己的Listener(从类注释上可以看出,需要定义一个接收构造器,),此处先debug进这个类的starting事件中。


	@Override
	public void starting() {
		this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
	}

这里就是通过构造器里实例化的initialMulticaster来分发事件,在ApplicationStartingEvent的构造器里,会将当期那的application对象存起来,在后续通过event类型匹配相应的listener的逻辑里会用到,先继续debug进multicastEvent方法:

	@Override
	public void multicastEvent(ApplicationEvent event) {
		multicastEvent(event, resolveDefaultEventType(event));
	}

这里要看下resolveDefaultEventType(event)方法,这个方法返回的是ResolvableType.class类,这个类在后面会经常用到,所以这里看一下这个类的作用,我这边主要就是通过类的注释以及查看类的方法来了解这个类的主要用途,主要就是将java.lang.Class的方法做了进一步的封装,比如获取一类的父类、父接口、泛型信息、判断两个类是否是父子关系等等,就是与java.lang.Class对象相关的一些功能封装类,通过截图右侧的方法列表可以看到这些API。

在这里插入图片描述

进一步debug到multicastEvent方法:
@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		Executor executor = getTaskExecutor();
		// 2.1
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
			// 2.2
				invokeListener(listener, event);
			}
		}
	}
源码2.1

主要看下getApplicationListeners(event, type)方法,这个方法就是遍历Listener,通过event的类型来找到匹配的listener

protected Collection<ApplicationListener<?>> getApplicationListeners(
			ApplicationEvent event, ResolvableType eventType) {
    // 2.1.1
		Object source = event.getSource();
		Class<?> sourceType = (source != null ? source.getClass() : null);
		ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
    
		// Quick check for existing entry on ConcurrentHashMap...
		ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
		if (retriever != null) {
			return retriever.getApplicationListeners();
		}
    //2.1.2
		if (this.beanClassLoader == null ||
				(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
						(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
			// Fully synchronized building and caching of a ListenerRetriever
			// 2.1.3
			synchronized (this.retrievalMutex) {
				retriever = this.retrieverCache.get(cacheKey);
				if (retriever != null) {
					return retriever.getApplicationListeners();
				}
				retriever = new ListenerRetriever(true);
				// 2.1.4
				Collection<ApplicationListener<?>> listeners =
						retrieveApplicationListeners(eventType, sourceType, retriever);
				this.retrieverCache.put(cacheKey, retriever);
				return listeners;
			}
		}
		else {
			// No ListenerRetriever caching -> no synchronization necessary
			return retrieveApplicationListeners(eventType, sourceType, null);
		}
	}
源码2.1.1

这里会获取application对象,并且与eventType对象一起构成一个ListenerCacheKey,这个key就缓存到本地的retrieverCache这个ConcurrentHashMap中的key,value就是匹配的listener,这里有个知识点,就是通过hash存储key-value的hashMap,在定义一个用来作为key的对象时,都需要按业务需要重新定义hashcode和equals方法,因为hashMap是通过hashcode值来定位具体的槽的位置,在发生hash碰撞时,又是通过equals方法来定位链表位置,这个可以网上自己看下hashmap的原理,比如这里的ListenerCacheKey,就定义了自己的hashcode方法,并根据业务重写了equals方法:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ryiGUABV-1596381806892)(quiver-image-url/3DC4F9C652AC7AF111171F6ED8487003.jpg =1541x608)]

源码2.1.2

从retrieverCache中未获取到listener时,这里会判断是否有指定bean加载的类加载器,如果指定的话,会判断当前广播的event的类型是否是该指定的类加载器或者该加载器的父加载器加载的,要理解这个地方,需要熟悉java类加载器里的双亲委派原则,简单说就是如果不是一个类加载器加载的Class对象,即使类名一样,那也不是一个对象,所以此处需要做层校验

代码2.1.3

这里有个同步锁,对于synchronized有个知识点,retrieverCache是个线程安全的concurrentHashMap,但是这里有个复合操作,get+put两个操作,所以此处需要加锁。即:对于线程安全对象的复合操作,也需要加锁。

代码2.1.4

这个方法主要就是遍历Listener,通过事件类型eventType来匹配对应的Listener。

private Collection<ApplicationListener<?>> retrieveApplicationListeners(
			ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {

		List<ApplicationListener<?>> allListeners = new ArrayList<>();
		Set<ApplicationListener<?>> listeners;
		Set<String> listenerBeans;
		synchronized (this.retrievalMutex) {
		  // 2.1.5
			listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
			// 2.1.6
			listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
		}

		// Add programmatically registered listeners, including ones coming
		// from ApplicationListenerDetector (singleton beans and inner beans).
		for (ApplicationListener<?> listener : listeners) {
		// 2.1.7
			if (supportsEvent(listener, eventType, sourceType)) {
				if (retriever != null) {
					retriever.applicationListeners.add(listener);
				}
				allListeners.add(listener);
			}
		}

		// Add listeners by bean name, potentially overlapping with programmatically
		// registered listeners above - but here potentially with additional metadata.
		if (!listenerBeans.isEmpty()) {
			ConfigurableBeanFactory beanFactory = getBeanFactory();
			for (String listenerBeanName : listenerBeans) {
				try {
					if (supportsEvent(beanFactory, listenerBeanName, eventType)) {
						ApplicationListener<?> listener =
								beanFactory.getBean(listenerBeanName, ApplicationListener.class);
						if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
							if (retriever != null) {
								if (beanFactory.isSingleton(listenerBeanName)) {
									retriever.applicationListeners.add(listener);
								}
								else {
									retriever.applicationListenerBeans.add(listenerBeanName);
								}
							}
							allListeners.add(listener);
						}
					}
					else {
						// Remove non-matching listeners that originally came from
						// ApplicationListenerDetector, possibly ruled out by additional
						// BeanDefinition metadata (e.g. factory method generics) above.
						Object listener = beanFactory.getSingleton(listenerBeanName);
						if (retriever != null) {
							retriever.applicationListeners.remove(listener);
						}
						allListeners.remove(listener);
					}
				}
				catch (NoSuchBeanDefinitionException ex) {
					// Singleton listener instance (without backing bean definition) disappeared -
					// probably in the middle of the destruction phase
				}
			}
		}

		AnnotationAwareOrderComparator.sort(allListeners);
		if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {
			retriever.applicationListeners.clear();
			retriever.applicationListeners.addAll(allListeners);
		}
		return allListeners;
	}
源码2.1.5

此处的listener就是在通过类反射实例化EventPublishingRunListener对象时,在构造器里放入的。

源码2.1.6

此时这个listenerBeans还没有值,因为在构造EventPublishingRunListener对象时,没有调用addApplicationListenerBean方法。

源码2.1.7

supportsEvent(listener, eventType, sourceType),这个方法就是用来判断listener是否支持当前这个事件类型,sourceType就是我们最开始的SpringApplication.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hQLIbb9K-1596381806896)(quiver-image-url/3BCC05B2027A1B2A46D1F3BCF4D41852.jpg =1002x674)]

protected boolean supportsEvent(
			ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {

		GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
				(GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener)); //2.1.8
		return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
	}

这里可以看下GenericApplicationListener接口定义及类图,可以看出这个类继承自ApplicationListener,且定义了两个用来做匹配逻辑的接口,也就是说我们在扩展自己的监听器时,可以继承自这个接口,这样就能实现自己的匹配逻辑。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7jQRkoDg-1596381806897)(quiver-image-url/6F900936996F3BCC9724FEB979FFCA02.jpg =1378x644)]

看下GenericApplicationListenerAdapter类里的supportsEventType方法,需要知道当前listener是否支持当前这个event,就必须知道当前listener支持的event是那种类型,这里就要看下GenericApplicationListenerAdapter的构造器,也就是代码2.1.8处。

public GenericApplicationListenerAdapter(ApplicationListener<?> delegate) {
		Assert.notNull(delegate, "Delegate listener must not be null");
		this.delegate = (ApplicationListener<ApplicationEvent>) delegate;
		// 2.1.9
		this.declaredEventType = resolveDeclaredEventType(this.delegate);
	}
源码2.1.9

此处就是从listener里解析出这个listener支持哪种事件

@Nullable
	private static ResolvableType resolveDeclaredEventType(ApplicationListener<ApplicationEvent> listener) {
	// 2.1.9.1
		ResolvableType declaredEventType = resolveDeclaredEventType(listener.getClass());
		if (declaredEventType == null || declaredEventType.isAssignableFrom(ApplicationEvent.class)) {
			Class<?> targetClass = AopUtils.getTargetClass(listener);
			if (targetClass != listener.getClass()) {
				declaredEventType = resolveDeclaredEventType(targetClass);
			}
		}
		return declaredEventType;
	}
源码2.1.9.1

这里就是通过Listener的类信息找到支持的event,哪为什么能从Listener的类信息里判断出所能支持的eventType呢?我们先看下ApplicationListener接口定义就能明白:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zyftqomI-1596381806898)(quiver-image-url/95501F5F9384F35123B0A46E04270007.jpg =988x459)]

可以看到接口里用到了泛型,那么在扩展这个接口时,就需要在类定义上也明确注明类型或者使用默认ApplicationEvent,而Class对象的getGenericInterfaces()方法能获取当前类对象带有泛型的类型信息。继续通过debug来验证这个逻辑:

static ResolvableType resolveDeclaredEventType(Class<?> listenerType) {
		ResolvableType eventType = eventTypeCache.get(listenerType);
		if (eventType == null) {
		// 2.1.9.1.1
			eventType = ResolvableType.forClass(listenerType).as(ApplicationListener.class).getGeneric();
			eventTypeCache.put(listenerType, eventType);
		}
		return (eventType != ResolvableType.NONE ? eventType : null);
	}

这里以我们debug进2.1.9.1.1的逻辑.
我们跳过ResolvableType.forClass(listenerType)的逻辑,这个就是将listener的Class对象转成ResolvableType对象,主要在as(ApplicationListener.class)方法里,这里的listener是AnsiOutputApplicationListener,我们要通过这个类的泛型信息推导出所支持的事件类型是ApplicationEnvironmentPreparedEvent.class,类定义及类图信息如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vfYpDPnF-1596381806899)(quiver-image-url/911395776F31330D4E4FE7AAC28FDE65.jpg =856x368)]

as(ApplicationListener.class)源码如下:

public ResolvableType as(Class<?> type) {
		if (this == NONE) {
			return NONE;
		}
		// 获取listener的Class对象
		Class<?> resolved = resolve();
		// 如果Class对象与给定的type相同就返回
		if (resolved == null || resolved == type) {
			return this;
		}
		// getInrerfaces()方法就是获取当前类的带有泛型信息的父接口
		for (ResolvableType interfaceType : getInterfaces()) {
		// 把父接口包装成ResolvableType后递归调用
			ResolvableType interfaceAsType = interfaceType.as(type);
			if (interfaceAsType != NONE) {
				return interfaceAsType;
			}
		}
		return getSuperType().as(type);
	}

通过as(ApplicationListener.class)方法上的注释可以知道,该方法的作用就是在当前这个类的父类和父接口里找到给定的那个类的包含泛型的类型信息,以AnsiOutputApplicationListener为例,这个as(ApplicationListener.class)方法,就是从它的父类和父接口中找出带有泛型的ApplicationListener.class,也就是ApplicationListener.class
先开始进入as方法的getInterfaces()方法的debug信息:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IWe7Rjnh-1596381806900)(quiver-image-url/6559FC909025B14B02705FD86BE9C546.jpg =1346x689)]

这里就是通过Class类的getGenericInterfaces()方法获取带有泛型的类型信息,然后递归调用as方法:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QyM1JISj-1596381806901)(quiver-image-url/74FA420B0C2C7627CBCD28FE4DEFD0B8.jpg =1358x776)]![在这里插入图片描述

下面是递归调用后的debug截图,这里由于ApplicationListener.class的类型信息就是ApplicationListener.Class,所以就会返回:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hHN7aXRp-1596381806902)(quiver-image-url/43A11F582D77FBE661B408428BD889F8.jpg =1349x764)]

然后通过getGeneric()方法来获取ApplicationListener.class尖括号里的泛型,即:ApplicationEnvironmentPreparedEvent.class:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H0Xlvwqg-1596381806902)(quiver-image-url/53FF5F3C925B44C2DFD7A6002325D949.jpg =1101x796)]

这里能看到获取尖括号里的泛型主要是通过ParameterizedType类的getActualTypeArguments()方法获取的,在debug这个方法前,需要将idea里的一个debug配置项给去掉:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PZwDW3Ub-1596381806903)(quiver-image-url/9D3B59CC63BD6436B3DFF9198489D777.jpg =1241x672)]

这个Enable 'toString()'主要是禁止idea在debug时去主动调用类的toString方法,这里需要去掉这个选项是因为ResolvableType类的toString()方法里调用了这个getGeneric()方法,会让我们debug到这一步时,会很困惑这个类的generics属性是什么时候被赋值的…,到了这里已经清楚Spring启动过程中的starting事件,是如何匹配到对应的Listener的了。
现在回到匹配Listenner逻辑的入口代码2.1处继续debug

@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		Executor executor = getTaskExecutor();
		// 2.1
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
			// 2.2
				invokeListener(listener, event);
			}
		}
	}
源码2.2

由于没有使用线程池executor异步执行listener,所以继续debug金2.2的逻辑,这里就是直接执行Listener的事件处理逻辑。

@SuppressWarnings({"rawtypes", "unchecked"})
	private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		try {
			listener.onApplicationEvent(event);
		}
		catch (ClassCastException ex) {
			String msg = ex.getMessage();
			if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
				// Possibly a lambda-defined listener which we could not resolve the generic event type for
				// -> let's suppress the exception and just log a debug message.
				Log logger = LogFactory.getLog(getClass());
				if (logger.isTraceEnabled()) {
					logger.trace("Non-matching event type for listener: " + listener, ex);
				}
			}
			else {
				throw ex;
			}
		}
	}

总结及思考

回到开头的三个问题:

这段源码做了什么?

这部分代码主要将Spring启动过程中,对外发布starting事件,怎么将事件广播给Listenner,并匹配对应的Listener来执行逻辑的。

为什么这么做?

知道做了什么,也就理解了为什么了。在主干逻辑的事件节点上去执行其他的子逻辑时,使用这种事件广播+监听者的模式技能很好的将子逻辑与主干逻辑解耦,同时也比较容易扩展。这里就是在Spring启动的starting阶段去执行一些早起的初始化工作、提前异步加载一些全局对象,拿BackgroundPreinitializer为例,就是异步执行了日志、转化服务、校验服务等的初始化工作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wnYFsyd8-1596381806904)(quiver-image-url/ED8DF8B20929DE7E13AD46A52C1FD6FC.jpg =1114x518)]

学到了哪些东西

  • 事件广播+监听者模式,在这种需要主干逻辑的一些关键节点上去执行和扩展一些子逻辑的场景时,可以选择这种设计模式来做解耦和扩展
  • 我们可以定义自己的Listener来监听Spring启动过程中的7种事件(SpringApplicationRunListener接口里清晰定义了7种事件的发布方法),而扩展的方式就是定义自己的Listener,并实现ApplicationListener接口,然后将自己的Listener配置到META-INF/spring.factories配置文件中。
  • 学到了一些关于获取泛型信息的一些api
    • Class类的getGenericInterfaces()方法获取带有泛型信息的父接口的Type对象
    • Class类的getGenericSuperclass()方法获取带有泛型信息的父类的Type对象
    • ParameterizedType的getActualTypeArguments()方法可以将尖括号里的泛型信息提取出来
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值