11月23

spring boot启动后自动执行代码方式汇总

| |
14:27编程杂谈  From: 本站原创
在实际项目开发过程中,我们有时候需要让项目在启动时执行特定方法。如要实现这些功能:
提前加载相应的数据到缓存中;
检查当前项目运行环境;
检查程序授权信息,若未授权则不能使用后续功能;
执行某个特定方法;

一直想整理一下Springboot启动后自执行某段代码或者方法相关的点,囿于时间问题及担心理解不是太全面所以没整理,后来一想先整理出来,将这一方面的东西记录下来。其实这篇文章不应该体现spring boot,因为自动执行方式不仅限于spring boot,java spring细化到bean的初始化都可以完成。

一、java自身的启动时加载方式

1、static代码块
static静态代码块,在类加载的时候即自动执行。 由于静态代码块没有名字,我们并不能主动调用,它会在类加载的时候,自动执行。所以静态代码块,可以更早的给类中的静态属性,进行初始化赋值操作。并且,静态代码块只会自动被执行一次,因为JVM在一次运行中,对一个类只会加载一次!

2、构造函数constructor
在对象初始化时执行。执行顺序在static静态代码块之后。

3、@PostConstruct注解
@PostConstruct
public void testInit() {
    System.out.printin("postConstruct“);
}
@PostConstruct注解使用在方法上,这个方法在对象依赖注入初始化之后执行。执行节点在BeanPostProcessor的postProcessBeforeInitialization之后,在postProcessAfterInitialization之前。
很多人以为该注解是Spring提供的。其实是Java自己的注解。Java中该注解的说明:@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。
注意:加了postconstruct注解的方法,如果执行失败,整个程序会无法正常启动!这个方法执行不完,整个程序也启动不了!也不建议将耗时逻辑放到这里面来。

二、Servlet加载的方式
1、实现ServletContextListener接口contextInitialized方法
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

@Slf4j
@Component
public class ServletContextListenerImpl implements ServletContextListener {
    /**
     * 在初始化Web应用程序中的任何过滤器或Servlet之前,将通知所有ServletContextListener上下文初始化。
     */
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("启动时自动执行 ServletContextListener 的 contextInitialized 方法");
    }
}
注意:该方法会在填充完普通Bean的属性,但是还没有进行Bean的初始化之前执行

三、Spring启动时加载方式
1、实现ServletContextAware接口setServletContext 方法
@Component
public class StartInitServletContext implements ServletContextAware {

    @Override
    public void setServletContext(ServletContext servletContext) {
        System.out.println("StartInitServletContext: 开始处理事情");
    }
}

2、ApplicationListener监听器
创建Listener的方式有两种:
方式1:编程实现ApplicationListener接口
方式2:使用@EventListener注解
注意监听的事件,通常是ApplicationStartedEvent 或者ApplicationReadyEvent,其他的事件可能无法注入bean。

编程实现ApplicationListener接口
/**
* 监听的是 ApplicationStartedEvent 事件,
* 则 ApplicationListener 一定会在 CommandLineRunner 和 ApplicationRunner 之前执行;
*/
@Component
public class StartInitStartedEvent implements ApplicationListener<ApplicationStartedEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        System.out.println("StartInitStartedEvent:开始处理事情");
    }
}
/**
* 监听的是 ApplicationReadyEvent 事件,
* 则 ApplicationListener 一定会在 CommandLineRunner 和 ApplicationRunner 之后执行;
*/
@Component
public class StartInitReadyEvent implements ApplicationListener<ApplicationReadyEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        System.out.println("StartInitReadyEvent: 开始处理事情");
    }
}
实现 public interface ApplicationListener<E extends ApplicationEvent> 接口,监听 ApplicationEvent 及其下面的子事件:
/**
* 事件监听器一般是由开发者自己定义
* 定义事件监听器
*/
@Component
//@Lazy
public class MyApplicationListener implements ApplicationListener{

  @Override
  public void onApplicationEvent(ApplicationEvent event) {
    event = (PayloadApplicationEvent)event;
    System.out.println(((PayloadApplicationEvent<?>) event).getPayload());
  }
}

