SpringBoot源码分析(8)--内置ApplicationContextInitializer

文章目录

  • 1、DelegatingApplicationContextInitializer
  • 2、SharedMetadataReaderFactoryContextInitializer
  • 3、ContextIdApplicationContextInitializer
  • 4、ConfigurationWarningsApplicationContextInitializer
  • 5、ServerPortInfoApplicationContextInitializer
  • 6、ConditionEvaluationReportLoggingListener
  • 7、RSocketPortInfoApplicationContextInitializer

本文基于spring-boot-2.2.14.BUILD-SNAPSHOT源码分析。

本篇文章是对上篇prepareContext的补充,在该方法的执行过程中,遍历了最初从META-INF/spring.factories文件中加载到的ApplicationContextInitializer,依次调用了其initialize方法。
在这里插入图片描述

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

SpringApplication初始化的时候,一共加载到了7个内置的ApplicationContextInitializer,本篇文章就逐一分析每个内置的初始化器做了哪些事情

spring-boot/src/main/resources/META-INF/spring.factories

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

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

1、DelegatingApplicationContextInitializer

获取环境中属性context.initializer.classesInitializer配置的ApplicationContextInitializer列表, 执行其initialize()方法。

SpringBoot允许我们通过各种属性配置方式自定义一些ApplicationContextInitializer,它们的调用时机就是该类的initialize方法

//代理初始化器
public class DelegatingApplicationContextInitializer implements
        ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
        
    private static final String PROPERTY_NAME = "context.initializer.classes";
    
    @Override
    public void initialize(ConfigurableApplicationContext context) {
        ConfigurableEnvironment environment = context.getEnvironment();
        //获取environment中配置的context.initializer.classes属性
        //其值为各个initializer,用逗号分隔开
        List<Class<?>> initializerClasses = getInitializerClasses(environment);
        if (!initializerClasses.isEmpty()) {
            //获取到各个initializer,并执行其initialize()方法
            applyInitializerClasses(context, initializerClasses);
        }
    }
            
}

进入getInitializerClasses方法

private static final String PROPERTY_NAME = "context.initializer.classes";

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

它从environment中找到context.initializer.classes属性,以逗号为分隔符解析成Class列表并返回

由于这里只通过getProperty方法取了一次,我们之前分析过,这个方法会从前往后遍历所有的PropertySource,取到了就立即返回,所以通过不同方式,比如启动参数、系统配置等不同途径设置的context.initializer.classes,只有优先级最高的那个会生效

然后对于找到的自定义初始化器,调用applyInitializerClasses方法

private void applyInitializerClasses(ConfigurableApplicationContext context, List<Class<?>> initializerClasses) {
	Class<?> contextClass = context.getClass();
	List<ApplicationContextInitializer<?>> initializers = new ArrayList<>();
	for (Class<?> initializerClass : initializerClasses) {
		initializers.add(instantiateInitializer(contextClass, initializerClass));
	}
	applyInitializers(context, initializers);
}

先实例化,然后调用applyInitializers方法

private void applyInitializers(ConfigurableApplicationContext context,
			List<ApplicationContextInitializer<?>> initializers) {
	initializers.sort(new AnnotationAwareOrderComparator());
	for (ApplicationContextInitializer initializer : initializers) {
		initializer.initialize(context);
	}
}

最终调用了它们的initialize方法

2、SharedMetadataReaderFactoryContextInitializer

添加了一个类型为CachingMetadataReaderFactoryPostProcessor的BeanFactoryPostProcessor, CachingMetadataReaderFactoryPostProcessor会做两件事情

  • 注册一个名称为internalCachingMetadataReaderFactory, 类型为SharedMetadataReaderFactoryBean的bean, 用于读取bean的元数据Metadata
  • 获取名称为internalConfigurationAnnotationProcessor, 类型为ConfigurationClassPostProcessor的bean定义, 为其添加name为metadataReaderFactory, value为internalCachingMetadataReaderFactory的internalCachingMetadataReaderFactory
