详解SpringBoot事件监听《SpringBoot-02》

SpringBoot有很多的evnet(事件),比如:ApplicationStartingEvent、ApplicationEnvironmentPreparedEven、ApplicationContextInitializedEvent、ApplicationPreparedEvent、ApplicationStartedEvent等。

这些事件会在Spring启动的不同节点触发,我们可以通过监听器来监听这些时间,从而再不同的节点做一些业务逻辑。

大家通常都使用@Component或者@Bean来注入监听器,然而有的事件是在Spring上下文之前触发的,这种方式没法触发。

接下来我们详细了解下SpringBoot监听器原理,你就懂会明白如何使用了。

从启动入口开始

Springboot监听器从启动一开始就开始工作了,我们先从入口来看:

@SpringBootApplication
public class SpringDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityDemoApplication.class, args);
    }
}

这是springboot的入口,我们从run方法进去,代码如下,又调用了当前类的一个子方法。

public class SpringApplication {
	private List<ApplicationContextInitializer<?>> initializers;
	private List<ApplicationListener<?>> listeners;

	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}

	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}
}

我们需要记住现在所说的所在的类是SpringApplication。然后run方法中一行代码有2个操作

先new SpringApplication(primarySources),然后用当前类的对象调用run方法。

primarySources是我们业务入口类,这个不需要关系。我们先看new SpringApplication这一步

new SpringApplication

点进去,你会看到调用了这个方法,还是在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));
        //判断webType
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		this.bootstrapRegistryInitializers = new ArrayList<>(
				getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

这个方法中,前面我标了注释的2行都很简单,可以自己看看(没有几行代码)。

重要的是第8、9、10、11行,第8行什么都没加载到,第11行加载了SpringBoot监听所需要的类。

这3行中,都使用了同一个方法:getSpringFactoriesInstances(xxx.class)。这里先说一下这个方法的作用:

getSpringFactoriesInstances从Spring所有的META-INFO/spring.factories文件中(自己的代码和引入的jar包都会扫描),寻找入参类型为key,所对应的value中所有的类,并为找到的这些类创建好对象。

如下代码,getSpringFactoriesInstances(ApplicationListener.class)会找到BackgroundPreinitializer,并创建该对象。

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

 getSpringFactoriesInstances详解

不想了解详细过程的可以跳过该部分内容。

还是当前类SpringApplication中,关键代码如下:

public class SpringApplication {
	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 = getClassLoader();
		//根据传入的类去找到所有对应的类名
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        //为找到的类创建对象
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

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

这段代码主要意思就是,先去META-INFO/spring.factories下找到所需的类,然后创建对象。

loadFactoryNames方法

找类的过程中,第9行,调用了SpringFactoriesLoader.loadFactoryNames方法,参数有2个,一个是所需类型,一个是类加载器。代码如下:

public final class SpringFactoriesLoader {
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        //确保类加载器不为null
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
        //获取类的类名
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

	private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        //先从缓存中获取,刚开始肯定是空的
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}
        //缓存没有,就初始化缓存
		result = new HashMap<>();
		try {
            //类加载器加载所有的META-INFO/spring.factories
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
            //循环
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
                //获取都单个spring.facories文件资源
				UrlResource resource = new UrlResource(url);
                //简析文件中所有的key value对,会有多个
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                //循环每一个,一个key会对应多个value
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    //类型名字
					String factoryTypeName = ((String) entry.getKey()).trim();
                    //value数组
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                    //给缓存(map)中放入,一个类型对应多个value
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			//缓存map中value去重,并且替换为不可修改的list
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}
}

整个加载文件的过程,注释已经标出,细细看一下,不难理解。

文件与结果对比如下:

 

 如图,最后结果就是按文件格式,key-value,一对多。该方法的前半部分loadSpringFactories,先将所有的文件读入缓存。然后根据所需的类型,找到我们需要的List。

return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());

 接下来就是上层方法用类名创建对象了,到此,getSpringFactoriesInstances就讲完了。

加载监听器

我们再回文章开头的SpringApplication类最开始的方法:

 这行现在就不难看出,给当前类的listeners属性赋值加载的监听器list,是刚刚从META-INFO/spring.factories中加载的。

理解监听器,我们不需要知道到底加载了那些对象,只需要知道他们都实现了这个接口:

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
	void onApplicationEvent(E event);
}

这个时候也能知道,我们可以通过刚才所说的添加文件的方式来添加监听器,实现这个接口就行。

Run方法

new SpringApplication(primarySources).run(args);的前半部分看完了,接下来看run方法。

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

我们只看这2行,第一行的方法,很简单,任然是从文件spring.factories中加载SpringApplicationRunListeners对应的value创建对象,然后作为参数new SpringApplicationRunListeners。

SpringApplicationRunListeners只对应一个EventPublishingRunListener

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

翻译一下代码就是:

	private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger,
				[new EventPublishingRunListener(types, this)],
				this.applicationStartup);
	}

整个方法涉及到的类关系如下:

 SpringApplicationRunListeners中的listeners中只包含一个EventPublishingRunListener,

EventPublishingRunListener中属性指向下一个类。如图以此类推。最后DefaultListenerRetriever中的applicationListeners包含了文章最开头加载的所有需要通知的监听器。DefaultListenerRetriever我们叫他监听检索器。

Starting事件

run方法中调用了starting方法,如下,starting方法中,循环该类中的listeners,调用每个listener的starting方法。

class SpringApplicationRunListeners {
	void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
		doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
				(step) -> {
					if (mainApplicationClass != null) {
						step.tag("mainApplicationClass", mainApplicationClass.getName());
					}
				});
	}

	private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
			Consumer<StartupStep> stepAction) {
		StartupStep step = this.applicationStartup.start(stepName);
		this.listeners.forEach(listenerAction);
		if (stepAction != null) {
			stepAction.accept(step);
		}
		step.end();
	}
}

从之前的类关系图中,我们知道,其实就是调用了EventPublishingRunListener的starting方法。

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
	@Override
	public void starting(ConfigurableBootstrapContext bootstrapContext) {
		this.initialMulticaster
				.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
	}
}

然后接着就是使用类关系图中的那几个类。

其中,EventPublishingRunListener中创建了ApplicationStartingEvent事件类型,之后就是使用监听检索器维护的list进行循环,用每个监听器去判断是否支持该事件(每个监听器都实现了统一的方法),支持则调用该监听器的onApplicationEvent方法。

总结:

本文以ApplicationStartingEvent事件为例子。

Spring启动最初,从文件中加载监听器,放入了图片中第3步骤所指的类(监听检索器)中,然后,再之后spring启动过程的第一步,调用starting方法,创建了ApplicationStartingEvent事件,用于监听器进行监听。

这个时候,Spring开没开始初始化,所以该事件的监听器不能通过@Component和@Bean进行注入,得通过文件的方式,或者调用SpringApplication提供的方法,addListeners放入。

欢迎留言讨论

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

七号公园的忧伤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值