@EventListener方式
将要执行的方法所在的类交个Spring容器扫描(@Component),并且在要执行的方法上添加@EventListener注解执行
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class EventListenerTest {
    @EventListener
    public void onApplicationEvent(ContextRefreshedEvent event) {
        log.info("启动时自动执行  @EventListener 注解方法");
    }
}

3、InitializingBean接口
@Component
@Data
public class TestInit implements InitializingBean,eanFactoryAware,ApplicationContextAware{
    private BeanFactory beanFactory:
    private ApplicationContext applicationContext;
    private String name;
@Override
public voidafterPropertiesSet(hrows Exception {
    System.out.printin("InIt heck");
}
}
Spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现afterPropertiesSet方法,或者在配置文件中通过init-method指定,两种方式可以同时使用。
实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率要高一点,但是init-method方式消除了对spring的依赖。先调用afterPropertieSet()方法,然后再调用init-method中指定的方法。

4、ApplicationRunner和CommandLineRunner
SpringBoot提供了两个接口来实现Spring容器启动完成后执行的功能,两个接口分别为CommandLineRunner和ApplicationRunner。这两个接口需要实现一个run方法,将代码在run中实现即可。这两个接口功能基本一致,其区别在于run方法的入参。ApplicationRunner的run方法入参为ApplicationArguments,为CommandLineRunner的run方法入参为String数组。
当有多个类实现了CommandLineRunner和ApplicationRunner接口时,可以通过在类上添加@Order注解来设定运行顺序。
注意:使用ApplicationRunner或者CommandLineRunner时如果报错或者出现超时的情况会导致整个程序崩溃。可以将run内的逻辑单独开一个线程使用。
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.util.Set;

@Slf4j
@Component
public class ApplicationRunnerImpl implements ApplicationRunner {
    /**
     * 用于指示bean包含在SpringApplication中时应运行的接口。可以定义多个ApplicationRunner bean
     * 在同一应用程序上下文中,可以使用有序接口或@order注释对其进行排序。
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("启动时自动执行 ApplicationRunner 的 run 方法");

        Set<String> optionNames = args.getOptionNames();
        for (String optionName : optionNames) {
            log.info("这是传过来的参数[{}]", optionName);
        }
        String[] sourceArgs = args.getSourceArgs();
        for (String sourceArg : sourceArgs) {
            log.info("这是传过来sourceArgs[{}]", sourceArg);
        }
    }
}

通过实现Ordered接口并重写getOrder方法实现,数字越小越先执行

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class CommandLineRunnerImpl implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        log.info("启动时自动执行 CommandLineRunner 的 run 方法");
    }
}
总结一下:
Spring应用启动过程中,肯定是要自动扫描有@Component注解的类,加载类并初始化对象进行自动注入。加载类时首先要执行static静态代码块中的代码,之后再初始化对象时会执行构造方法。最新 Spring Boot 面试题整理好了,点击Java面试库小程序在线刷题。
在对象注入完成后,调用带有@PostConstruct注解的方法。当容器启动成功后,再根据@Order注解的顺序调用CommandLineRunner和ApplicationRunner接口类中的run方法。
说明:
  1)默认执行顺序ApplicationContextInitializer、ApplicationRunner、CommandLineRunner
  2)ApplicationRunner与CommandLineRunner可通过@Order改变顺序使CommandLineRunner早于ApplicationRunner执行
  3)两个Runner的区别:CommandLineRunner参数是原始的,ApplicationRunner是对原始参数的封装

执行顺序为static>constructor>初始化前>ServletContextListener>@PostConstruct>ServletContextAware>@EventListener>InitializingBean>初始化后>ApplicationRunner和CommandLineRunner。


来源:Heck's Blog
地址:https://www.heckjj.com/post/657/
转载时须以链接形式注明作者和原始出处及本声明,否则将追究法律责任,谢谢配合!
阅读(556) | 评论(0) | 引用(0)