点击上方蓝色“方志朋”,选择“设为星标”

回复“666”获取独家整理的学习资料!

Spring 是一个控制反转依赖管理的容器,作为 Java Web 的开发人员,基本没有不熟悉 Spring 技术栈的,尽管在依赖注入领域,Java Web 领域不乏其他优秀的框架,如 google 开源的依赖管理框架 guice,如 Jersey web 框架等。但 Spring 已经是 Java Web 领域使用最多,应用最广泛的 Java 框架。

此文将专注讲解如何在 Spring 容器启动时实现我们自己想要实现的逻辑。我们时常会遇到在 Spring 启动的时候必须完成一些初始化的操作,如创建定时任务,创建连接池等。

本文将介绍以下几种 Spring 启动监听方式:

  • Bean 构造函数方式

  • 使用 @PostConstruct 注解

  • 实现 InitializingBean 接口

  • 监听 ApplicationListener 事件

  • 使用 Constructor 注入方式

  • 实现 SpringBoot 的 CommandLineRunner 接口

  • SmartLifecycle 机制

原始构造函数

如果没有 Spring 容器,不依赖于 Spring 的实现,回归 Java 类实现本身,我们可以在静态代码块,在类构造函数中实现相应的逻辑,Java 类的初始化顺序依次是静态变量 > 静态代码块 > 全局变量 > 初始化代码块 > 构造器

比如,Log4j 的初始化,就是在 LogManager 的静态代码块中实现的:


static {Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));repositorySelector = new DefaultRepositorySelector(h);String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,null);if(override == null || "false".equalsIgnoreCase(override)) {String configurationOptionStr = OptionConverter.getSystemProperty(DEFAULT_CONFIGURATION_KEY, null);String configuratorClassName = OptionConverter.getSystemProperty(CONFIGURATOR_CLASS_KEY, null);URL url = null;if(configurationOptionStr == null) {url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);if(url == null) {url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);}} else {try {url = new URL(configurationOptionStr);} catch (MalformedURLException ex) {url = Loader.getResource(configurationOptionStr);}}if(url != null) {LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");try {OptionConverter.selectAndConfigure(url, configuratorClassName,LogManager.getLoggerRepository());} catch (NoClassDefFoundError e) {LogLog.warn("Error during default initialization", e);}} else {LogLog.debug("Could not find resource: ["+configurationOptionStr+"].");}} else {LogLog.debug("Default initialization of overridden by " +  DEFAULT_INIT_OVERRIDE_KEY + "property.");}
}

比如在构造函数中实现相应的逻辑:

@Component
public class CustomBean {@Autowiredprivate Environment env;public CustomBean() {env.getActiveProfiles();}
}

这里考验一下各位,上面的代码是否可以正常运行。—— 不行,构造函数中的env将会发生NullPointException异常。这是因为在 Spring 中将先初始化 Bean,也就是会先调用类的构造函数,然后才注入成员变量依赖的 Bean(@Autowired@Resource注解修饰的成员变量),注意@Value等注解的配置的注入也是在构造函数之后。

@PostConstruct

在 Spring 中,我们可以使用@PostConstruct在 Bean 初始化之后实现相应的初始化逻辑,@PostConstruct修饰的方法将在 Bean 初始化完成之后执行,此时 Bean 的依赖也已经注入完成,因此可以在方法中调用注入的依赖 Bean。

@Component
public class CustomBean {@Autowiredprivate Environment env;@PostConstrucepublic void init() {env.getActiveProfiles();}
}

@PostConstruct相对应的,如果想在 Bean 注销时完成一些清扫工作,如关闭线程池等,可以使用@PreDestroy注解:

@Component
public class CustomBean {@Autowiredprivate ExecutorService executor = Executors.newFixedThreadPool(1)@PreDestroypublic void destroy() {env.getActiveProfiles();}
}

InitializingBean

实现 Spring 的InitializingBean接口同样可以实现以上在 Bean 初始化完成之后执行相应逻辑的功能,实现InitializingBean接口,在afterPropertiesSet方法中实现逻辑:

@Component
public class CustomBean implements InitializingBean {private static final Logger LOG= Logger.getLogger(InitializingBeanExampleBean.class);@Autowiredprivate Environment environment;@Overridepublic void afterPropertiesSet() throws Exception {LOG.info(environment.getDefaultProfiles());}
}

ApplicationListener

我们可以在 Spring 容器初始化的时候实现我们想要的初始化逻辑。这时我们就可以使用到 Spring 的初始化事件。Spring 有一套完整的事件机制,在 Spring 启动的时候,Spring 容器本身预设了很多事件,在 Spring 初始化的整个过程中在相应的节点触发相应的事件,我们可以通过监听这些事件来实现我们的初始化逻辑。Spring 的事件实现如下:

  • ApplicationEvent,事件对象,由 ApplicationContext 发布,不同的实现类代表不同的事件类型。

  • ApplicationListener,监听对象,任何实现了此接口的 Bean 都会收到相应的事件通知。实现了 ApplicationListener 接口之后,需要实现方法 onApplicationEvent(),在容器将所有的 Bean 都初始化完成之后,就会执行该方法。