class SharedMetadataReaderFactoryContextInitializer implements
        ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

    public static final String BEAN_NAME = "org.springframework.boot.autoconfigure."
            + "internalCachingMetadataReaderFactory";

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        //spring上下文容器添加一个CachingMetadataReaderFactoryPostProcessor
        applicationContext.addBeanFactoryPostProcessor(
                new CachingMetadataReaderFactoryPostProcessor());
    }
}

具体类型为CachingMetadataReaderFactoryPostProcessor,跟第一个初始化器的逻辑一样,它也是内部类,并且实现的也是BeanDefinitionRegistryPostProcessor接口,其中postProcessBeanFactory方法为空,所以只需要看其postProcessBeanDefinitionRegistry方法

private static class CachingMetadataReaderFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
   private CachingMetadataReaderFactoryPostProcessor() {
    }

	......
	......

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        this.register(registry);
        this.configureConfigurationClassPostProcessor(registry);
    }
    ......
}

首先register方法从内部类SharedMetadataReaderFactoryBean获取了一个BeanDefinition,并注册到了容器的BeanDefinitionMap中

private void register(BeanDefinitionRegistry registry) {
    BeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition(SharedMetadataReaderFactoryContextInitializer.SharedMetadataReaderFactoryBean.class, SharedMetadataReaderFactoryContextInitializer.SharedMetadataReaderFactoryBean::new).getBeanDefinition();
    registry.registerBeanDefinition("org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory", definition);
}

这个SharedMetadataReaderFactoryBean顾名思义,是一个FactoryBean,同时它实现了BeanClassLoaderAware接口,在这个接口的回调方法setBeanClassLoader中初始化了内部的ConcurrentReferenceCachingMetadataReaderFactory,并在getObject方法返回

static class SharedMetadataReaderFactoryBean implements FactoryBean<ConcurrentReferenceCachingMetadataReaderFactory>, BeanClassLoaderAware, ApplicationListener<ContextRefreshedEvent> {
    private ConcurrentReferenceCachingMetadataReaderFactory metadataReaderFactory;

    SharedMetadataReaderFactoryBean() {
    }

    public void setBeanClassLoader(ClassLoader classLoader) {
        this.metadataReaderFactory = new ConcurrentReferenceCachingMetadataReaderFactory(classLoader);
    }

    public ConcurrentReferenceCachingMetadataReaderFactory getObject() throws Exception {
        return this.metadataReaderFactory;
    }

返回的这个ConcurrentReferenceCachingMetadataReaderFactory,它可以生产一个MetadataReader,这个Reader的作用就是读取类的元数据,包括Class相关的信息,比如是否接口、是否抽象类、是否有注解等等,以及获得注解相关的元数据,比如加了哪些注解等等,在整个Bean的生命周期中起到了非常重要的作用

register方法执行完毕,调用configureConfigurationClassPostProcessor方法

private void configureConfigurationClassPostProcessor(BeanDefinitionRegistry registry) {
        try {
        BeanDefinition definition = registry.getBeanDefinition("org.springframework.context.annotation.internalConfigurationAnnotationProcessor");
        definition.getPropertyValues().add("metadataReaderFactory", new RuntimeBeanReference("org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory"));
    } catch (NoSuchBeanDefinitionException var3) {
        ;
    }
}

先从容器获取了名为internalConfigurationAnnotationProcessor的BeanDefinition,然后把上面生成的metadataReaderFactory设置到它的属性中

在新建Spring容器的时候,会初始化一个BeanDefinitionReader,而这个Reader的初始化过程中,会往容器中注册一个ConfigurationClassPostProcessor,名字就是上面的internalConfigurationAnnotationProcessor,它是Spring容器完成扫描的起点,包括@Component、@Configuration的处理都是在这个类中进行的,而完成这些工作,自然需要解析每个类的元数据,所以它把metadataReaderFactory赋给了ConfigurationClassPostProcessor的属性,后续就会用它来完成一些Bean的元数据解析

3、ContextIdApplicationContextInitializer

初始化容器ID,这个类的作用是给容器设置一个ID,其实就是我们的项目名, 获取属性spring.application.name配置的应用名称, 如果不存在的话, 默认使用application

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    //获取并设置容器ID
    ContextId contextId = getContextId(applicationContext);
    applicationContext.setId(contextId.getId());
    //容器beanFactory中注册一个名称为ContextId类名
    //值为contextId的bean
    applicationContext.getBeanFactory().registerSingleton(ContextId.class.getName(),
            contextId);
}

