八仙过海,各显神通
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 {
@Autowired
private 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 {
@Autowired
private Environment env;
@PostConstruce
public void init() {
env.getActiveProfiles();
}
}
与@PostConstruct
相对应的,如果想在 Bean 注销时完成一些清扫工作,如关闭线程池等,可以使用@PreDestroy
注解:
@Component
public class CustomBean {
@Autowired
private ExecutorService executor = Executors.newFixedThreadPool(1)
@PreDestroy
public 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);
@Autowired
private Environment environment;
@Override
public 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> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
log.info("Subject ContextRefreshedEvent");
}
}
除了通过实现ApplicationListener
接口来监听相应的事件,Spring 的事件机制也实现了通过@EventListener
注解来监听相对应事件:
@Component
@Slf4j
public class StartupApplicationListenerExample {
@EventListener
public 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;
@Autowired
public 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 {
@Override
public 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;
@Override
public void start() {
System.out.println("start");
isRunning = true;
}
@Override
public int getPhase() {
// 默认为 0
return 0;
}
@Override
public boolean isAutoStartup() {
// 默认为 false
return true;
}
@Override
public boolean isRunning() {
// 默认返回 false
return isRunning;
}
@Override
public void stop(Runnable callback) {
System.out.println("stop(Runnable)");
callback.run();
isRunning = false;
}
@Override
public void stop() {
System.out.println("stop");
isRunning = false;
}
}
点击下方卡片关注,订阅更多精彩内容
推荐阅读:
API 面试四连杀:接口如何设计?安全如何保证?签名如何实现?防重如何实现?