当我们出现一些需求需要在项目启动的时候就去运行某个方法,比如定时任务
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的方式去执行。
当容器初始化完成之后会调用CommandLineRunner中的run()方法,同样能够达到容器启动之后完成一些事情。这种方式和ApplicationListener相比更加灵活,如下:
/**
* @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
这里先讲以下这个注解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
,并在一个独立的线程中执行。这样,你的预加载操作将在后台运行,不会阻塞主线程,从而提高了应用的性能和响应性。
使用异步任务来实现预加载特别适合那些需要执行耗时操作的场景,如初始化大量数据、加载远程资源或执行复杂的计算。通过将这些操作放入异步方法中,你可以确保应用仍能保持高度响应,并且不会因为预加载而导致阻塞。