第一行先获取了一个容器ID对象,然后把ID属性设置到容器中,并把这个ID对象作为一个单例Bean注册到容器的单例池
我们看下这个ContextId怎么来的,进入getContextId方法

//获取ContextID
private ContextId getContextId(ConfigurableApplicationContext applicationContext) {
    //父容器获取spring.application.name对应的bean
    ApplicationContext parent = applicationContext.getParent();
    if (parent != null && parent.containsBean(ContextId.class.getName())) {
        return parent.getBean(ContextId.class).createChildId();
    }
    //父容器获取不到,则生成一个contextId
    return new ContextId(getApplicationId(applicationContext.getEnvironment()));
}

如果有父容器,就根据父容器的ID创建一个子容器ID,格式为 父容器ID - 子容器序号

ContextIdApplicationContextInitializer.ContextId createChildId() {
    return ContextIdApplicationContextInitializer.this.new ContextId(this.id + "-" + this.children.incrementAndGet());
}

如果没有父容器,就到environment中取spring.application.name属性,没有配置的话默认为application

//获取应用ID
private String getApplicationId(ConfigurableEnvironment environment) {
    //获取属性:spring.application.name
    String name = environment.getProperty("spring.application.name");
    //如果为空,默认名application
    return StringUtils.hasText(name) ? name : "application";
}

将取到的结果作为参数传给ContextId的构造函数

 //ContextId类
class ContextId {
    //原子Long类
    private final AtomicLong children = new AtomicLong(0);

    private final String id;

    ContextId(String id) {
        this.id = id;
    }

    ContextId createChildId() {
        //线程安全递增
        return new ContextId(this.id + "-" + this.children.incrementAndGet());
    }

    String getId() {
        return this.id;
    }
}

也就是说默认情况下,这个ContextId存了我们的项目名,然后把它设置到了容器中

4、ConfigurationWarningsApplicationContextInitializer

配置告警初始化器(通过分析源码实际情况,默认扫描启动类所在的路径(或者@ComponentScan注解指定的路径)如果系统配置包扫描到了org或者org.springframework包就会发出警告打印warn日志并停止系统启动

在这里插入图片描述
ConfigurationWarningsApplicationContextInitializer初始化器源码如下:

public class ConfigurationWarningsApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext> {

	private static final Log logger = LogFactory.getLog(ConfigurationWarningsApplicationContextInitializer.class);
	//初始化方法会在容器启动时调用,并将ConfigurationWarningsPostProcessor后置处理器注入到应用上下文中
	@Override
	public void initialize(ConfigurableApplicationContext context) {
		context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));
	}

	/**
	 * 返回有问题的扫描包(@ComponentScan)Check对象
	 * @return the checks to apply
	 */
	protected Check[] getChecks() {
		return new Check[] { new ComponentScanPackageCheck() };
	}
}

ComponentScanPackageCheck是ConfigurationWarningsApplicationContextInitializer的一个内部类,源码分析

/**
* 可以应用的单一检查
 */
@FunctionalInterface
protected interface Check {

	/**
	 * 返回检查结果,如果检查失败,则返回警告,如果没有问题,则返回null
	 * @param registry the {@link BeanDefinitionRegistry}
	 * @return a warning message or {@code null}
	 */
	String getWarning(BeanDefinitionRegistry registry);

}

