在上一篇文章中用三种方式实现了向Spring容器中注入自定义的属性
第一种实现方式源码:
在SpringBoot的启动类的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();
}
跟进getSpringFactoriesInstance(ApplicationContextInitializer.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;
}
先来看下第一个方法 Set names = new LinkedHashSet<>( SpringFactoriesLoader.loadFactoryNames(type, classLoader));
这个方法在SpringFactoriesLoader类中
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 factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
再来看第二个方法:List 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
}
第三个方法 AnnotationAwareOrderComparator.sort(instances); 就是根据@Order注解来进行排序了
最后会将这些实例全部放到SpringApplication类的变量 private List<ApplicationContextInitializer<?>> initializers;中去,所以在第二种实现方式中使用springApplication.addInitializers(new SecondInitializer()),其实本质也就是将这个实例添加到这个变量集合当中去
现在看下自定义初始化器如何执行的
这个是run()方法中
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
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;
}
我们进入到prepareContext(context, environment, listeners, applicationArguments,printedBanner);
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
进入到applyInitializers(context);方法中
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
在这里for循环中有一个getInitializers()方法,它的作用就是返回之前已经实例化好了我们自定义系统初始化器的集合
public Set<ApplicationContextInitializer<?>> getInitializers() {
return asUnmodifiableOrderedSet(this.initializers);
}
所以会依次调用 initializer.initialize(context); 这个方法,也就是我们实现了ApplicationContextInitializer这个接口需要实现的initialize()方法
第一,第二种方法我们已经知道Spring是如何将我们自定义属性添加到Spring容器中的,现在看下第三种方法
还记得第一种方式是如何找到我们自定义的初始化器类的嘛??不清楚可以看下文章开头,有一步是说实例化系统初始化器,我们看下返回的实例中有一个
没错就是DelegatingApplicationContextInitializer这个实例,在第三种方式中我们是在application.properties配置文件中配置了
context.initializer.classes=com.example.souce.initializer.ThirdInitializer
我们看下这个类的源码:我只截取了前面一部分
public class DelegatingApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
private static final String PROPERTY_NAME = "context.initializer.classes";
private int order = 0;
@Override
public void initialize(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
List<Class<?>> initializerClasses = getInitializerClasses(environment);
if (!initializerClasses.isEmpty()) {
applyInitializerClasses(context, initializerClasses);
}
}
private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
String classNames = env.getProperty(PROPERTY_NAME);
List<Class<?>> classes = new ArrayList<>();
if (StringUtils.hasLength(classNames)) {
for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
classes.add(getInitializerClass(className));
}
}
return classes;
}
系统初始化器就介绍完了,那看下相关的面试题
1:介绍下SpringFactoriesLoader:
它是Spring的一个通用的工厂加载类,SpringBoot用来它从jar包中读取指定文件来实现扩展类的载入
2:SpringFactoriesLoader是如何加载工厂类的
首先会根据指定路径找到文件,然后将文件转换成properties文件,然后依次遍历文件内容,并封装成类名以及实现类,并通过@Order注解进行排序
3:系统初始化的作用:
它其实是SpringBoot容器的一个回调接口,我们可以通过它向Spring容器中定义我们自己的属性
4:系统初始化调用的时机:
在run方法中的prepareContext()方法中的applyInitializers(context)调用
5:如何自定义实现系统初始化器:
在上一篇文章中已经实现了三种了
6:自定义初始化器需要注意的问题:
order大小排序的问题,还有一个就是泛型的问题