如何在Spring应用程序启动时加入逻辑

摩森特沃 2021年03月17日 1,379次浏览

声明:本文多处直接引用了文末的参考链接中的内容,如有侵权,请联系删除

前言

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

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

本文将介绍以下几种方式(按照执行先后排序):

  • 初始化代码块和构造函数方式
  • 实现*Aware接口
  • 使用 @PostConstruct 注解
  • 实现 InitializingBean 接口
  • 实现 SmartLifecycle 接口
  • 监听 ApplicationListener 事件

初始化代码块和构造函数方式

如果没有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);

    ...
}

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

@Component
public class CustomBean {

    @Autowired
    private Environment env;

    public CustomBean() {
        env.getActiveProfiles();
    }
}

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

因为在类加载时会调用执行静态代码块的语句,而在初始化实例对象的时候又会依次调用普通代码块和构造函数,因此这部分的代码是可以最先执行的。顺序为:static静态代码块 > 普通代码块 > 构造函数。

实现*Aware接口

*Aware接口是Spring的一种标记接口,只要是spring管理的bean实现了Aware接口,那么spring就会在bean创建的某个时机将相应的资源注入到该spring bean中。

Aware的意思是感知,实现了*Aware接口中的setXX()方法后,可以很方便的获得Spring容器中的xx资源。

这里主要介绍ApplicationContextAware和ServletContextAware两种Aware接口。

实现ApplicationContextAware接口

实现ApplicationContextAware接口并重写setApplicationContext方法,该方法中可以获取到ApplicationContext资源,该方法会在普通的Bean初始化之后执行,可以在方法内部直接调用注入的普通Bean属性。

@Component
public class TestStarted implements ApplicationContextAware {
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        System.out.println("setApplicationContext方法");
    }
}

实现ServletContextAware接口

实现ServletContextAware接口并重写setServletContext方法,该方法中可以获取到ServletContext资源,该方法会在普通的Bean初始化之后执行,可以在方法内部直接调用注入的普通Bean属性,在执行顺序上执行在setApplicationContext方法之后。

@Component
public class TestStarted implements ServletContextAware {
    
    @Override
    public void setServletContext(ServletContext servletContext) {
        System.out.println("setServletContext方法");
    }
}

@PostConstruct

在Spring中,我们可以使用@PostConstruct在Bean初始化之后实现相应的初始化逻辑,@PostConstruct修饰的方法将在Bean初始化完成之后执行,此时Bean的依赖也已经注入完成,因此可以在方法中调用注入的依赖Bean,在使用@PostConstruct时,首先应将方法所在的类交给spring容器扫描(在类上加上@Component功能的注解)

@Component
public class CustomBean {

    @Autowired
    private Environment env;

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

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

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实现自定义的事件发布订阅功能。

Spring boot中额外增加的两种方式

如果我们的项目使用的是Spring Boot,那么可以使用Spring Boot提供的 CommandLineRunner和ApplicationRunner接口来实现初始化逻辑。

ApplicationRunner

/**
 * 用于指示bean包含在SpringApplication中时应运行的接口。可以定义多个applicationrunner bean
 * 在同一应用程序上下文中,可以使用有序接口或@order注释对其进行排序。
 */
@Override
public void run(ApplicationArguments args) throws Exception {
    System.out.println("ApplicationRunner的run方法");
}

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来控制它们的执行顺序。

引用参考