/**
 * 检查@ComponentScan注解扫描有问题的包
 */
protected static class ComponentScanPackageCheck implements Check {

	private static final Set<String> PROBLEM_PACKAGES;
	//定义扫描有问题的包
	static {
		Set<String> packages = new HashSet<>();
		packages.add("org.springframework");
		packages.add("org");
		PROBLEM_PACKAGES = Collections.unmodifiableSet(packages);
	}
	
	//检查@ComponentScan注解扫描的包是否有问题,如果有,则返回警告,否则返回null
	@Override
	public String getWarning(BeanDefinitionRegistry registry) {
     //获取@ComponentScan注解扫描的包集合
		Set<String> scannedPackages = getComponentScanningPackages(registry);
     //获取有问题的扫描包集合
		List<String> problematicPackages = getProblematicPackages(scannedPackages);
		if (problematicPackages.isEmpty()) {
			return null;
		}
		return "Your ApplicationContext is unlikely to start due to a @ComponentScan of "
				+ StringUtils.collectionToDelimitedString(problematicPackages, ", ") + ".";
	}
	
	//获取@ComponentScan注解扫描的包
	protected Set<String> getComponentScanningPackages(BeanDefinitionRegistry registry) {
		Set<String> packages = new LinkedHashSet<>();
     //获取容器中所有bean定义名称
		String[] names = registry.getBeanDefinitionNames();
		for (String name : names) {
       //获取name对应的bean定义对象
			BeanDefinition definition = registry.getBeanDefinition(name);
			if (definition instanceof AnnotatedBeanDefinition) {
				AnnotatedBeanDefinition annotatedDefinition = (AnnotatedBeanDefinition) definition;
				addComponentScanningPackages(packages, annotatedDefinition.getMetadata());
			}
		}
		return packages;
	}
	
	//将bean实例上注解@ComponentScan扫描包
	private void addComponentScanningPackages(Set<String> packages, AnnotationMetadata metadata) {
		AnnotationAttributes attributes = AnnotationAttributes
				.fromMap(metadata.getAnnotationAttributes(ComponentScan.class.getName(), true));
		if (attributes != null) {
			addPackages(packages, attributes.getStringArray("value"));
			addPackages(packages, attributes.getStringArray("basePackages"));
			addClasses(packages, attributes.getStringArray("basePackageClasses"));
			if (packages.isEmpty()) {
				packages.add(ClassUtils.getPackageName(metadata.getClassName()));
			}
		}
	}

	private void addPackages(Set<String> packages, String[] values) {
		if (values != null) {
			Collections.addAll(packages, values);
		}
	}

	private void addClasses(Set<String> packages, String[] values) {
		if (values != null) {
			for (String value : values) {
				packages.add(ClassUtils.getPackageName(value));
			}
		}
	}
	
	//获取有问题的扫描包集合,即包名是:org或org.springframework
	private List<String> getProblematicPackages(Set<String> scannedPackages) {
		List<String> problematicPackages = new ArrayList<>();
		for (String scannedPackage : scannedPackages) {
       //判定包名是否有问题,即包名是:org或org.springframework
			if (isProblematicPackage(scannedPackage)) {
				problematicPackages.add(getDisplayName(scannedPackage));
			}
		}
		return problematicPackages;
	}
	//判定包名是否有问题,即包名是:org或org.springframework
	private boolean isProblematicPackage(String scannedPackage) {
		if (scannedPackage == null || scannedPackage.isEmpty()) {
			return true;
		}
		return PROBLEM_PACKAGES.contains(scannedPackage);
	}

	private String getDisplayName(String scannedPackage) {
		if (scannedPackage == null || scannedPackage.isEmpty()) {
			return "the default package";
		}
		return "'" + scannedPackage + "'";
	}
}

最终在控制台打印如下日志

