预加载
当我们出现一些需求需要在项目启动的时候就去运行某个方法,比如定时任务
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","abc@163.com","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 的初始化过程中的执行顺序如下:
- Bean的实例化(Instantiation): 首先,Spring会实例化一个 Bean。这通常涉及到调用 Bean 类的构造函数来创建对象。在这一步完成后,Bean 的属性尚未设置。
- 依赖注入(Dependency Injection): 如果 Bean 有依赖关系,例如其他 Bean 或配置属性,Spring 将会注入这些依赖。这是通过自动装配(Autowired)或显式配置(例如通过 XML 或 Java 配置类)完成的。
@PostConstruct
方法的调用: 一旦依赖注入完成,Spring 会查找使用了@PostConstruct
注解的方法,并在此时调用它们。这些方法用于执行 Bean 的初始化操作。可以有多个方法都使用了@PostConstruct
注解,它们的执行顺序取决于它们在类中的声明顺序。- 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测试"); } }
异步任务
- 创建一个异步方法:
首先,你需要在Spring Bean中创建一个异步方法,用于执行预加载操作。你可以在这个方法上添加@Async
注解,以便告诉Spring该方法应该在异步线程中执行。
import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @Service public class MyService { @Async public void preloadData() { // 执行预加载操作的代码 } }
- 配置
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(); } }
- 调用异步方法:
在你的应用中调用异步方法,这将触发预加载操作。由于该方法被标记为异步,调用它不会阻塞主线程。
@Autowired private MyService myService; public void someMethod() { // 其他同步操作 myService.preloadData(); // 调用异步方法 // 继续其他同步操作 }
- 触发预加载操作:
当你调用异步方法时,Spring Boot会自动将该方法委托给TaskExecutor
,并在一个独立的线程中执行。这样,你的预加载操作将在后台运行,不会阻塞主线程,从而提高了应用的性能和响应性。
使用异步任务来实现预加载特别适合那些需要执行耗时操作的场景,如初始化大量数据、加载远程资源或执行复杂的计算。通过将这些操作放入异步方法中,你可以确保应用仍能保持高度响应,并且不会因为预加载而导致阻塞。