找回密码
 立即注册
首页 业界区 安全 SpringBoot--配置加载、分析器、监听器、初始化器等 ...

SpringBoot--配置加载、分析器、监听器、初始化器等

蒲善思 2025-9-24 14:27:51
SpringApplication是SpringBoot提供的一个工具类,它提供了run()方法来启动Spring容器,运行SpringBoot应用。
类配置与XML配置

传统Spring大多使用XML进行配置,但SpringBoot使用java类(带@Configuration注解)进行配置。
如果希望SpringBoot能加载其他配置类或扫描其他包下的配置类或bean组件可使用如下注解:
@Import:显式指定要加载的配置类
@ComponentScan:指定SpringBoot扫描指定包及其子包下所有的配置类或bean组件。
@ImportResource:导入XML配置。
  1. // 额外指定扫描org.crazyit.app和org.fkit.app包及其子包下所有配置类和Bean组件
  2. @SpringBootApplication(scanBasePackages = {"org.crazyit.app", "org.fkit.app"})
  3. // 加载类加载路径下beans.xml文件作为配置文件
  4. @ImportResource("classpath:beans.xml")
  5. // 加载cn.fkjava.app包下的MyConfig文件作为配置类
  6. @Import(cn.fkjava.app.MyConfig.class)
  7. public class App
  8. {
  9.    public static void main(String[] args)
  10.    {
  11.       // 创建Spring容器、运行Spring Boot应用
  12.       SpringApplication.run(App.class, args);
  13.    }
  14. }
复制代码
启动日志与失败分析器

使用SpringApplication的run方法启动应用,默认显式info级别的日志,如果想关闭启动信息日志,则可设置为
spring.main.log-startup-info=false
上面的配置也会关闭活动Profile日志
将日志级别设为debug
java -jar firstboot-0.0.1-snapshot.jar --debug  // jar包启动时设置
在application.properties添加配置
  1. logging.level.org.springframework.boot.autoconfigure.logging=debug
复制代码
虽然SpringBoot内置大量的失败分析器,但依然允许开发者注册自己的失败分析器,以下是开发自定义的失败分析器
  1. // 自定义失败分析器应继承AbstractFailureAnalyzer<T> ,泛型T代表失败分析器要处理的异常
  2. public class MyAnalyzer extends AbstractFailureAnalyzer<BindException>
  3. {
  4.    // 重写analyze()方法,分析异常
  5.    // 返回值FailureAnalysis代表分析结果,如果不想分析异常,可以返回null
  6.    // 返回值FailureAnalysis的本质上是一个异常分析结果的描述信息,包含三个信息
  7.    // 1. 错误信息(错误的原因,第一个参数)
  8.    // 2. 修复建议(如何解决错误,第二个参数)
  9.    // 3. 导致失败的异常(导致错误的异常,第三个参数)
  10.    @Override
  11.    public FailureAnalysis analyze(Throwable rootFailure, BindException cause)
  12.    {
  13.       cause.printStackTrace();
  14.       return new FailureAnalysis("程序启动出错,程序绑定的端口被占用:"
  15.             + cause.getMessage(),
  16.             "请先停止占用8080端口的程序后再运行本应用或使用" +
  17.                   "server.port改变本应用的端口", cause);
  18.    }
  19. }
复制代码
自定义失败分析器需要在项目的resources/META-INF/spring.factories文件中注册,内容如下:
  1. org.springframework.boot.diagnostics.FailureAnalyzer=\
  2.   org.crazyit.app.MyAnalyzer
复制代码
延迟初始化

SpringBoot允许将ApplicationContext的预初始化改为延迟初始化,有一下三种方式:

  • 调用SpringApplicationBuilder.lazyInitialization(true)
  • 调用SpringApplication.setLazyInitialization(true);
  • 在application.properties配置spring.main.lazy-initialization=true
启用延迟初始化后,spring容器不会预初始化bean,而是等到需要调用bean的方法时才执行初始化,因此可降低SpringBoot的启动时间,但是有如下缺点:

  • 在web应用中,web相关的bean要等到http请求第一次到来时才会初始化,因为会降低http的响应效率。
  • bean错误被延迟发现。如果bean的配置存在错误,启动时不会报错,等到应用这个bean时才会报错,这样就延迟了发现错误的时机。
  • 运行过程中内存紧张。随着程序运行,当应用中所有bean初始化完成后,可能会造成运行时内存紧张。
一般来说,除非特殊需要,不建议启动延迟初始化。
自定义banner

