优化你的Spring Boot应用:预加载的秘密

优化你的Spring Boot应用:预加载的秘密

  • 预加载
    • ApplicationListener实现
      • 内置事件
      • 实现
      • 自定义实例
    • SpringBoot的CommandLineRunner接口
    • 当bean加载完初始化的时候,调用
    • 异步任务

预加载

当我们出现一些需求需要在项目启动的时候就去运行某个方法,比如定时任务

ApplicationListener实现

ApplicationContext事件机制是观察者设计模式的实现,通过ApplicationEvent类和ApplicationListener接口,可以实现ApplicationContext事件处理。

如果容器中有一个ApplicationListener Bean,每当ApplicationContext发布ApplicationEvent时,ApplicationListener Bean将自动被触发。这种事件机制都必须需要程序显示的触发。

其中spring有一些内置的事件,当完成某种操作时会发出某些事件动作。比如监听ContextRefreshedEvent事件,当所有的bean都初始化完成并被成功装载后会触发该事件,实现ApplicationListener接口可以收到监听动作,然后可以写自己的逻辑

内置事件

  • ContextRefreshedEvent

    ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext接口中使用 refresh() 方法来发生。此处的初始化是指:所有的Bean被成功装载,后处理Bean被检测并激活,所有Singleton Bean 被预实例化,ApplicationContext容器已就绪可用

  • ContextStartedEvent

    当使用 ConfigurableApplicationContext (ApplicationContext子接口)接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。

  • ContextStoppedEvent

    当使用 ConfigurableApplicationContext 接口中的 stop() 停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作。

  • ContextClosedEvent

    当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启。

  • RequestHandledEvent

    这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务。只能应用于使用DispatcherServlet的Web应用。在使用Spring作为前端的MVC控制器时,当Spring处理用户请求结束后,系统会自动触发该事件。

实现

比如要监听ContextRefreshedEvent的时可以实现ApplicationListener接口,并且传入要监听的事件

/**
 * @author xiaobo
 * @date 2022/3/29
 */
@Component
@Slf4j
public class SysQuartzJobListener implements ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    private ISysQuartzJobService sysQuartzJobService;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        try {
            sysQuartzJobService.updateJobStartById("23dedbb5-af24-11ec-a42d-0894ef72d9c4");
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        System.out.println(contextRefreshedEvent);
        System.out.println("listen..........");
    }
}

自定义实例

可以自定义事件,然后做完业务处理后手动发出。同上集成某个监听接口,接收到事件后进行业务处理

事件定义:

public class EmailEvent extends ApplicationEvent{
   private String address;
   private String text;
   public EmailEvent(Object source, String address, String text){
   super(source);
      this.address = address;
      this.text = text;
   }
   public EmailEvent(Object source) {
     super(source);
   }
   //......address和text的setter、getter
}

监听定义

public class EmailNotifier implements ApplicationListener{
   public void onApplicationEvent(ApplicationEvent event) {
     if (event instanceof EmailEvent) {
        EmailEvent emailEvent = (EmailEvent)event;
        System.out.println("邮件地址:" + emailEvent.getAddress());
        System.our.println("邮件内容:" + emailEvent.getText());
     } else {
        System.our.println("容器本身事件:" + event);
     }
   }
} 

业务触发

public class SpringTest {
   public static void main(String args[]){
     ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
     //创建一个ApplicationEvent对象
     EmailEvent event = new EmailEvent("hello","[email protected]","This is a test");
     //主动触发该事件
     context.publishEvent(event);
   }
}

**注意⚠️:**不管是内置监听还是外部自定义监听一定要把实现ApplicationListener的类定义成一个bean才行,可以是通过注解@Component等也可以通过xml的方式去执行。

SpringBoot的CommandLineRunner接口

当容器初始化完成之后会调用CommandLineRunner中的run()方法,同样能够达到容器启动之后完成一些事情。这种方式和ApplicationListener相比更加灵活,如下:

  • 不同的CommandLineRunner实现可以通过@Order()指定执行顺序
  • 可以接收从控制台输入的参数。
/**
 * @author xiaobo
 * @date 2022/3/29
 */
@Component
@Slf4j
public class SysQuartzJobRunner implements CommandLineRunner {

    @Autowired
    private ISysQuartzJobService sysQuartzJobService;

