草庐IT

Springboot扩展点之ApplicationContextInitializer

凡夫贩夫 2023-08-19 原文

Springboot扩展点系列实现方式、工作原理集合:

Springboot扩展点之ApplicationContextInitializer

Springboot扩展点之BeanFactoryPostProcessor

Springboot扩展点之BeanDefinitionRegistryPostProcessor

Springboot扩展点之BeanPostProcessor

Springboot扩展点之InstantiationAwareBeanPostProcessor

Springboot扩展点之SmartInstantiationAwareBeanPostProcessor

Springboot扩展点之ApplicationContextAwareProcessor

Springboot扩展点之@PostConstruct

Springboot扩展点之InitializingBean

Springboot扩展点之SmartInitializingSingleton

Springboot扩展点之CommandLineRunner和ApplicationRunner

Springboot扩展点之FactoryBean

Springboot扩展点之DisposableBean

Springboot扩展点系列之终结篇:Bean的生命周期


前言        

        一提到Spring、Springoboot,很多人马上就会想到依赖注入、控制反转、自动装配、约定大于配置、使开发变得简单等等。但是如果仅仅会使用Springboot、SpringMVC完成一些增删改查,解决一些bug,那么实际上你并没有真的懂Spring、Springboot。Spring的核心是容器,Springboot更是封装了Spring,把复杂隐藏在内部,让其在使用上更简单,同时又预留了很多的扩展。所以我认为学会Springboot的简单使用只是一个开始,对业务开发更有参考和学习意义的是Springboot如何把复杂变得简单、预留的扩展接口又是如何使用的。

1. ApplicationContextInitializer的功能作用

  1. 在Spring容器初始化开始的时候,ApplicationContextInitializer接口的所有实现在类会被实例化;
  2. 在Spring容器刷新前,所有实现类的org.springframework.context.ApplicationContextInitializer#initialize方法会被调用,initialize方法的形参类型是ConfigurableApplicationContext,因此可以认为ApplicationContextInitializer实际上是Spring容器初始化前ConfigurableApplicationContext的回调接口,可以对上下文环境作一些操作,如运行环境属性注册、激活配置文件等
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

   /**
    * Initialize the given application context.
    * @param applicationContext the application to configure
    */
   void initialize(C applicationContext);

}

2. 三种实现方式

        下面通过一个具体实现类实现ApplicationContextInitializer接口,来分享一下其具体的实现方式。Springboot的扩展点ApplicationContextInitializer接口的实现主要分为两步:

1、实现ApplicationContextInitializer接口; 

        MyApplicationContextInitializer实现ApplicationContextInitializer接口,并打印一下系统相关属性的key、value

public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        Map<String, Object> systemProperties = applicationContext.getEnvironment().getSystemProperties();
        System.out.println("---------------start------------------");
        systemProperties.forEach((key,value)->{
            System.out.println("key:"+key+",value:"+value);
        }); System.out.println("---------------end------------------");
    }
}

2、把实现类注册到Spring容器中

把实现类MyApplicationContextInitializer注册到Spring容器中,主要有三种方式:

2.1 spring.factories

        在resources目录新建/META-INFI/spring.factories文件,并预置以下内容,即可完成自定义MyApplicationContextInitializer的注册;

org.springframework.context.ApplicationContextInitializer=com.fanfu.config.MyApplicationContextInitializer

2.2 application.properties

在application.properties文件中预置以下配置内容,即可完成自定义MyApplicationContextInitializer的注册;

context.initializer.classes=com.fanfu.config.MyApplicationContextInitializer

2.3 springApplication.addInitializers()

在springboot的启动类中,使用springApplication.addInitializers(new MyApplicationContextInitializer()),完成自定义MyApplicationContextInitializer的注册;

@SpringBootApplication
public class FanfuApplication {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(FanfuApplication.class);
        springApplication.addInitializers(new MyApplicationContextInitializer());
        springApplication.run(args);
    }
}

执行结果如下:(user.dir=项目的根目录)

3. 初始化时机

ApplicationContextInitializer接口的实现类的初始化,是在SpringApplication类的构造函数中,即spring容器初始化开始前,先通过 getSpringFactoriesInstances(...)得到所有实现类的集合,然后通过 setInitializers(...)注入到SpringApplication类的initializersn属性中;

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

4. 执行时机