在启动springboot时,默认显示Spring的banner,可在application.properties中设置

    1. spring.main.banner-mode=console  // 在控制台输出banner
    复制代码
    1. spring.main.banner-mode=log // 在日志文件中输出banner
    复制代码
    1. spring.main.banner-mode=off  // 关闭banner
    复制代码
SpringBoot允许自定义banner,在类加载路径下加一个banner.txt或设置spring.banner.location,可指定自定义banner文件位置;默认使用UTF-8字符集来读取banner内容,如果需要改变字符集,可在application.properties配置:
  1. spring.banner.charset=GBK
复制代码
SpringBoot允许使用图片文件在夹banner,在类加载路径下加一个banner.gif(gif\jpg\png)或设置spring.banner.image.location;SpringBoot会把图片转换成字符画(ASCII art)形式。
  1. # 定义图片banner的大小
  2. spring.banner.image.height=20
  3. spring.banner.image.width=60
  4. # 设置字符串色深
  5. spring.banner.image.bitdepth=4
复制代码
当图片banner和文本banner都存在,会先显示图片banner,再显示文本banner。
SpringApplication还提供setBanner()设置Banner。
设置SpringApplication与流式API

如果要对SpringApplication进行自定义设置,需要先定义SpringApplication对象,然后调用run()。
  1. public static void main(String[] args) {
  2.         var application = new SpringApplication(App.class);
  3.     application.setLazyInitialization(true); // 设置懒加载
  4.     application.setBanner(new Banner() {
  5.         @Override
  6.         public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
  7.             out.println("===================================");
  8.             out.println("  欢迎使用Spring Boot  ");
  9.             out.println("===================================");
  10.         }
  11.     });
  12.     // 创建Spring容器、运行Spring Boot应用
  13.     application.run(args);
  14. }
复制代码
除了直接使用构造器来创建SpringApplication对象,也可以使用SpringApplicationBuilder工具类以流式API来创建SpringApplication
  1. new SpringApplicationBuilder()
  2.         // 设置应用的主类(父容器)
  3.         .sources(Parent.class)
  4.         // 设置应用的子类 (子容器)
  5.         .child(App.class)
  6.         .setlazyInitialization(true) // 设置懒加载
  7.         .bannerMode(Banner.Mode.OFF) // 关闭Banner
  8.         .run(args);
复制代码
让多个Spring容器有层次结构具有很多好处,比如,由于子容器是由父容器负责加载的,因此子容器中的bean可以访问父容器中的bean,但父容器中的bean不能访问子容器中的bean。
事件监听器和容器初始化器

SpringApplication注册事件监听器的方式

  • 调用SpringApplication的addLIsteners()或者SpringApplicationBuilder的listeners()来添加事件监听器。
  • 在/META-INF/spring.factories文件中注册事件监听器
    1. org.springframework.context.ApplicationListener=org.crazyit.app.MyListener
    复制代码
当SpringBoot启动时,SpringApplication会依次触发如下事件:

  • 应用刚刚开始,未进行任何处理,触发ApplicationStartingEvent事件,除非监听器和初始化器注册失败。
  • 当ApplicationContext要使用的Environment已经确定,但ApplicationContext还未被创建时,触发ApplicationEnvironmentPreparedEvent事件。
  • 当ApplicationContext准备完成且初始化器已被调用,但未加载任何bean定义前,触发ApplicationContextInitializedEvent事件。
  • 在所有bean定义被加载之后,Spring容器刷新之前,触发ApplicationPreparedEvent事件。
  • 在Spring容器刷新之后,ApplicationRunner、CommandLineRunner的接口方法被调用之前,触发ApplicationStartedEvent事件。
  • 当应用程序LivenessState状态变成CORRECT,表明应用程序进入live状态,触发AvailabilityChangeEvent事件。
  • 当所有ApplicationRunner、CommandLineRunner的接口方法被调用完成后,触发ApplicationReadyEvent事件。
  • 当应用程序的ReadinessState状态变成ACCEPTING_TRAFFIC,表明应用程序准备接受服务请求,触发AvailabilityChangeEvent事件。
  • 如果启动遇到异常,触发ApplicationFailedEvent事件。
此外,还会在ApplicationPreparedEvent事件与ApplicationStartedEvent事件之间触发如下2个容器事件:

  • web服务器初始化完成后触发WebServerInitializedEvent事件;

    • 如果使用servlet web服务器,则触发ServletWebServerInitializedEvent
    • 如果使用反应式web服务器,则触发ReactiveWebServerInitializedEvent

  • 在刷新ApplicationContext时触发ContextRefreshEvent事件。