** WARNING ** : Your ApplicationContext is unlikely to start due to a @ComponentScan of 'org'.

这里只是打印一行日志,并不会停止程序,不过实际测试下来程序是没办法正常启动的,这个路径是Spring框架本身的包路径,扫描这个包会干扰Spring正常执行流程,陷入循环,当然正常情况下我们项目的路径也不会这样定义。

5、ServerPortInfoApplicationContextInitializer

服务端口初始化器, 分别实现了ApplicationContextInitializer和ApplicationListener接口, 在applicationContext中添加了事件监听器this, 监听了WebServerInitializedEvent事件, 配置服务的端口号

public class ServerPortInfoApplicationContextInitializer
        implements ApplicationContextInitializer<ConfigurableApplicationContext>,
        ApplicationListener<WebServerInitializedEvent> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        //把this添加到Application的listener中
        applicationContext.addApplicationListener(this);
    }
    
    //监听处理WebServerInitializedEvent事件
    @Override
    public void onApplicationEvent(WebServerInitializedEvent event) {
        //获取environment中配置的server.ports
        String propertyName = "local." + getName(event.getApplicationContext()) + ".port";
        setPortProperty(event.getApplicationContext(), propertyName,
                event.getWebServer().getPort());
    }
}

6、ConditionEvaluationReportLoggingListener

条件评估日志监听器, 主要作用是给applicationContext添加了一个ConditionEvaluationReportListener监听器, ConditionEvaluationReportListener监听了ContextRefreshedEvent和ApplicationFailedEvent事件, 打印相应日志

public class ConditionEvaluationReportLoggingListener
        implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        //applicationContext中添加一个ConditionEvaluationReportListener
        applicationContext.addApplicationListener(new ConditionEvaluationReportListener());
        if (applicationContext instanceof GenericApplicationContext) {
            //注册一个ConditionEvaluationReport bean
            this.report = ConditionEvaluationReport
                    .get(this.applicationContext.getBeanFactory());
        }
    }
}

先往容器的监听器列表添加一个监听器ConditionEvaluationReportListener,这个类是其内部类,通过supportsEventType方法指定了感兴趣的事件类型为ContextRefreshedEvent和ApplicationFailedEvent

private class ConditionEvaluationReportListener implements GenericApplicationListener {
    private ConditionEvaluationReportListener() {
    }
......
......
    public boolean supportsEventType(ResolvableType resolvableType) {
        Class<?> type = resolvableType.getRawClass();
        if (type == null) {
            return false;
        } else {
            return ContextRefreshedEvent.class.isAssignableFrom(type) || ApplicationFailedEvent.class.isAssignableFrom(type);
        }
    }
    ......
}

具体在这两个事件做了什么处理,我们后面说到具体事件的时候再来分析

然后下面的if分支,当前Spring容器的类型是AnnotationConfigServletWebServerApplicationContext,它是GenericApplicationContext的子类,所以会进if分支,调用ConditionEvaluationReport的get方法

//用于记录Condition注解的评估情况
public final class ConditionEvaluationReport {

    //bean名称为autoConfigurationReport
    //类型为ConditionEvaluationReport
    private static final String BEAN_NAME = "autoConfigurationReport";
    
    //从beanFactory中获取ConditionEvaluationReport实例
    public static ConditionEvaluationReport get(
            ConfigurableListableBeanFactory beanFactory) {
        synchronized (beanFactory) {
            ConditionEvaluationReport report;
            if (beanFactory.containsSingleton(BEAN_NAME)) {
                //如果bean已经被注册,立即返回
                report = beanFactory.getBean(BEAN_NAME, ConditionEvaluationReport.class);
            }
            else {
                //否则注册bean
                report = new ConditionEvaluationReport();
                beanFactory.registerSingleton(BEAN_NAME, report);
            }
            locateParent(beanFactory.getParentBeanFactory(), report);
            return report;
        }
    }
}