ApplicationContextInitializer接口的实现类的执行时机是在org.springframework.boot.SpringApplication#prepareContext-->org.springframework.boot.SpringApplication#applyInitializers中,即spring容器正式刷新前,准备上下文环境时;

  1. getInitializers()得到,SpringApplication类的构造函数中通过 setInitializers(...)注入到SpringApplication类的initializersn属性中的所有ApplicationContextInitializer接口的实现类;
  2. 遍历这些实现类,并调用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);
   }
}

5. 内置的实现类

        Springboot内部也有一些内置的实现类,用于辅助Spring相关功能的实现,常见的实现类如下:

5.1 DelegatingApplicationContextInitializer

ApplicationContextInitializer的第二种实现方式(application.properties),在application.properties文件中配置context.initializer.classes=com.fanfu.config.MyApplicationContextInitializer,DelegatingApplicationContextInitializer的作用就是找到application.properties文件中配置的实现类实例化,并执行initialize()方法

5.2 ContextIdApplicationContextInitializer

ContextIdApplicationContextInitializer用于设置 Spring 应用上下文 ID,如果在application.properties中未设置spring.application.name,则默认为“application”

5.3 ConfigurationWarningsApplicationContextInitializer

ConfigurationWarningsApplicationContextInitializer用于报告 Spring 容器的一些常见的错误配置,可以看出,该初始化器为 context 增加了一个 Bean 的后置处理器。这个处理器是在注册 BeanDefinition 实例之后生效的,用于处理注册实例过程中产生的告警信息,其实就是通过日志打印出告警信息。

5.4 ServerPortInfoApplicationContextInitializer

ServerPortInfoApplicationContextInitializer除了实现了ApplicationContextInitializer接口外,还实现了ApplicationListener接口,ServerPortInfoApplicationContextInitializer作用就是把自己作为一个监听器注册到Spring的上下文环境中;

5.5 SharedMetadataReaderFactoryContextInitializer

实例化了一个org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer.CachingMetadataReaderFactoryPostProcessor对象并注册到了Spring的上下文环境中,CachingMetadataReaderFactoryPostProcessor是SharedMetadataReaderFactoryContextInitializer的一个内部类,这里就不具体展开了,有兴趣的小伙伴可以继续跟一下org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer.CachingMetadataReaderFactoryPostProcessor#postProcessBeanDefinitionRegistry方法了解一下具体的内容;

5.6 ConditionEvaluationReportLoggingListener

ConditionEvaluationReportLoggingListener从名字看是一个监听器,实际上不是,其内部类org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener.ConditionEvaluationReportListener才是实际的监听器,而ConditionEvaluationReportLoggingListener的作用就是把内部类ConditionEvaluationReportListener注册到Spring上下文环境中;

6. 总结

通过这篇文章,可以了解到:

第一,在Spring容器被刷新前,可以通过实现ApplicationContextInitializer接口对Spring上下文环境作一些配置或操作;

第二,ApplicationContextInitializer接口的实现方式有三种,可以根据项目需要选择合适的;

第三,了解了ApplicationContextInitializer接口实现类的初始化时机和执行时机,以及Springboot内置的具体实现类,可以学习到spring本身是如何利用扩展接口实现一些功能的,对实际的项目开发具有一定的参考意义。

示例代码地址:凡夫贩夫 / fanfu-web · GitCode(ApplicationContextInitializer分支)