由于事件监听器采用同一个线程来执行,因此不应该在监听器中执行耗时操作,如果确实需要,建议使用ApplicationRunner或CommandLineRunner接口。
  1. // 定义事件监听器,负责监听ApplicationStartedEvent事件
  2. // 实现ApplicationContextAware接口,可用于获取所在的Spring容器
  3. public class MyListener implements ApplicationContextAware,
  4.       ApplicationListener
  5. {
  6.    private ApplicationContext ctx;
  7.          
  8.     // 监听ApplicationStartedEvent事件
  9.    @Override
  10.    public void onApplicationEvent(ApplicationStartedEvent event)
  11.    {
  12.       // 获取激发事件的容器
  13.       ConfigurableApplicationContext c = event.getApplicationContext();
  14.       if (c == ctx)
  15.       {
  16.          System.out.println("-----激发事件的容器与监听器所在容器相同-----");
  17.       }
  18.       // 后面的代码可插入任意自定义的处理
  19.       System.out.println("========执行自定义处理=======");
  20.    }
  21.    // 接口注入方法,通过该方法可访问Spring容器
  22.    @Override
  23.    public void setApplicationContext(ApplicationContext ctx) throws BeansException
  24.    {
  25.       this.ctx = ctx;
  26.    }
  27. }
复制代码
上面这些时间都是通过Spring框架事件机制来发布的,这种机制保证子容器触发的事件也会自动触发父容器的监听器。因此,如果采用层次结构的容器,监听器有可能接收到同一类型事件的多个实例--它们来自不同的容器。
为了让监听器能区分事件来自哪个容器,可以用监听器依赖注入的 容器与事件的容器进行比较,将容器依赖注入事件监听器通过如下2种方式:

  • 接口注入:让监听器实现ApplicationContextAware接口,Spring容器会被注入监听器。
  • 普通注入:如果监听器是容器中的bean,可使用@Autowired完成依赖注入。
除了监听器,SpringBoot提供了ApplicationContextInitializer对 Spring容器执行初始化。ApplicationContextInitializer接口的实现类被称为“初始化器”,实现类必然实现initialize(C ctx)方法。有了实现类,通过如下方式来注册初始化器:

  • 调用SpringApplication的addInitializer()或SpringApplicationBuilder的initializers()添加初始化器。
  • 使用/META-INF/spring.factories来配置初始化器:
    1. org.springframework.context.ApplicationContextInitializer=org.crazyit.app.MyInitializer
    复制代码
  1. public class MyInitializer implements
  2.       ApplicationContextInitializer<ConfigurableApplicationContext>
  3. {
  4.     // 大部分时候springboot会自动执行一些初始化工作,不需要开发者执行额外初始化,如果要对Spring容器进行某些与业务相关的特定设置,或者让SpringBoot整合某些前沿的、官方未支持的框架,则可在此处执行自定义初始化
  5.    @Override
  6.    public void initialize(ConfigurableApplicationContext configurableApplicationContext)
  7.    {
  8.       // 接下来的代码可对Spring容器执行任意的初始化
  9.       System.out.println("====模拟对Spring容器执行初始化====");
  10.    }
  11. }
复制代码
配置环境后处理器

如果想在Environment对象创建后、Spring容器刷新前对Environment对象进行定制,SpringBoot提供配置环境后处理器--实现EnvironmentPostProcessor接口的类被称为“配置环境后处理器”。实现类必然实现postProcessEnvironment(environment,application)方法,通过该方法的参数对Environment进行定制,比如将自定义配置文件的属性加载到配置环境中。
可通过/META-INF/spring.factories来注册配置环境后处理器:
  1. org.springframework.boot.env.EnvironmentPostProcessor=org.crazyit.app.FkEnvironmentPostProcessor
复制代码
  1. public class FkEnvironmentPostProcessor implements EnvironmentPostProcessor
  2. {
  3.    private final PropertiesPropertySourceLoader loader = new PropertiesPropertySourceLoader();
  4.    @Override
  5.    public void postProcessEnvironment(ConfigurableEnvironment environment,
  6.          SpringApplication application)
  7.    {
  8.       // 指定自定义的配置文件,加载类加载路径下的fk/fk.properties文件
  9.       Resource path = new ClassPathResource("fk/fk.properties");
  10.       // 加载自定义配置文件
  11.       PropertySource<?> ps = loadProperty(path);
  12.       System.out.println("fkjava.name: " + ps.getProperty("fkjava.name"));
  13.       System.out.println("fkjava.age: " +ps.getProperty("fkjava.age"));
  14.       // 将PropertySource中的属性添加到Environment配置环境中
  15.       environment.getPropertySources().addLast(ps);
  16.    }
  17.    private PropertySource<?> loadProperty(Resource path)
  18.    {
  19.       if (!path.exists())
  20.       {
  21.          throw new IllegalArgumentException("资源: " + path + " 不存在");
  22.       }
  23.       try
  24.       {
  25.          // 加载path对应的配置文件
  26.          return this.loader.load("custom-resource", path).get(0);
  27.       }
  28.       catch (IOException ex)
  29.       {
  30.          throw new IllegalStateException("加载配置文件出现错误: " + path, ex);
  31.       }
  32.    }
  33. }