与 Spring Context 生命周期相关的几个事件有以下几个:

  • ApplicationStartingEvent: 这个事件在 Spring Boot 应用运行开始时,且进行任何处理之前发送(除了监听器和初始化器注册之外)。

  • ContextRefreshedEvent: ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来发生。

  • ContextStartedEvent: 当使用 ConfigurableApplicationContext 接口中的 start() 方法启动 ApplicationContext 时,该事件被触发。你可以查询你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。

  • ApplicationReadyEvent: 这个事件在任何 application/ command-line runners 调用之后发送。

  • ContextClosedEvent: 当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被触发。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启。

  • ContextStoppedEvent: Spring 最后完成的事件。

因此,如果我们想在 Spring 启动的时候实现一些相应的逻辑,可以找到 Spring 启动过程中符合我们需要的事件,通过监听相应的事件来完成我们的逻辑:

@Component
@Slf4j
public class StartupApplicationListenerExample implements ApplicationListener<ContextRefreshedEvent> {@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {log.info("Subject ContextRefreshedEvent");}
}

除了通过实现ApplicationListener接口来监听相应的事件,Spring 的事件机制也实现了通过@EventListener注解来监听相对应事件:

@Component
@Slf4j
public class StartupApplicationListenerExample {@EventListenerpublic void onApplicationEvent(ContextRefreshedEvent event) {log.info("Subject ContextRefreshedEvent");}
}

Spring Event 是一套完善的进程内事件发布订阅机制,我们除了用来监听 Spring 内置的事件,也可以使用 Spring Event 实现自定义的事件发布订阅功能。

Constructor 注入

在学习 Spring 的注入机制的时候,我们都知道 Spring 可以通过构造函数、Setter 和反射成员变量注入等方式。上面我们在成员变量上通过@Autoware注解注入依赖 Bean,但是在 Bean 的构造函数函数中却无法使用到注入的 Bean(因为 Bean 还未注入),其实我们也是使用 Spring 的构造函数注入方式, 这也是 Spring 推荐的注入机制(在我们使用 IDEA 的时候,如果没有关闭相应的代码 Warning 机制,会发现在成员变量上的@Autoware是黄色的,也就是 idea 不建议的代码)。Spring 更推荐构造函数注入的方式:

@Component
@Slf4j
public class ConstructorBean {private final Environment environment;@Autowiredpublic LogicInConstructorExampleBean(Environment environment) {this.environment = environment;log.info(Arrays.asList(environment.getDefaultProfiles()));}
}

CommandLineRunner

如果我们的项目使用的是 Spring Boot,那么可以使用 Spring Boot 提供的 CommandLineRunner 接口来实现初始化逻辑,Spring Boot 将在启动初始化完成之后调用实现了CommandLineRunner的接口的run方法:

@Component
@Slf4j
public class CommandLineAppStartupRunner implements CommandLineRunner {@Overridepublic void run(String...args) throws Exception {log.info("Increment counter");}
}

并且,多个CommandLineRunner实现,可以通过@Order来控制它们的执行顺序。

SmartLifecycle

还有一种更高级的方法来实现我们的逻辑。这可以 Spring 高级开发必备技能哦。SmartLifecycle 不仅仅能在初始化后执行一个逻辑,还能再关闭前执行一个逻辑,并且也可以控制多个 SmartLifecycle 的执行顺序,就像这个类名表示的一样,这是一个智能的生命周期管理接口。

  • start():bean 初始化完毕后,该方法会被执行。

  • stop():容器关闭后,spring 容器发现当前对象实现了 SmartLifecycle,就调用 stop(Runnable), 如果只是实现了 Lifecycle,就调用 stop()。

  • isRunning:当前状态,用来判你的断组件是否在运行。

  • getPhase:控制多个 SmartLifecycle 的回调顺序的,返回值越小越靠前执行 start() 方法,越靠后执行 stop() 方法。

  • isAutoStartup():start 方法被执行前先看此方法返回值,返回 false 就不执行 start 方法了。

  • stop(Runnable):容器关闭后,spring 容器发现当前对象实现了 SmartLifecycle,就调用 stop(Runnable), 如果只是实现了 Lifecycle,就调用 stop()。

@Component
public class SmartLifecycleExample implements SmartLifecycle {private boolean isRunning = false;@Overridepublic void start() {System.out.println("start");isRunning = true;}@Overridepublic int getPhase() {// 默认为 0return 0;}@Overridepublic boolean isAutoStartup() {// 默认为 falsereturn true;}@Overridepublic boolean isRunning() {// 默认返回 falsereturn isRunning;}@Overridepublic void stop(Runnable callback) {System.out.println("stop(Runnable)");callback.run();isRunning = false;}@Overridepublic void stop() {System.out.println("stop");isRunning = false;}}

热门内容:
  • 切记!MySQL中ORDER BY与LIMIT 不要一起用,有大坑