先到容器中找名为autoConfigurationReport的单例Bean,如果没有的话就新建一个,并存储到容器的单例池,然后调用locateParent方法,如果存在父容器,检查父容器中有没有名为autoConfigurationReport的单例Bean,有的话,将父容器中的Report设置到当前Report的parent属性中

 private static void locateParent(BeanFactory beanFactory, ConditionEvaluationReport report) {
        if (beanFactory != null && report.parent == null && beanFactory.containsBean("autoConfigurationReport")) {
            report.parent = (ConditionEvaluationReport)beanFactory.getBean("autoConfigurationReport", ConditionEvaluationReport.class);
        }
    }

ConditionEvaluationReport的作用是在SpringBoot自动配置的过程中,打印一些匹配结果的DEBUG日志,包括哪些类完成了自动配置,哪些类的哪些条件没有满足而装载失败等等,比如下图中

============================
CONDITIONS EVALUATION REPORT
============================


Positive matches:
-----------------

   CodecsAutoConfiguration matched:
      - @ConditionalOnClass found required class 'org.springframework.http.codec.CodecConfigurer' (OnClassCondition)

   CodecsAutoConfiguration.JacksonCodecConfiguration matched:
      - @ConditionalOnClass found required class 'com.fasterxml.jackson.databind.ObjectMapper' (OnClassCondition)
      ......
      ......
      ......

Negative matches:
-----------------

   ActiveMQAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)

   AopAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'org.aspectj.lang.annotation.Aspect' (OnClassCondition)
       ......
       ......
       ......

7、RSocketPortInfoApplicationContextInitializer

添加了一个 RSocketServerInitializedEvent事件的 监听器到 ApplicationContext中。

public class RSocketPortInfoApplicationContextInitializer
		implements ApplicationContextInitializer<ConfigurableApplicationContext> {

	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		/**
		  * 注入一个端口检查和设置的监听器,对应的事件RSocketServerInitializedEvent
		  **/
		applicationContext.addApplicationListener(new Listener(applicationContext));
	}
    
    //这里直接写了个内部类实现RSocketServerInitializedEvent事件的监听
	private static class Listener implements ApplicationListener<RSocketServerInitializedEvent> {

		private static final String PROPERTY_NAME = "local.rsocket.server.port";

		private final ConfigurableApplicationContext applicationContext;

		Listener(ConfigurableApplicationContext applicationContext) {
			this.applicationContext = applicationContext;
		}

		@Override
		public void onApplicationEvent(RSocketServerInitializedEvent event) {
			if (event.getServer().address() != null) {
				setPortProperty(this.applicationContext, event.getServer().address().getPort());
			}
		}

		private void setPortProperty(ApplicationContext context, int port) {
			if (context instanceof ConfigurableApplicationContext) {
				setPortProperty(((ConfigurableApplicationContext) context).getEnvironment(), port);
			}
			if (context.getParent() != null) {
				setPortProperty(context.getParent(), port);
			}
		}

		private void setPortProperty(ConfigurableEnvironment environment, int port) {
			MutablePropertySources sources = environment.getPropertySources();
			PropertySource<?> source = sources.get("server.ports");
			if (source == null) {
				source = new MapPropertySource("server.ports", new HashMap<>());
				sources.addFirst(source);
			}
			setPortProperty(port, source);
		}

		@SuppressWarnings("unchecked")
		private void setPortProperty(int port, PropertySource<?> source) {
			((Map<String, Object>) source.getSource()).put(PROPERTY_NAME, port);
		}

	}

}

所有的这些初始化类都没有进行启动服务的实质性操作,都是通过注册对象,埋点,后面invokeBeanFactoryPostProcessors才真正调用初始化方法,而且在项目启动之前

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/64393.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PPT忘记密码如何解除?