有关Springboot扩展点之ApplicationContextInitializer的更多相关文章

  1. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  2. c - mkmf 在编译 C 扩展时忽略子文件夹中的文件 - 2

    我想这样组织C源代码:+/||___+ext||||___+native_extension||||___+lib||||||___(Sourcefilesarekeptinhere-maycontainsub-folders)||||___native_extension.c||___native_extension.h||___extconf.rb||___+lib||||___(Rubysourcecode)||___Rakefile我无法使此设置与mkmf一起正常工作。native_extension/lib中的文件(包含在native_extension.c中)将被完全忽略。

  3. ruby-on-rails - 向 Rails 3 添加 Ruby 扩展方法的最佳实践? - 2

    我有一个要在我的Rails3项目中使用的数组扩展方法。它应该住在哪里?我有一个应用程序/类,我最初把它放在(array_extensions.rb)中,在我的config/application.rb中我加载路径:config.autoload_paths+=%W(#{Rails.root}/应用程序/类)。但是,当我转到railsconsole时,未加载扩展。是否有一个预定义的位置可以放置我的Rails3扩展方法?或者,一种预先定义的方式来添加它们?我知道Rails有自己的数组扩展方法。我应该将我的添加到active_support/core_ext/array/conversion

  4. ruby - 如何在 ruby​​ 中复制目录结构,不包括某些文件扩展名 - 2

    我想编写一个ruby​​脚本来递归复制目录结构,但排除某些文件类型。因此,给定以下目录结构:folder1folder2file1.txtfile2.txtfile3.csfile4.htmlfolder2folder3file4.dll我想复制这个结构,但不包含.txt和.cs文件。因此,生成的目录结构应如下所示:folder1folder2file4.htmlfolder2folder3file4.dll 最佳答案 您可以使用查找模块。这是一个代码片段:require"find"ignored_extensions=[".cs"

  5. ruby - 扩展类和实例 - 2

    这个问题有两个部分。在RubyProgrammingLanguage一书中,有一个使用模块扩展字符串对象和类的示例(第8.1.1节)。第一个问题。为什么如果您使用新方法扩展类,然后创建该类的对象/实例,则无法访问该方法?irb(main):001:0>moduleGreeter;defciao;"Ciao!";end;end=>nilirb(main):002:0>String.extend(Greeter)=>Stringirb(main):003:0>String.ciao=>"Ciao!"irb(main):004:0>x="foobar"=>"foobar"irb(main):

  6. ruby - 动态扩展现有方法或覆盖 ruby​​ 中的发送方法 - 2

    假设我们有A、B、C类。Adefself.inherited(sub)#metaprogramminggoeshere#takeclassthathasjustinheritedclassA#andforfooclassesinjectprepare_foo()as#firstlineofmethodthenrunrestofthecodeenddefprepare_foo#=>prepare_foo()neededhere#somecodeendendBprepare_foo()neededhere#somecodeendend如您所见,我正在尝试将foo_prepare()调用注入

  7. ruby-on-rails - 如何扩展 Ruby Test::Unit 断言以包含 assert_false? - 2

    显然在Test::Unit中没有assert_false。您将如何通过扩展断言并添加文件config/initializers/assertions_helper.rb来添加它?这是最好的方法吗?我不想修改test/unit/assertions.rb。顺便说一句,我不认为这是多余的。我使用的是assert_equalfalse,something_to_evaluate。这种方法的问题是很容易意外使用assertfalse,something_to_evaluate。这将始终失败,不会引发错误或警告,并且会在测试中引入错误。 最佳答案

  8. ruby-on-rails - 无法构建 gem native 扩展 (mkmf (LoadError)) - Ubuntu 12.04 - 2

    这个问题在这里已经有了答案:Unabletoinstallgem-Failedtobuildgemnativeextension-cannotloadsuchfile--mkmf(LoadError)(17个答案)关闭9年前。嘿,我正在尝试在一台新的ubuntu机器上安装rails。我安装了ruby​​和rvm,但出现“无法构建gemnative扩展”错误。这是什么意思?$sudogeminstallrails-v3.2.9(没有sudo表示我没有权限)然后它会输出很多“获取”命令,最终会出现这个错误:Buildingnativeextensions.Thiscouldtakeawhi

  9. ruby-on-rails - 使用模块扩展带有 "has_many"的插件中的模型 - 2

    我在引擎样式插件中有一些代码,其中包含一些模型。在我的应用程序中,我想扩展其中一个模型。通过在初始值设定项中包含一个模块,我已经设法将实例和类方法添加到相关模型中。但是我似乎无法添加关联、回调等。我收到“找不到方法”错误。/libs/qwerty/core.rbmoduleQwertymoduleCoremoduleExtensionsmoduleUser#InstanceMethodsGoHere#ClassMethodsmoduleClassMethodshas_many:hits,:uniq=>true#nomethodfoundbefore_validation_on_crea

  10. ruby-on-rails - Ruby 1.9.3 -> 2.0 别名方法和扩展 - 2

    我正在尝试将Ruby1.9.3应用程序升级到2.0,除了一个小问题外,一切似乎都很顺利。我写了一个模块,我将其包含在我的模型中以覆盖activerecorddestroy。它将现有的destroy方法别名为destroy!,然后覆盖destroy以更改记录上的deleted_at时间戳。仅当我升级到ruby​​2.0时,destroy!不再破坏记录,但其行为就像我的新覆盖方法一样。知道为什么会这样吗?下面是更相关的代码部分。完整要点here.defself.included(base)base.class_evaldoalias_method:destroy!,:destroyalia

随机推荐