在应用启动过程中,我们一般想要执行某些方法,比如一些监听事件的注册,消费者开始消费数据。这里总结了几个常用于在项目启动时就可以调用的方法:
一、直接在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 都是在项目加载完成前被调用,如果耗时比较久的方法会导致项目启动缓慢,其他方法都是在项目加载完成后调用,不会影响项目的执行。但要注意一点,所有方法都是在主线程中执行,如果自定义的方法是监听或订阅这种方法,最好单独起一个线程调用方法,避免阻塞主线程。