From a6b6ba88dc96b4121d103bb54471acf49320bc3b Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Fri, 20 Nov 2009 22:21:45 +0000 Subject: [PATCH] SPR-6368 The parser for the 'executor' element in the task namespace now creates a FactoryBean so that the pool-size range can be configured after property placeholder resolution when necessary. --- .../config/ExecutorBeanDefinitionParser.java | 58 +----- .../config/TaskExecutorFactoryBean.java | 169 ++++++++++++++++++ .../ExecutorBeanDefinitionParserTests.java | 38 ++++ .../scheduling/config/executorContext.xml | 33 +++- 4 files changed, 237 insertions(+), 61 deletions(-) create mode 100644 org.springframework.context/src/main/java/org/springframework/scheduling/config/TaskExecutorFactoryBean.java diff --git a/org.springframework.context/src/main/java/org/springframework/scheduling/config/ExecutorBeanDefinitionParser.java b/org.springframework.context/src/main/java/org/springframework/scheduling/config/ExecutorBeanDefinitionParser.java index d99be608de3..1448b3e4aa8 100644 --- a/org.springframework.context/src/main/java/org/springframework/scheduling/config/ExecutorBeanDefinitionParser.java +++ b/org.springframework.context/src/main/java/org/springframework/scheduling/config/ExecutorBeanDefinitionParser.java @@ -22,7 +22,6 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.core.JdkVersion; import org.springframework.util.StringUtils; /** @@ -36,12 +35,7 @@ public class ExecutorBeanDefinitionParser extends AbstractSingleBeanDefinitionPa @Override protected String getBeanClassName(Element element) { - if (shouldUseBackport(element)) { - return "org.springframework.scheduling.backportconcurrent.ThreadPoolTaskExecutor"; - } - else { - return "org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"; - } + return "org.springframework.scheduling.config.TaskExecutorFactoryBean"; } @Override @@ -56,48 +50,8 @@ public class ExecutorBeanDefinitionParser extends AbstractSingleBeanDefinitionPa } configureRejectionPolicy(element, builder); String poolSize = element.getAttribute("pool-size"); - if (!StringUtils.hasText(poolSize)) { - return; - } - Integer[] range = null; - try { - int separatorIndex = poolSize.indexOf('-'); - if (separatorIndex != -1) { - range = new Integer[2]; - range[0] = Integer.valueOf(poolSize.substring(0, separatorIndex)); - range[1] = Integer.valueOf(poolSize.substring(separatorIndex + 1, poolSize.length())); - if (range[0] > range[1]) { - parserContext.getReaderContext().error( - "Lower bound of pool-size range must not exceed the upper bound.", element); - } - if (!StringUtils.hasText(queueCapacity)) { - // no queue-capacity provided, so unbounded - if (range[0] == 0) { - // actually set 'corePoolSize' to the upper bound of the range - // but allow core threads to timeout - builder.addPropertyValue("allowCoreThreadTimeOut", true); - range[0] = range[1]; - } - else { - // non-zero lower bound implies a core-max size range - parserContext.getReaderContext().error( - "A non-zero lower bound for the size range requires a queue-capacity value.", element); - } - } - } - else { - Integer value = Integer.valueOf(poolSize); - range = new Integer[] {value, value}; - } - } - catch (NumberFormatException ex) { - parserContext.getReaderContext().error("Invalid pool-size value [" + poolSize + "]: only single " + - "maximum integer (e.g. \"5\") and minimum-maximum range (e.g. \"3-5\") are supported.", - element, ex); - } - if (range != null) { - builder.addPropertyValue("corePoolSize", range[0]); - builder.addPropertyValue("maxPoolSize", range[1]); + if (StringUtils.hasText(poolSize)) { + builder.addPropertyValue("poolSize", poolSize); } } @@ -129,10 +83,4 @@ public class ExecutorBeanDefinitionParser extends AbstractSingleBeanDefinitionPa builder.addPropertyValue("rejectedExecutionHandler", new RootBeanDefinition(policyClassName)); } - private boolean shouldUseBackport(Element element) { - String poolSize = element.getAttribute("pool-size"); - return (StringUtils.hasText(poolSize) && poolSize.startsWith("0") && - JdkVersion.getMajorJavaVersion() < JdkVersion.JAVA_16); - } - } diff --git a/org.springframework.context/src/main/java/org/springframework/scheduling/config/TaskExecutorFactoryBean.java b/org.springframework.context/src/main/java/org/springframework/scheduling/config/TaskExecutorFactoryBean.java new file mode 100644 index 00000000000..649374121c1 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/scheduling/config/TaskExecutorFactoryBean.java @@ -0,0 +1,169 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.scheduling.config; + +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.core.JdkVersion; +import org.springframework.core.task.TaskExecutor; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * FactoryBean for creating TaskExecutor instances. + * + * @author Mark Fisher + * @since 3.0 + */ +public class TaskExecutorFactoryBean implements FactoryBean, BeanNameAware { + + private volatile TaskExecutor target; + + private volatile BeanWrapper beanWrapper; + + private volatile String poolSize; + + private volatile Integer queueCapacity; + + private volatile Object rejectedExecutionHandler; + + private volatile Integer keepAliveSeconds; + + private volatile String beanName; + + private final Object initializationMonitor = new Object(); + + + public void setPoolSize(String poolSize) { + this.poolSize = poolSize; + } + + public void setQueueCapacity(int queueCapacity) { + this.queueCapacity = queueCapacity; + } + + public void setRejectedExecutionHandler(Object rejectedExecutionHandler) { + this.rejectedExecutionHandler = rejectedExecutionHandler; + } + + public void setKeepAliveSeconds(int keepAliveSeconds) { + this.keepAliveSeconds = keepAliveSeconds; + } + + public void setBeanName(String beanName) { + this.beanName = beanName; + } + + public Class getObjectType() { + if (this.target != null) { + return this.target.getClass(); + } + return TaskExecutor.class; + } + + public TaskExecutor getObject() throws Exception { + if (this.target == null) { + this.initializeExecutor(); + } + return this.target; + } + + public boolean isSingleton() { + return true; + } + + private void initializeExecutor() throws Exception { + synchronized (this.initializationMonitor) { + if (this.target != null) { + return; + } + String executorClassName = (shouldUseBackport(this.poolSize)) + ? "org.springframework.scheduling.backportconcurrent.ThreadPoolTaskExecutor" + : "org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"; + Class executorClass = getClass().getClassLoader().loadClass(executorClassName); + this.beanWrapper = new BeanWrapperImpl(executorClass); + this.setValueIfNotNull("queueCapacity", this.queueCapacity); + this.setValueIfNotNull("keepAliveSeconds", this.keepAliveSeconds); + this.setValueIfNotNull("rejectedExecutionHandler", this.rejectedExecutionHandler); + Integer[] range = this.determinePoolSizeRange(); + if (range != null) { + this.setValueIfNotNull("corePoolSize", range[0]); + this.setValueIfNotNull("maxPoolSize", range[1]); + } + this.target = (TaskExecutor) this.beanWrapper.getWrappedInstance(); + } + } + + private void setValueIfNotNull(String name, Object value) { + Assert.notNull(this.beanWrapper, "Property values cannot be set until the BeanWrapper has been created."); + if (value != null) { + this.beanWrapper.setPropertyValue(name, value); + } + } + + private Integer[] determinePoolSizeRange() { + if (!StringUtils.hasText(this.poolSize)) { + return null; + } + Integer[] range = null; + try { + int separatorIndex = poolSize.indexOf('-'); + if (separatorIndex != -1) { + range = new Integer[2]; + range[0] = Integer.valueOf(poolSize.substring(0, separatorIndex)); + range[1] = Integer.valueOf(poolSize.substring(separatorIndex + 1, poolSize.length())); + if (range[0] > range[1]) { + throw new BeanCreationException(this.beanName, + "Lower bound of pool-size range must not exceed the upper bound."); + } + if (this.queueCapacity == null) { + // no queue-capacity provided, so unbounded + if (range[0] == 0) { + // actually set 'corePoolSize' to the upper bound of the range + // but allow core threads to timeout + this.setValueIfNotNull("allowCoreThreadTimeOut", true); + range[0] = range[1]; + } + else { + // non-zero lower bound implies a core-max size range + throw new BeanCreationException(this.beanName, + "A non-zero lower bound for the size range requires a queue-capacity value."); + } + } + } + else { + Integer value = Integer.valueOf(poolSize); + range = new Integer[] {value, value}; + } + } + catch (NumberFormatException ex) { + throw new BeanCreationException(this.beanName, + "Invalid pool-size value [" + poolSize + "]: only single maximum integer " + + "(e.g. \"5\") and minimum-maximum range (e.g. \"3-5\") are supported.", ex); + } + return range; + } + + private boolean shouldUseBackport(String poolSize) { + return (StringUtils.hasText(poolSize) && poolSize.startsWith("0") && + JdkVersion.getMajorJavaVersion() < JdkVersion.JAVA_16); + } + +} diff --git a/org.springframework.context/src/test/java/org/springframework/scheduling/config/ExecutorBeanDefinitionParserTests.java b/org.springframework.context/src/test/java/org/springframework/scheduling/config/ExecutorBeanDefinitionParserTests.java index 02bd56bf3c2..50d9c5ad637 100644 --- a/org.springframework.context/src/test/java/org/springframework/scheduling/config/ExecutorBeanDefinitionParserTests.java +++ b/org.springframework.context/src/test/java/org/springframework/scheduling/config/ExecutorBeanDefinitionParserTests.java @@ -22,6 +22,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.DirectFieldAccessor; +import org.springframework.beans.factory.BeanCreationException; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -56,6 +57,11 @@ public class ExecutorBeanDefinitionParserTests { assertEquals(42, this.getMaxPoolSize(executor)); } + @Test(expected = BeanCreationException.class) + public void invalidPoolSize() { + this.context.getBean("invalidPoolSize"); + } + @Test public void rangeWithBoundedQueue() { Object executor = this.context.getBean("rangeWithBoundedQueue"); @@ -74,6 +80,38 @@ public class ExecutorBeanDefinitionParserTests { assertEquals(Integer.MAX_VALUE, this.getQueueCapacity(executor)); } + @Test + public void propertyPlaceholderWithSingleSize() { + Object executor = this.context.getBean("propertyPlaceholderWithSingleSize"); + assertEquals(123, this.getCorePoolSize(executor)); + assertEquals(123, this.getMaxPoolSize(executor)); + assertEquals(60, this.getKeepAliveSeconds(executor)); + assertEquals(false, this.getAllowCoreThreadTimeOut(executor)); + assertEquals(Integer.MAX_VALUE, this.getQueueCapacity(executor)); + } + + @Test + public void propertyPlaceholderWithRange() { + Object executor = this.context.getBean("propertyPlaceholderWithRange"); + assertEquals(5, this.getCorePoolSize(executor)); + assertEquals(25, this.getMaxPoolSize(executor)); + assertEquals(false, this.getAllowCoreThreadTimeOut(executor)); + assertEquals(10, this.getQueueCapacity(executor)); + } + + @Test + public void propertyPlaceholderWithRangeAndCoreThreadTimeout() { + Object executor = this.context.getBean("propertyPlaceholderWithRangeAndCoreThreadTimeout"); + assertEquals(99, this.getCorePoolSize(executor)); + assertEquals(99, this.getMaxPoolSize(executor)); + assertEquals(true, this.getAllowCoreThreadTimeOut(executor)); + } + + @Test(expected = BeanCreationException.class) + public void propertyPlaceholderWithInvalidPoolSize() { + this.context.getBean("propertyPlaceholderWithInvalidPoolSize"); + } + private int getCorePoolSize(Object executor) { return (Integer) new DirectFieldAccessor(executor).getPropertyValue("corePoolSize"); diff --git a/org.springframework.context/src/test/resources/org/springframework/scheduling/config/executorContext.xml b/org.springframework.context/src/test/resources/org/springframework/scheduling/config/executorContext.xml index 49f5e47c9c2..4137c32b646 100644 --- a/org.springframework.context/src/test/resources/org/springframework/scheduling/config/executorContext.xml +++ b/org.springframework.context/src/test/resources/org/springframework/scheduling/config/executorContext.xml @@ -1,11 +1,13 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:task="http://www.springframework.org/schema/task" + xmlns:context="http://www.springframework.org/schema/context" + xmlns:util="http://www.springframework.org/schema/util" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd + http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> @@ -15,4 +17,23 @@ + + + + + + + + + + + + + + 123 + 5-25 + 0-99 + 22-abc + +