PPT文件所带有的两种加密方式&#xff0c;打开密码以及修改权限&#xff0c;两种密码在打开文件的时候都会有相应的提示&#xff0c;但不同的是两种加密忘记密码之后是不同的。 如果忘记了打开密码&#xff0c;我们就没办法打开PPT文件了&#xff1b;如果是忘记了修改密码&…

解决github打不开的方法

解决github打不开的方法 本文参考文章&#xff1a;解决可ping通但无法访问github网站的问题 一、确定域名github.com的ip地址 进入网址 IP/服务器github.com的信息 - 站长工具 (chinaz.com)&#xff0c;查看 ip 地址。 20.205.243.166 github.com二、确定域名github.global.…

Node.js新手在哪儿找小项目练手?

前言 可以参考一下下面的nodejs相关的项目&#xff0c;希望对你的学习有所帮助&#xff0c;废话少说&#xff0c;让我们直接进入正题>> 1、 NodeBB Star: 13.3k 一个基于Node.js的现代化社区论坛软件&#xff0c;具有快速、可扩展、易于使用和灵活的特点。它支持多种数…

深度学习Redis(2):持久化

前言 在上一篇文章中&#xff0c;介绍Redis的内存模型&#xff0c;从这篇文章开始&#xff0c;将依次介绍Redis高可用相关的知识——持久化、复制(及读写分离)、哨兵、以及集群。 本文将先说明上述几种技术分别解决了Redis高可用的什么问题&#xff1b;然后详细介绍Redis的持…

Java课题笔记~6个重要注解参数含义

1、[掌握]Before 前置通知-方法有 JoinPoint 参数 在目标方法执行之前执行。被注解为前置通知的方法&#xff0c;可以包含一个 JoinPoint 类型参数。 该类型的对象本身就是切入点表达式。通过该参数&#xff0c;可获取切入点表达式、方法签名、目标对象等。 不光前置通知的方…

HBase Shell 操作

1、基本操作 1.1、进入HBase客户端命令行 前提是先启动hadoop集群和zookeeper集群。 bin/hbase shell 1.2、查看帮助命令 helphelp 查看指定命令的语法规则 查看 list_namespace 的用法&#xff08;‘记得加单引号’&#xff09; help list_namespace 2、namespace 我们…

HTML基础铺垫

&#x1f60a;HTML基础铺垫 &#x1f47b;前言&#x1f4dc;HTML文档结构&#x1f3ad;头部head&#x1f94f;标题title标记&#x1f94f;元信息meta标记 &#x1f3ad;主体body&#x1f94f;body标记&#x1f94f;body标记属性 &#x1f3ad;HTML基本语法&#x1f94f;标记类型…

Tomcat的介绍和安装配置、eclipse中动态web项目的创建和运行、使用IDEA创建web项目并运行

一、Tomcat的介绍和安装配置 安装tomcat&#xff1a; 环境变量的配置&#xff1a; 配置之后重启cmd&#xff0c;执行startup命令&#xff0c;启动tomcat 在localhost:8080&#xff0c;能进入tomcat主界面&#xff0c;说明配置成功 二、eclipse中动态web项目的创建和运行 tomca…

Web3 solidity编写交易所合约 编写ETH和自定义代币存入逻辑 并带着大家手动测试

上文 Web3 叙述交易所授权置换概念 编写transferFrom与approve函数我们写完一个简单授权交易所的逻辑 但是并没有测试 其实也不是我不想 主要是 交易所也没实例化 现在也测试不了 我们先运行 ganache 启动一个虚拟的区块链环境 先发布 在终端执行 truffle migrate如果你跟着我…

【车道线】TwinLiteNet 复现过程全纪录

码字不易&#xff0c;喜欢的请点赞收藏&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 论文全文翻译&#xff1a;【freespace】TwinLiteNet: An Efficient and Lightweight Model for Driveable Area and Lane Segmentation_莫克_Cheney的博客-CSDN博客 目录…

全面讲解最小二乘法

