diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/task/ExecutorConfig.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/task/ExecutorConfig.java index 3fac42679..7bcb2c085 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/task/ExecutorConfig.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/task/ExecutorConfig.java @@ -2,6 +2,7 @@ package com.bsd.pjcloud.hard.task; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.security.concurrent.DelegatingSecurityContextExecutor; @@ -12,6 +13,7 @@ import java.util.concurrent.Executors; public class ExecutorConfig { @Bean + @Primary public ThreadPoolTaskExecutor hardTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/task/ExecutorConfigContextTest.java b/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/task/ExecutorConfigContextTest.java new file mode 100644 index 000000000..d42832e45 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/task/ExecutorConfigContextTest.java @@ -0,0 +1,91 @@ +package com.bsd.pjcloud.hard.task; + +import com.bsd.pjcloud.common.asynctask.config.AsyncTaskAutoConfiguration; +import com.bsd.pjcloud.common.asynctask.service.AsyncTaskExecutorService; +import com.bsd.pjcloud.common.core.config.AsyncConfiguration; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.transaction.support.TransactionTemplate; + +import static com.bsd.pjcloud.common.asynctask.config.AsyncTaskAutoConfiguration.ASYNC_TASK_WORKER_EXECUTOR_BEAN_NAME; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** 验证新增公共任务线程池后,实际 Spring 容器仍能确定默认异步执行器。 */ +class ExecutorConfigContextTest { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(ExecutorConfig.class) + .withConfiguration(AutoConfigurations.of(AsyncConfiguration.class)); + + @Test + void originalSingleExecutorStartsWithoutPrimary() { + withoutPrimary(contextRunner).run(context -> { + assertThat(context).hasNotFailed(); + assertThat(context.getBean(AsyncConfiguration.class).getAsyncExecutor()) + .isSameAs(context.getBean("hardTaskExecutor")); + }); + } + + @Test + void addingCommonExecutorWithoutPrimaryReproducesStartupFailure() { + withoutPrimary(withCommonExecutor()).run(context -> { + assertThat(context).hasFailed(); + assertThat(context.getStartupFailure()) + .hasRootCauseInstanceOf(NoUniqueBeanDefinitionException.class) + .hasStackTraceContaining("hardTaskExecutor,commonAsyncTaskWorkerExecutor"); + }); + } + + @Test + void primaryKeepsDefaultAsyncAndCommonTaskExecutorsSeparate() { + withCommonExecutor().run(context -> { + assertThat(context).hasNotFailed(); + assertThat(context.getBeansOfType(ThreadPoolTaskExecutor.class)) + .containsOnlyKeys("hardTaskExecutor", ASYNC_TASK_WORKER_EXECUTOR_BEAN_NAME); + assertThat(context.getBean(AsyncConfiguration.class).getAsyncExecutor()) + .isSameAs(context.getBean("hardTaskExecutor")); + assertThat(ReflectionTestUtils.getField(context.getBean(AsyncTaskExecutorService.class), + "asyncTaskWorkerExecutor")) + .isSameAs(context.getBean(ASYNC_TASK_WORKER_EXECUTOR_BEAN_NAME)); + }); + } + + private ApplicationContextRunner withCommonExecutor() { + return contextRunner.withUserConfiguration(TestInfrastructure.class) + .withConfiguration(AutoConfigurations.of(AsyncTaskAutoConfiguration.class)) + .withInitializer(context -> context.addBeanFactoryPostProcessor(beanFactory -> { + // 公共工厂方法返回接口类型,先实例化才能暴露真实线程池类型;覆盖线上触发冲突的顺序。 + for (String name : beanFactory.getBeanNamesForType(AsyncConfiguration.class, false, false)) { + beanFactory.getBeanDefinition(name).setDependsOn(ASYNC_TASK_WORKER_EXECUTOR_BEAN_NAME); + } + })); + } + + private ApplicationContextRunner withoutPrimary(ApplicationContextRunner runner) { + return runner.withInitializer(context -> context.addBeanFactoryPostProcessor(beanFactory -> + beanFactory.getBeanDefinition("hardTaskExecutor").setPrimary(false))); + } + + @Configuration(proxyBeanMethods = false) + static class TestInfrastructure { + + @Bean + @SuppressWarnings("unchecked") + RedisTemplate redisTemplate() { + return mock(RedisTemplate.class); + } + + @Bean + TransactionTemplate transactionTemplate() { + return mock(TransactionTemplate.class); + } + } +}