使用SpringBoot 2集成Quartz和RabbitMQ是一件非常简单非常方便的事情,甚至可以达到了零配置运行,大大方便了我们的使用。但是过于智能化也带来了一个问题,就是在控制的时候不太好控制。比如我的项目里集成了Quartz,但是需要部署到不同的服务器上,其中一个服务器需要运行Quartz,另一个服务器则不要运行Quartz。众所周知,像Quartz和RabbitMQ这类框架,在集成到SpringBoot之后, 都是会自动运行的,但是不可能要我打包不同的配置到不同的服务器上吧。
经过查看代码,发现此类自运行的框架都是实现了spring的SmartLifecycle接口,详看:Spring SmartLifecycle 在容器所有bean加载和初始化完毕执行,发现有个方法:
/**
* 根据该方法的返回值决定是否执行start方法。
* 返回true时start方法会被自动执行,返回false则不会。
*/
public boolean isAutoStartup() {
// 默认为false
return true;
}
这个方法就是关键了。根据解释,当此方法返回false时,不会自动执行start()方法。
1.首先查看Quartz。说到Quartz,肯定从核心类看起。我们知道任务的启动与暂停都跟QuartzScheduler有关,所以先从QuartzScheduler的start()方法看起。通过debug,往上一直找到SchedulerFactoryBean
//---------------------------------------------------------------------
// Implementation of SmartLifecycle interface
//---------------------------------------------------------------------
@Override
public void start() throws SchedulingException {
if (this.scheduler != null) {
try {
startScheduler(this.scheduler, this.startupDelay);
}
catch (SchedulerException ex) {
throw new SchedulingException("Could not start Quartz Scheduler", ex);
}
}
}
可以看到是实现了SmartLifecycle接口。那就简单了,直接去找isAutoStartup方法,看到是返回了autoStartup字段。
@Override
public boolean isAutoStartup() {
return this.autoStartup;
}
刚好看到了
public void setAutoStartup(boolean autoStartup) {
this.autoStartup = autoStartup;
}
大功告成,只要找到SchedulerFactoryBean,然后调用setAutoStartup方法就行了。
下面是springboot2集成quartz的SchedulerFactoryBean配置代码
/**
* @Description 对SchedulerFactoryBean的配置。
* @Author yangpeng
* @Date 2018/9/18
**/
@Component
public class SchedulerConfig implements SchedulerFactoryBeanCustomizer {
@Value("${mongcent.init.task}")
private boolean runTask = false;
@Override
public void customize(SchedulerFactoryBean schedulerFactoryBean) {
//如果项目不运行quartz定时任务的话,就不自动运行了
if (!runTask) {
schedulerFactoryBean.setAutoStartup(false);
}
schedulerFactoryBean.setStartupDelay(10);
schedulerFactoryBean.setOverwriteExistingJobs(true);
}
}
2.查看RabbitMQ。首先找到RabbitMQ的启动方法,查资料可以知道是RabbitListenerEndpointRegistry.start()方法启动MQ,RabbitListenerEndpointRegistry.stop()方法关闭MQ。
@Override
public void start() {
for (MessageListenerContainer listenerContainer : getListenerContainers()) {
startIfNecessary(listenerContainer);
}
}
/**
* Start the specified {@link MessageListenerContainer} if it should be started
* on startup or when start is called explicitly after startup.
* @param listenerContainer the container.
* @see MessageListenerContainer#isAutoStartup()
*/
private void startIfNecessary(MessageListenerContainer listenerContainer) {
if (this.contextRefreshed || listenerContainer.isAutoStartup()) {
listenerContainer.start();
}
}
可以看到,是MessageListenerContainer类中的isAutoStartup方法决定是否要运行MQ,listenerContainer.isAutoStartup()的值从哪里来呢?我们来看一下MessageListenerContainer的初始化:在start()方法里看到是来自getListenerContainers()方法。
/**
* @return the managed {@link MessageListenerContainer} instance(s).
*/
public Collection getListenerContainers() {
return Collections.unmodifiableCollection(this.listenerContainers.values());
}
再查找this.listenerContainers是从哪里来的
/**
* Create a message listener container for the given {@link RabbitListenerEndpoint}.
* This create the necessary infrastructure to honor that endpoint
* with regards to its configuration.
*
The {@code startImmediately} flag determines if the container should be
* started immediately.
* @param endpoint the endpoint to add.
* @param factory the {@link RabbitListenerContainerFactory} to use.
* @param startImmediately start the container immediately if necessary
* @see #getListenerContainers()
* @see #getListenerContainer(String)
*/
@SuppressWarnings("unchecked")
public void registerListenerContainer(RabbitListenerEndpoint endpoint, RabbitListenerContainerFactory> factory,
boolean startImmediately) {
Assert.notNull(endpoint, "Endpoint must not be null");
Assert.notNull(factory, "Factory must not be null");
String id = endpoint.getId();
Assert.hasText(id, "Endpoint id must not be empty");
synchronized (this.listenerContainers) {
Assert.state(!this.listenerContainers.containsKey(id),
"Another endpoint is already registered with id '" + id + "'");
MessageListenerContainer container = createListenerContainer(endpoint, factory);
this.listenerContainers.put(id, container);
if (StringUtils.hasText(endpoint.getGroup()) && this.applicationContext != null) {
List containerGroup;
if (this.applicationContext.containsBean(endpoint.getGroup())) {
containerGroup = this.applicationContext.getBean(endpoint.getGroup(), List.class);
}
else {
containerGroup = new ArrayList();
this.applicationContext.getBeanFactory().registerSingleton(endpoint.getGroup(), containerGroup);
}
containerGroup.add(container);
}
if (startImmediately) {
startIfNecessary(container);
}
}
}
看到下面这两行代码了吗
MessageListenerContainer container = createListenerContainer(endpoint, factory);
this.listenerContainers.put(id, container);
在createListenerContainer方法看到是从RabbitListenerContainerFactory.createListenerContainer方法生成的。
MessageListenerContainer listenerContainer = factory.createListenerContainer(endpoint);
看一下实现类,在AbstractRabbitListenerContainerFactory中,看到配置了一大堆属性,其中就有我们关心的autoStartup,返回的是 this.autoStartup
else if (this.autoStartup != null) {
instance.setAutoStartup(this.autoStartup);
}
很自然的,就找到了set方法
/**
* @param autoStartup true for auto startup.
* @see AbstractMessageListenerContainer#setAutoStartup(boolean)
*/
public void setAutoStartup(Boolean autoStartup) {
this.autoStartup = autoStartup;
}
那么,如何才能调用AbstractRabbitListenerContainerFactory中的setAutoStartup方法呢?有没有觉得这个类名有点眼熟,这是一个抽象类,我们去看一下有哪些子类。一看,看到了SimpleRabbitListenerContainerFactory,这不就是我们配置消费者属性的那个配置类吗,完美!直接调用setAutoStartup方法就解决了
最后给出配置。
@Bean(name = "myRabbitListenerContainer")
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
//如果项目没有
if (!runMq) {
//是否自动启动
factory.setAutoStartup(false);
}
return factory;
}
结语:转了一大圈,不过就是在配置里面加上两行代码而已,似乎没什么用。但看了这一圈,让我对spring的启动过程有了更多的认识,同时学习了优秀框架的一些设计思维,这是一件非常值得去做的事