    @Override
    public void run(String... args) throws Exception {
        try {
            sysQuartzJobService.updateJobStartById("23dedbb5-af24-11ec-a42d-0894ef72d9c4");
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        log.info("test");
    }
}

这里的String… 可以换为ApplicationArguments

当bean加载完初始化的时候,调用

这里先讲以下这个注解PostConstruct

首先这个注解是java提供的,为不是Spring提供的

该注解的方法在整个bean的 初始化中的执行顺序是:

@PostConstruct 注解的方法在整个 Bean 的初始化过程中的执行顺序如下:

  1. Bean的实例化(Instantiation): 首先,Spring会实例化一个 Bean。这通常涉及到调用 Bean 类的构造函数来创建对象。在这一步完成后,Bean 的属性尚未设置。

  2. 依赖注入(Dependency Injection): 如果 Bean 有依赖关系,例如其他 Bean 或配置属性,Spring 将会注入这些依赖。这是通过自动装配(Autowired)或显式配置(例如通过 XML 或 Java 配置类)完成的。

  3. @PostConstruct 方法的调用: 一旦依赖注入完成,Spring 会查找使用了 @PostConstruct 注解的方法,并在此时调用它们。这些方法用于执行 Bean 的初始化操作。可以有多个方法都使用了 @PostConstruct 注解,它们的执行顺序取决于它们在类中的声明顺序。

  4. Bean 初始化完成: 一旦 @PostConstruct 方法执行完毕,Bean 就被认为是初始化完成的,可以正常使用了。此后,Bean 就可以响应应用程序的请求并执行其它业务逻辑。

需要注意的是,@PostConstruct 注解标记的方法只会在 Bean 的初始化阶段执行一次。如果你有多个 Bean,它们都使用了 @PostConstruct 注解,Spring 会按照它们在各自类中的声明顺序执行这些方法。

总之,@PostConstruct 注解方法提供了一个在 Bean 初始化完成后执行自定义初始化逻辑的方式,确保 Bean 处于可用状态。

注意⚠️:子类实例话过程中会调用父类的加了该注解的方法

具体实现如下:

@Component
@Slf4j
public class SysQuartzJobTest {

    @Autowired
    private ISysQuartzJobService sysQuartzJobService;

    @PostConstruct
    public void test() throws Exception{
        try {
            sysQuartzJobService.updateJobStartById("23dedbb5-af24-11ec-a42d-0894ef72d9c4");
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        log.info("PostConstruct测试");
    }
}

异步任务

  1. 创建一个异步方法:

    首先,你需要在Spring Bean中创建一个异步方法,用于执行预加载操作。你可以在这个方法上添加@Async注解,以便告诉Spring该方法应该在异步线程中执行。

    import org.springframework.scheduling.annotation.Async;
    import org.springframework.stereotype.Service;
    
    @Service
    public class MyService {
        @Async
        public void preloadData() {
            // 执行预加载操作的代码
        }
    }
    
  2. 配置TaskExecutor

    在你的应用中配置一个TaskExecutor,这将用于执行异步方法。你可以使用Spring Boot提供的默认TaskExecutor,或者自定义一个,具体取决于你的需求。

    import org.springframework.context.annotation.Bean;
    import org.springframework.core.task.SimpleAsyncTaskExecutor;
    import org.springframework.core.task.TaskExecutor;
    import org.springframework.stereotype.Component;
    
    @Component
    public class TaskExecutorConfig {
        @Bean
        public TaskExecutor taskExecutor() {
            return new SimpleAsyncTaskExecutor();
        }
    }
    
  3. 调用异步方法:

    在你的应用中调用异步方法,这将触发预加载操作。由于该方法被标记为异步,调用它不会阻塞主线程。

    @Autowired
    private MyService myService;
    
    public void someMethod() {
        // 其他同步操作
        myService.preloadData(); // 调用异步方法
        // 继续其他同步操作
    }
    
  4. 触发预加载操作:

    当你调用异步方法时,Spring Boot会自动将该方法委托给TaskExecutor,并在一个独立的线程中执行。这样,你的预加载操作将在后台运行,不会阻塞主线程,从而提高了应用的性能和响应性。

使用异步任务来实现预加载特别适合那些需要执行耗时操作的场景,如初始化大量数据、加载远程资源或执行复杂的计算。通过将这些操作放入异步方法中,你可以确保应用仍能保持高度响应,并且不会因为预加载而导致阻塞。

你可能感兴趣的:(springboot,spring,boot,后端,java,预加载)