在应用启动过程中,我们一般想要执行某些方法,比如一些监听事件的注册,消费者开始消费数据。这里总结了几个常用于在项目启动时就可以调用的方法:
一、直接在main方法中调用相关方法:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BootApplication {

    public static void main(String[] args) {
        SpringApplication.run(BootApplication.class, args);

        System.out.println("用户自定义方法被调用了,我在 BootApplication 中被调用,执行线程是 : " + Thread.currentThread().getName());
    }
}

二、实现CommandLineRunner接口,在run方法中执行相关的业务代码:
在一个项目中可以实现多个CommandLineRunner接口,并通过指定Order按顺序执行多个CommandLineRunner实现类

import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(value = 1)
public class MyCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("用户自定义方法被调用了,通过实现 CommandLineRunner 接口被调用,执行线程是 : " + Thread.currentThread().getName());
    }
}

三、通过实现ApplicationRunner接口,在run方法中执行相关的业务代码:

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class MyApplicationRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("用户自定义方法被调用了,通过实现 ApplicationRunner 接口被调用,执行线程是 : " + Thread.currentThread().getName());
    }
}

四、通过实现ApplicationListener接口,在onApplicationEvent方法中执行相关代码,当应用有事件时就会触发该方法的执行,所以该方法会多次被调用,在执行自定义方法时注意判断事件类型避免重复调用方法:

import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class MyApplicationListener implements ApplicationListener {

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if(event instanceof ApplicationStartedEvent) {
            System.out.println("用户自定义方法被调用了,通过实现 ApplicationListener 接口被调用,执行线程是 : " + Thread.currentThread().getName());
        }
    }
}

五、通过实现InitializingBean接口,在afterPropertiesSet方法中调用自定义方法:

import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

@Component
public class MyInitializingBean implements InitializingBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("用户自定义方法被调用了,通过实现 InitializingBean 接口被调用,执行线程是 : " + Thread.currentThread().getName());
    }
}

六、通过实现Filter过滤器,在init方法中执行自定义方法:

import org.springframework.stereotype.Component;

import javax.servlet.*;
import java.io.IOException;

@Component
public class MyFilter implements Filter {
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);

        System.out.println("用户自定义方法被调用了,通过实现 Filter 接口被调用,执行线程是 : " + Thread.currentThread().getName());
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

七、直接在类方法中添加 @PostConstruct 注解,添加该注解的方法会在项目启动时被调用:

import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Component
public class MyBean {

    @PostConstruct
    public void postMethod() {
        System.out.println("用户自定义方法被调用了,通过在 Bean 中添加 @PostConstruct 注解被调用,执行线程是 : " + Thread.currentThread().getName());
    }

    @PreDestroy
    public void preDestroyMethod() {
        System.out.println("MyBean destroy");
    }
}

下面总结一下上面的方法被调用顺序:

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::               (v2.6.11)

2023-09-20 18:27:13.416  INFO 17160 --- [           main] org.example.BootApplication              : Starting BootApplication using Java 1.8.0_161 on L15013317 with PID 17160 (D:\develop_tools\idea_workspace\test-demo\target\classes started by 00079095 in D:\develop_tools\idea_workspace\test-demo)
2023-09-20 18:27:13.418  INFO 17160 --- [           main] org.example.BootApplication              : No active profile set, falling back to 1 default profile: "default"
2023-09-20 18:27:13.965  INFO 17160 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-09-20 18:27:13.972  INFO 17160 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-09-20 18:27:13.972  INFO 17160 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.65]
2023-09-20 18:27:14.041  INFO 17160 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-09-20 18:27:14.041  INFO 17160 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 598 ms
用户自定义方法被调用了,通过实现 Filter 接口被调用,执行线程是 : main
用户自定义方法被调用了,通过在 Bean 中添加 @PostConstruct 注解被调用,执行线程是 : main
用户自定义方法被调用了,通过实现 InitializingBean 接口被调用,执行线程是 : main
2023-09-20 18:27:14.228  INFO 17160 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-09-20 18:27:14.233  INFO 17160 --- [           main] org.example.BootApplication              : Started BootApplication in 0.994 seconds (JVM running for 1.213)
用户自定义方法被调用了,通过实现 ApplicationListener 接口被调用,执行线程是 : main
用户自定义方法被调用了,通过实现 CommandLineRunner 接口被调用,执行线程是 : main
用户自定义方法被调用了,通过实现 ApplicationRunner 接口被调用,执行线程是 : main
用户自定义方法被调用了,我在 BootApplication 中被调用,执行线程是 : main

通过上面的输出结果可知,Filter 、@PostConstruct 、InitializingBean 都是在项目加载完成前被调用,如果耗时比较久的方法会导致项目启动缓慢,其他方法都是在项目加载完成后调用,不会影响项目的执行。但要注意一点,所有方法都是在主线程中执行,如果自定义的方法是监听或订阅这种方法,最好单独起一个线程调用方法,避免阻塞主线程。