复制代码
1.png

ApplicationRunner和CommandLineRunner

ApplicationRunner和CommandLineRunner也属于SpringApplication的初始化API,在SpringApplication的run方法完成之前,ApplicationRunner和CommandLineRunner的实现类会自动调用接口中的run()方法。
由此可见ApplicationRunner和CommandLineRunner适合在程序启动后,对外提供服务之前进行一些特定的初始化处理。
ApplicationRunner和CommandLineRunner功能基本一样,都只定义了一个run方法,区别是参数类型不同:

  • ApplicationRunner中run方法的参数类型是ApplicationArguments,用于访问运行SpringApplication的参数。ApplicationArguments使用如下方法来获取运行参数:

    • SetgetOptionNames():获取参数中所有的选项参数名,以双横线--开头的参数才是选项参数
    • ListgetOptionValues(String name):根据选项名获取选项值
    • ListgetNonOptionArgs():获取非选项参数
    • String[] getSourceArgs():获取原始的参数

  • CommandLineRunner中的run方法的参数类型是String...,可直接代表SpringApplication的参数
  1. @Component
  2. public class FkRunner implements ApplicationRunner {
  3.     // 改方法在Spring Boot应用启动完成后执行,
  4.     @Override
  5.     public void run(ApplicationArguments args) throws Exception {
  6.         System.out.println("====模拟对Spring容器执行初始化====" +
  7.                 "\n" + "应用启动完成后执行的代码" +
  8.                 "\n" + "getOptionNames = " + args.getOptionNames() +
  9.                 "\n" + "getSourceArgs = " + args.getSourceArgs() +
  10.                 "\n" + "getNonOptionArgs = " + args.getNonOptionArgs());
  11.     }
  12. }
复制代码
2.png

结果:
3.png

“--”开头的参数会覆盖application.properties等配置文件中的属性。
创建非web应用

SpringApplication会按如下规则来创建ApplicationContext容器:

  • 如果项目添加了SpringMVC依赖,则使用AnnotationConfigServletWebServerApplicationContext作为Spring容器。
  • 如果添加了Spring WebFlux依赖,则使用AnnotationConfigReactiveWebServerApplicationContext作为Spring容器。
  • 否则使用AnnotationConfigApplicationContext作为Spring容器。
如果项目同时添加了SpringMVC依赖和Spring WebFlux依赖,依然使用AnnotationConfigServletWebServerApplicationContext作为Spring容器。
可以调用SpringApplication的setWebApplicationType()方法来设置Spring容器类型,该方法 接收WebApplicationType类型的枚举值:

  • NONE:运行非Web应用,不启动内嵌的Web服务器
  • REACTIVE:运行反应式Web应用,启动内嵌的反应式Web服务器
  • SERVLET:运行Servlet的Web应用,启动内嵌的Servlet Web服务器
如果要创建非Web应用:
  1. public class App {
  2.     public static void main(String[] args) {
  3.         var application = new SpringApplication(App.class);
  4.         application.setWebApplicationType(WebApplicationType.NONE);
  5.         // 创建Spring容器、运行Spring Boot应用
  6.         application.run(args);
  7.     }
  8. }
复制代码
通过ApplicationArguments访问应用参数

SpringBoot会自动将ApplicationArguments注入容器中,因此使用 @Autowired依赖注入实例变量即可获取对象。
  1. @RestController
  2. public class HelloController
  3. {
  4.    // 依赖注入容器中的ApplicationArguments Bean
  5.    @Autowired
  6.    private ApplicationArguments args;
  7.    @GetMapping("/")
  8.    public void test()
  9.    {
  10.       System.out.println("访问应用的运行参数");
  11.       System.out.println("getSourceArgs:" + Arrays.toString(args.getSourceArgs()));
  12.       System.out.println("getOptionValues:" + args.getOptionValues("book.name"));
  13.       System.out.println("getNonOptionArgs:" + args.getNonOptionArgs());
  14.    }
  15. }
复制代码
运行结果:
4.png


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册