常见的最小二乘法我们就不多说了&#xff0c;下面主要介绍一下最小二乘法的一些先进方法。 正则化的最小二乘法 在使用常见的最小二乘法进行回归分析时&#xff0c;常常会遇到过拟合的问题&#xff0c;也就是在训练数据集上表现的很好&#xff0c;但是在测试数据集上表现的很…

如何解决 Elasticsearch 查询缓慢的问题以获得更好的用户体验

作者&#xff1a;Philipp Kahr Elasticsearch Service 用户的重要注意事项&#xff1a;目前&#xff0c;本文中描述的 Kibana 设置更改仅限于 Cloud 控制台&#xff0c;如果没有我们支持团队的手动干预&#xff0c;则无法进行配置。 我们的工程团队正在努力消除对这些设置的限制…

Oracle-expdp报错ORA-39077、06502(Bug-16928674)

问题: 用户在使用expdp进程导出时&#xff0c;出现队列报错ORA-39077、ORA-06502 ORA-31626: job does not exist ORA-31638: cannot attach to job SYS_EXPORT_SCHEMA_01 for user SYS ORA-06512: at "SYS.DBMS_SYS_ERROR", line 95 ORA-06512: at "SYS.KUPV$…

Windows下安装 Redis

目录 1.下载 1.1.Redis官网 1.2.github下载地址 2.安装步骤 2.1.解压Redis压缩包 2.2.创建临时服务 2.3.启动客户端 2.4.注册Redis服务 3.总结 4.致谢 1.下载 1.1.Redis官网 Download | Redis Redis 官方网站没有提供 Windows 版的安装包&#xff0c;但可以通过…

标准化归一化 batch norm, layer norm, group norm, instance norm

Layer Normalization - EXPLAINED (in Transformer Neural Networks) Layer Normalization - EXPLAINED (in Transformer Neural Networks) 0~4min:什么是multi-head attention 5~7min:layer norm图示 7~9min:公式举例layer norm 9:54-end:layer norm的代码示例 group n…

什么?200?跨域?

情景复现 今天我遇到了一件很奇怪的事情就是&#xff0c;当我请求后端网关&#xff0c;然后通过网关去请求相应的服务&#xff0c;都进行了跨域处理 但是&#xff0c;奇怪的是我在请求的时候&#xff0c;回来的响应码是200&#xff0c;但是报错了&#xff0c;报的还是200的同…

对等离子体的认识

认识等离子体等离子体的产生等离子体的定义等离子体并不远 认识等离子体 等离子体的产生 物质由一百多种元素组成&#xff0c;不同元素结合成各种分子。大量的分子又依靠某种作用力结合或凝聚在一起&#xff0c;由于作用力的强弱不等而表现为各种不同的存在状态。 物质的存在状…

Centos7克隆快速复制多台虚拟机|互通互联

背景&#xff1a;有时候&#xff0c;我们在用虚拟机的时候会用到多个进行使用。重新安装会花费大量的时间&#xff0c;此时&#xff0c;我们可以通过vmware虚拟机自带的功能快速克隆出完全相同的系统。 前提&#xff1a;被克隆的虚拟机系统要处于关闭状态 步骤&#xff1a;…

【图论】单源最短路

算法提高课笔记。&#xff08;本篇还未更新完… 目录 单源最短路的建图方式例题热浪题意思路代码 信使题意思路代码 香甜的黄油题意思路代码 最小花费题意思路代码 最优乘车题意思路代码 昂贵的聘礼题意思路代码 单源最短路的建图方式 最短路问题可以分为以下两类&#xff1a…

了解文档管理软件在团队协作中的作用

在团队协作中&#xff0c;文档管理软件发挥着重要的作用。文档管理软件是一种使团队成员可以共享、编辑、审查和保存各种文档的工具。它以一种结构化的方式存储和组织文档&#xff0c;提供了团队成员之间的协同工作和知识共享的平台。 文档管理软件提供了一个集中的库&#xf…