Java 为什么 Spring 4 在一个上下文中只允许一个 TaskScheduler?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21758116/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 10:30:52  来源:igfitidea点击:

Why does Spring 4 only allow one TaskScheduler in a context?

javaspring

提问by user2112901

We have a Spring web application we're porting from Spring 3.2 to Spring 4. Our application has several sub-contexts assembled into a single runtime context when the web application is started.

我们有一个从 Spring 3.2 移植到 Spring 4 的 Spring Web 应用程序。当 Web 应用程序启动时,我们的应用程序将多个子上下文组装到一个运行时上下文中。

We use separate TaskSchedulers in two of our sub-contexts. With Spring 3.2 this works fine; when using Spring 4 we get an exception with the following message:

我们在两个子上下文中使用单独的 TaskScheduler。使用 Spring 3.2 可以正常工作;使用 Spring 4 时,我们收到以下消息的异常:

java.lang.IllegalStateException: More than one TaskScheduler and/or ScheduledExecutorService  exist within the context. Remove all but one of the beans; or implement the SchedulingConfigurer interface and call ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback. Found the following beans: [commonScheduler, communicationTaskScheduler]
        at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.onApplicationEvent(ScheduledAnnotationBeanPostProcessor.java:289) ~[spring-context-4.0.1.RELEASE.jar:4.0.1.RELEASE]
        at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.onApplicationEvent(ScheduledAnnotationBeanPostProcessor.java:72) ~[spring-context-4.0.1.RELEASE.jar:4.0.1.RELEASE]
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:98) ~[spring-context-4.0.1.RELEASE.jar:4.0.1.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:333) ~[spring-context-4.0.1.RELEASE.jar:4.0.1.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:776) ~[spring-context-4.0.1.RELEASE.jar:4.0.1.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:485) ~[spring-context-4.0.1.RELEASE.jar:4.0.1.RELEASE]
        at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:403) ~[spring-web-4.0.1.RELEASE.jar:4.0.1.RELEASE]
        at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:306) ~[spring-web-4.0.1.RELEASE.jar:4.0.1.RELEASE]
        at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:106) ~[spring-web-4.0.1.RELEASE.jar:4.0.1.RELEASE]
        at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4961) ~[catalina.jar:7.0.50]
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5455) ~[catalina.jar:7.0.50]
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) ~[catalina.jar:7.0.50]
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901) ~[catalina.jar:7.0.50]
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877) ~[catalina.jar:7.0.50]
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:634) ~[catalina.jar:7.0.50]
        at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:1074) ~[catalina.jar:7.0.26]
        at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1858) ~[catalina.jar:7.0.26]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) ~[na:1.7.0_25]
        at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334) ~[na:1.7.0_25]
        at java.util.concurrent.FutureTask.run(FutureTask.java:166) ~[na:1.7.0_25]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) ~[na:1.7.0_25]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) ~[na:1.7.0_25]
        at java.lang.Thread.run(Thread.java:724) ~[na:1.7.0_25]

One scheduler is defined via:

一个调度程序通过以下方式定义:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd">

    <!-- Enables annotation-driven task scheduling; detects @Scheduled- and 
        @Async-annotated process methods to be invoked via proxy -->
    <task:annotation-driven mode="aspectj" />
    <task:scheduler id="commonScheduler" pool-size="5" />

</beans>

The other scheduler is defined in (additional beans removed for clarity):

另一个调度程序定义在(为了清楚起见,删除了额外的 bean):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <context:spring-configured />

    <bean id="communicationExecutor"
        class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="corePoolSize" value="15" />
        <property name="maxPoolSize" value="20" />
        <property name="queueCapacity" value="20" />
    </bean>
    <bean id="communicationTaskScheduler"
        class="org.springframework.scheduling.concurrent.ConcurrentTaskScheduler">
        <property name="concurrentExecutor" ref="communicationExecutor" />
    </bean>

</beans>

The contexts are assembled at runtime using (additional contexts removed for clarity):

上下文在运行时使用(为了清楚起见,删除了其他上下文):

<beans xmlns="http://www.springframework.org/schema/beans"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <import resource="classpath:spring/tasks-context.xml" />
    <import resource="classpath:spring/collectors-context.xml" />
</beans>

Why does Spring 4 have this restriction? How should one work around it?

为什么 Spring 4 有这个限制?应该如何解决它?

回答by Ryan J. McDonough

Yeah, this is a change in behavior for sure. It appears that the ScheduledAnnotationBeanPostProcessornow has a limit of 2 schedulers per context. I ran into this with Spring 4's new WebSocket message broker as it allocates 2 schedulers for the Simple STOMP broker and the SockJS adapter. When I added my own, it totally dorked out with the same message you got. I found it rather annoying that I had to find out this limitation via errors rather documentation. This gotcha doesn't appear to be described in the Spring 4 documentation.

是的,这肯定是行为的改变。似乎ScheduledAnnotationBeanPostProcessor现在每个上下文有 2 个调度程序的限制。我在 Spring 4 的新 WebSocket 消息代理中遇到了这个问题,因为它为 Simple STOMP 代理和 SockJS 适配器分配了 2 个调度程序。当我添加自己的时,它完全与您收到的消息相同。我发现我不得不通过错误而不是文档来找出这个限制,这很烦人。Spring 4 文档中似乎没有描述这个问题。

The solution is to create your own SchedulingConfigurerthat manages its own TaskScheduler. I suspect that the reason is that there is only one and additional TaskSchedulerimplementations need to be added in order to isolate them from one another. I did something like so:

解决方案是创建自己的SchedulingConfigurer管理自己的TaskScheduler. 我怀疑原因是只有一个并且TaskScheduler需要添加额外的实现才能将它们彼此隔离。我做了这样的事情:

@Configuration
@EnableScheduling
public class MySchedulingConfigurer implements SchedulingConfigurer
{
   @Bean
   public TimedThingy timedThingy()
   {
      return new TimedThingy();
   }

  @Bean()
  public ThreadPoolTaskScheduler taskScheduler() {
     return new ThreadPoolTaskScheduler();
  }

  @Override
  public void configureTasks(ScheduledTaskRegistrar taskRegistrar)
  {
      taskRegistrar.setTaskScheduler(taskScheduler());
      taskRegistrar.addFixedRateTask(new Runnable()
      {
         public void run()
         {
            timedThingy().sendIt();
         }
      }, 1000);
  }
}

Once I did that, the issue went away and things worked as desired. The down side is that you can't use annotations like @Scheduled, etc.. But you do gain more control and things work. Hope this helps.

一旦我这样做了,问题就消失了,事情按预期进行。不利的一面是您不能使用诸如@Scheduled, 之类的注释。但是您确实获得了更多控制权并且一切正常。希望这可以帮助。

回答by vladimir83

You need to explicitly specify which scheduler to use with @Scheduledannotations. Just add schedulerattribute:

您需要明确指定与@Scheduled注释一起使用的调度程序。只需添加scheduler属性:

<task:annotation-driven scheduler="commonScheduler" mode="aspectj" />
<task:scheduler id="commonScheduler" pool-size="5" />

回答by Kovalsky Dmitryi

Just add bean to your config:

只需将 bean 添加到您的配置中:

 @Bean()
 public ThreadPoolTaskScheduler taskScheduler() {
    return new ThreadPoolTaskScheduler();
 }