  • 为什么国内流行的 MyBatis ,国外 Java 工程师却不愿意使用?

  • 干掉Navicat:正版,MySQL官方客户端真香!

  • JDK 16 即将发布,新特性速览!

最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

明天见(。・ω・。)ノ♡

了解这些,你就可以在Spring启动时为所欲为了相关推荐

  1. spring启动时只执行一次的方法实现

    spring项目如何在启动项目是执行一些操作,在spring中能通过那些操作实现这个功能呢. 1.方法一 我在spring的配置文件中添加上这条,这个配置只能在启动项目是执行一遍.  还有一点 要注意 ...

  2. 解决:Spring启动时注入失败

    在Spring启动时会帮我们自动装配一些组件,依赖注入,此时如果项目中自己定义的类名有相同的,或者自己定义的类名与引入的第三方包有相同的,就会引发冲突,原因是Spring在帮我们管理这些Bean时不知 ...

  3. 【惊】Spring源码的秘密|一起看看Spring启动时究竟做了什么惊天动地的事情?

    初识Spring 文章目录 初识Spring 入口代码 大致流程 代码分析 [1]AnnotationConfigApplicationContext剖析 [2]AnnotatedBeanDefini ...

  4. Spring启动时的Spring社交示例,或者我如何不再担心和喜欢自动配置

    对于Spring Boot 1.1.0.RC1,添加了自动配置和Spring Social的启动程序pom,这意味着我不必为pom添加一百个依赖关系,并且将为我处理许多毫无意义的Spring配置. 让 ...

  5. 【264期】面试官问:Spring Boot 启动时自动执行代码方式有哪几种?解释一二!...

    点击上方"Java精选",选择"设为星标" 别问别人为什么,多问自己凭什么! 下方有惊喜,留言必回,有问必答! 每一天进步一点点,是成功的开始... 前言 目前 ...

  6. 白话Elasticsearch70-ES生产集群部署之production mode下启动时的bootstrap check

    文章目录 概述 官方文档 什么是bootstrap check(启动时检查)? development mode vs. production mode heap size check file de ...

  7. SpringBoot启动时实现自动执行代码的几种方式讲解

    点击关注公众号,实用技术文章及时了解 来源:blog.csdn.net/u011291072/article/ details/81813662 前言 目前开发的SpringBoot项目在启动的时候需 ...

  8. spring 启动日志报 DEBUG BeanNameUrlHandlerMapping:86 - Rejected bean name 'org.springframework.web.servl

    :86 - Rejected bean name 'applicationEventMulticaster': no URL paths identified 0:48:46,197 DEBUG De ...

  9. Spring Boot————Web应用启动时自动执行ApplicationListener用法

    原文:<web服务启动spring自动执行ApplicationListener的用法> 引言 我们知道,一般来说一个项目启动时需要加载或者执行一些特殊的任务来初始化系统,通常的做法就是用 ...

最新文章

  1. html5 梵高 星,梵高作品欣赏《星空》
  2. OSI七层与TCP/IP五层
  3. ubnutu18.10拔除硬盘后进行recovery mode
  4. 35 岁程序员的独家面试经历
  5. 各种排序笔记---基于非比较排序部分
  6. embedding亦福亦祸?XGBoost与LightGBM的新机遇
  7. java中 将字符串时间 '2015-9-8 17:05:06' 转化为格式 '2015-09-08 17:05:06'
  8. 使用“时间机器”备份您的 Mac
  9. GO学习笔记 - Go 只有一种循环结构—— for 循环。
  10. RabbitMQ的深入理解和最简单的用途说明
  11. python设计模式5-原型模式
  12. [转]将微信和支付宝支付的个二维码合二为一
  13. 为什么前端工程师很难找?
  14. 普元云计算-云计算平台项目团队组织架构与缘起
  15. Python全栈之路系列----之-----守护进程\进程锁\队列\生产者消费者模式\数据共享\进程池(同步,异步)\回调函数\concurrent.futures模块...
  16. 2022谷粒商城学习笔记(二十五)支付宝沙箱模拟支付
  17. 今之君子,其责人也详,其待己也廉
  18. 实现一个脚本引擎(燕良译)- -
  19. ​2019胡润百富榜公布:中国互联网上演: 龙虎斗 , 阿里暂时领跑 , 企鹅紧随其后 , 李彦宏,雷军掉队...
  20. Java 数据交换格式反射机制SpringIOC原理分析

热门文章

  1. 李宏毅机器学习笔记(二)-------Why we need learn Machine Learning?
  2. 2019-03-20 Python爬取需要登录的有验证码的网站
  3. 安装mayavi和VTK库的血泪史
  4. bzoj1927: [Sdoi2010]星际竞速
  5. 整理Simple.Data使用方法
  6. 使用 AFNetworking 进行 XML 和 JSON 数据请求
  7. 区别:电感、磁珠和零欧电阻的作用
  8. 抽象工厂与工厂模式例子
  9. 带进度条的ASP无组件断点续传下载代码
  10. 【数据结构】双链表的实现(C语言)