From 61d893257ed02ed041ea1bc03e9f1ab3ec163015 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Fri, 25 Sep 2020 13:41:10 +0200 Subject: [PATCH] Rewrite "performance" test to JMH benchmarks This commit rewrites the remaining "fastEnough" performance tests into proper JMH benchmarks. See gh-24830 --- .../AbstractPropertyAccessorBenchmark.java | 101 ++++++++ .../ConcurrentBeanFactoryBenchmark.java | 83 +++++++ .../DefaultListableBeanFactoryBenchmark.java | 142 ++++++++++++ ...oncurrentBeanFactoryBenchmark-context.xml} | 0 .../beans/AbstractPropertyAccessorTests.java | 60 ----- .../factory/ConcurrentBeanFactoryTests.java | 168 -------------- .../DefaultListableBeanFactoryTests.java | 217 ------------------ .../AnnotationProcessorBenchmark.java | 105 +++++++++ ...ApplicationContextExpressionBenchmark.java | 71 ++++++ .../AspectJAutoProxyCreatorTests.java | 73 ------ .../aop/framework/AbstractAopProxyTests.java | 33 --- .../AnnotationProcessorPerformanceTests.java | 161 ------------- .../ApplicationContextExpressionTests.java | 35 --- ...onUtilsUniqueDeclaredMethodsBenchmark.java | 63 +++++ .../util/ReflectionUtilsTests.java | 38 --- .../expression/spel/SpelBenchmark.java | 106 +++++++++ .../expression/spel/MapAccessTests.java | 36 +-- .../expression/spel/PerformanceTests.java | 134 ----------- .../MockMvcWebClientBuilderTests.java | 9 - .../MockMvcHtmlUnitDriverBuilderTests.java | 9 - .../bind/ServletRequestUtilsBenchmark.java | 74 ++++++ .../web/bind/ServletRequestUtilsTests.java | 81 ------- 22 files changed, 752 insertions(+), 1047 deletions(-) create mode 100644 spring-beans/src/jmh/java/org/springframework/beans/AbstractPropertyAccessorBenchmark.java create mode 100644 spring-beans/src/jmh/java/org/springframework/beans/factory/ConcurrentBeanFactoryBenchmark.java create mode 100644 spring-beans/src/jmh/java/org/springframework/beans/factory/DefaultListableBeanFactoryBenchmark.java rename spring-beans/src/{test/resources/org/springframework/beans/factory/ConcurrentBeanFactoryTests-context.xml => jmh/resources/org/springframework/beans/factory/ConcurrentBeanFactoryBenchmark-context.xml} (100%) delete mode 100644 spring-beans/src/test/java/org/springframework/beans/factory/ConcurrentBeanFactoryTests.java create mode 100644 spring-context/src/jmh/java/org/springframework/context/annotation/AnnotationProcessorBenchmark.java create mode 100644 spring-context/src/jmh/java/org/springframework/context/expression/ApplicationContextExpressionBenchmark.java delete mode 100644 spring-context/src/test/java/org/springframework/context/annotation/AnnotationProcessorPerformanceTests.java create mode 100644 spring-core/src/jmh/java/org/springframework/util/ReflectionUtilsUniqueDeclaredMethodsBenchmark.java create mode 100644 spring-expression/src/jmh/java/org/springframework/expression/spel/SpelBenchmark.java delete mode 100644 spring-expression/src/test/java/org/springframework/expression/spel/PerformanceTests.java create mode 100644 spring-web/src/jmh/java/org/springframework/web/bind/ServletRequestUtilsBenchmark.java diff --git a/spring-beans/src/jmh/java/org/springframework/beans/AbstractPropertyAccessorBenchmark.java b/spring-beans/src/jmh/java/org/springframework/beans/AbstractPropertyAccessorBenchmark.java new file mode 100644 index 00000000000..d407318f103 --- /dev/null +++ b/spring-beans/src/jmh/java/org/springframework/beans/AbstractPropertyAccessorBenchmark.java @@ -0,0 +1,101 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.beans; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import org.springframework.beans.propertyeditors.CustomNumberEditor; +import org.springframework.beans.propertyeditors.StringTrimmerEditor; + +/** + * Benchmark for {@link AbstractPropertyAccessor} use on beans. + * + * @author Brian Clozel + */ +@BenchmarkMode(Mode.Throughput) +public class AbstractPropertyAccessorBenchmark { + + @State(Scope.Benchmark) + public static class BenchmarkState { + + @Param({"DirectFieldAccessor", "BeanWrapper"}) + public String accessor; + + @Param({"none", "stringTrimmer", "numberOnPath", "numberOnNestedPath", "numberOnType"}) + public String customEditor; + + public int[] input; + + public PrimitiveArrayBean target; + + public AbstractPropertyAccessor propertyAccessor; + + @Setup + public void setup() { + this.target = new PrimitiveArrayBean(); + this.input = new int[1024]; + if (this.accessor.equals("DirectFieldAccessor")) { + this.propertyAccessor = new DirectFieldAccessor(this.target); + } + else { + this.propertyAccessor = new BeanWrapperImpl(this.target); + } + switch (this.customEditor) { + case "stringTrimmer": + this.propertyAccessor.registerCustomEditor(String.class, new StringTrimmerEditor(false)); + break; + case "numberOnPath": + this.propertyAccessor.registerCustomEditor(int.class, "array.somePath", new CustomNumberEditor(Integer.class, false)); + break; + case "numberOnNestedPath": + this.propertyAccessor.registerCustomEditor(int.class, "array[0].somePath", new CustomNumberEditor(Integer.class, false)); + break; + case "numberOnType": + this.propertyAccessor.registerCustomEditor(int.class, new CustomNumberEditor(Integer.class, false)); + break; + } + + } + + } + + @Benchmark + public PrimitiveArrayBean setPropertyValue(BenchmarkState state) { + state.propertyAccessor.setPropertyValue("array", state.input); + return state.target; + } + + @SuppressWarnings("unused") + private static class PrimitiveArrayBean { + + private int[] array; + + public int[] getArray() { + return this.array; + } + + public void setArray(int[] array) { + this.array = array; + } + } +} diff --git a/spring-beans/src/jmh/java/org/springframework/beans/factory/ConcurrentBeanFactoryBenchmark.java b/spring-beans/src/jmh/java/org/springframework/beans/factory/ConcurrentBeanFactoryBenchmark.java new file mode 100644 index 00000000000..3214290d187 --- /dev/null +++ b/spring-beans/src/jmh/java/org/springframework/beans/factory/ConcurrentBeanFactoryBenchmark.java @@ -0,0 +1,83 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.beans.factory; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.infra.Blackhole; + +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.beans.propertyeditors.CustomDateEditor; + +import static org.springframework.core.testfixture.io.ResourceTestUtils.qualifiedResource; + +/** + * Benchmark for creating prototype beans in a concurrent fashion. + * This benchmark requires to customize the number of worker threads {@code -t } on the + * CLI when running this particular benchmark to leverage concurrency. + * + * @author Brian Clozel + */ +@BenchmarkMode(Mode.Throughput) +public class ConcurrentBeanFactoryBenchmark { + + @State(Scope.Benchmark) + public static class BenchmarkState { + + public DefaultListableBeanFactory factory; + + @Setup + public void setup() { + this.factory = new DefaultListableBeanFactory(); + new XmlBeanDefinitionReader(this.factory).loadBeanDefinitions( + qualifiedResource(ConcurrentBeanFactoryBenchmark.class, "context.xml")); + + this.factory.addPropertyEditorRegistrar( + registry -> registry.registerCustomEditor(Date.class, + new CustomDateEditor(new SimpleDateFormat("yyyy/MM/dd"), false))); + } + + } + + @Benchmark + public void concurrentBeanCreation(BenchmarkState state, Blackhole bh) { + bh.consume(state.factory.getBean("bean1")); + bh.consume(state.factory.getBean("bean2")); + } + + + public static class ConcurrentBean { + + private Date date; + + public Date getDate() { + return this.date; + } + + public void setDate(Date date) { + this.date = date; + } + } +} diff --git a/spring-beans/src/jmh/java/org/springframework/beans/factory/DefaultListableBeanFactoryBenchmark.java b/spring-beans/src/jmh/java/org/springframework/beans/factory/DefaultListableBeanFactoryBenchmark.java new file mode 100644 index 00000000000..ec828fb185b --- /dev/null +++ b/spring-beans/src/jmh/java/org/springframework/beans/factory/DefaultListableBeanFactoryBenchmark.java @@ -0,0 +1,142 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.beans.factory; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.testfixture.beans.LifecycleBean; +import org.springframework.beans.testfixture.beans.TestBean; + +/** + * Benchmark for retrieving various bean types from the {@link DefaultListableBeanFactory}. + * + * @author Brian Clozel + */ +@BenchmarkMode(Mode.Throughput) +public class DefaultListableBeanFactoryBenchmark { + + public static class Shared { + public DefaultListableBeanFactory beanFactory; + } + + @State(Scope.Benchmark) + public static class PrototypeCreationState extends Shared { + + @Param({"simple", "dependencyCheck", "constructor", "constructorArgument", "properties", "resolvedProperties"}) + public String mode; + + @Setup + public void setup() { + this.beanFactory = new DefaultListableBeanFactory(); + RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class); + + switch (this.mode) { + case "simple": + break; + case "dependencyCheck": + rbd = new RootBeanDefinition(LifecycleBean.class); + rbd.setDependencyCheck(RootBeanDefinition.DEPENDENCY_CHECK_OBJECTS); + this.beanFactory.addBeanPostProcessor(new LifecycleBean.PostProcessor()); + break; + case "constructor": + rbd.getConstructorArgumentValues().addGenericArgumentValue("juergen"); + rbd.getConstructorArgumentValues().addGenericArgumentValue("99"); + break; + case "constructorArgument": + rbd.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference("spouse")); + this.beanFactory.registerBeanDefinition("test", rbd); + this.beanFactory.registerBeanDefinition("spouse", new RootBeanDefinition(TestBean.class)); + break; + case "properties": + rbd.getPropertyValues().add("name", "juergen"); + rbd.getPropertyValues().add("age", "99"); + break; + case "resolvedProperties": + rbd.getPropertyValues().add("spouse", new RuntimeBeanReference("spouse")); + this.beanFactory.registerBeanDefinition("spouse", new RootBeanDefinition(TestBean.class)); + break; + } + rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE); + this.beanFactory.registerBeanDefinition("test", rbd); + this.beanFactory.freezeConfiguration(); + } + + } + + @Benchmark + public Object prototypeCreation(PrototypeCreationState state) { + return state.beanFactory.getBean("test"); + } + + @State(Scope.Benchmark) + public static class SingletonLookupState extends Shared { + + @Setup + public void setup() { + this.beanFactory = new DefaultListableBeanFactory(); + this.beanFactory.registerBeanDefinition("test", new RootBeanDefinition(TestBean.class)); + this.beanFactory.freezeConfiguration(); + } + } + + @Benchmark + public Object singletLookup(SingletonLookupState state) { + return state.beanFactory.getBean("test"); + } + + @Benchmark + public Object singletLookupByType(SingletonLookupState state) { + return state.beanFactory.getBean(TestBean.class); + } + + @State(Scope.Benchmark) + public static class SingletonLookupManyBeansState extends Shared { + + @Setup + public void setup() { + this.beanFactory = new DefaultListableBeanFactory(); + this.beanFactory.registerBeanDefinition("test", new RootBeanDefinition(TestBean.class)); + for (int i = 0; i < 1000; i++) { + this.beanFactory.registerBeanDefinition("a" + i, new RootBeanDefinition(A.class)); + } + this.beanFactory.freezeConfiguration(); + } + } + + // See SPR-6870 + @Benchmark + public Object singletLookupByTypeManyBeans(SingletonLookupState state) { + return state.beanFactory.getBean(B.class); + } + + static class A { + } + + static class B { + } + +} diff --git a/spring-beans/src/test/resources/org/springframework/beans/factory/ConcurrentBeanFactoryTests-context.xml b/spring-beans/src/jmh/resources/org/springframework/beans/factory/ConcurrentBeanFactoryBenchmark-context.xml similarity index 100% rename from spring-beans/src/test/resources/org/springframework/beans/factory/ConcurrentBeanFactoryTests-context.xml rename to spring-beans/src/jmh/resources/org/springframework/beans/factory/ConcurrentBeanFactoryBenchmark-context.xml diff --git a/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java b/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java index 924ac6100d5..0bb5febde05 100644 --- a/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java @@ -34,11 +34,9 @@ import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; -import org.apache.commons.logging.LogFactory; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowire; -import org.springframework.beans.propertyeditors.CustomNumberEditor; import org.springframework.beans.propertyeditors.StringArrayPropertyEditor; import org.springframework.beans.propertyeditors.StringTrimmerEditor; import org.springframework.beans.support.DerivedFromProtectedBaseBean; @@ -51,17 +49,13 @@ import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.GenericConversionService; -import org.springframework.core.testfixture.Assume; -import org.springframework.core.testfixture.EnabledForTestGroups; import org.springframework.lang.Nullable; -import org.springframework.util.StopWatch; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.within; -import static org.springframework.core.testfixture.TestGroup.PERFORMANCE; /** * Shared tests for property accessors. @@ -959,60 +953,6 @@ public abstract class AbstractPropertyAccessorTests { assertThat(target.getArray()[1]).isEqualTo(2); } - @Test - @EnabledForTestGroups(PERFORMANCE) - public void setPrimitiveArrayPropertyLargeMatching() { - Assume.notLogging(LogFactory.getLog(AbstractPropertyAccessorTests.class)); - - PrimitiveArrayBean target = new PrimitiveArrayBean(); - AbstractPropertyAccessor accessor = createAccessor(target); - int[] input = new int[1024]; - StopWatch sw = new StopWatch(); - sw.start("array1"); - for (int i = 0; i < 1000; i++) { - accessor.setPropertyValue("array", input); - } - sw.stop(); - assertThat(target.getArray().length).isEqualTo(1024); - assertThat(target.getArray()[0]).isEqualTo(0); - long time1 = sw.getLastTaskTimeMillis(); - assertThat(sw.getLastTaskTimeMillis() < 100).as("Took too long").isTrue(); - - accessor.registerCustomEditor(String.class, new StringTrimmerEditor(false)); - sw.start("array2"); - for (int i = 0; i < 1000; i++) { - accessor.setPropertyValue("array", input); - } - sw.stop(); - assertThat(sw.getLastTaskTimeMillis() < 125).as("Took too long").isTrue(); - - accessor.registerCustomEditor(int.class, "array.somePath", new CustomNumberEditor(Integer.class, false)); - sw.start("array3"); - for (int i = 0; i < 1000; i++) { - accessor.setPropertyValue("array", input); - } - sw.stop(); - assertThat(sw.getLastTaskTimeMillis() < 100).as("Took too long").isTrue(); - - accessor.registerCustomEditor(int.class, "array[0].somePath", new CustomNumberEditor(Integer.class, false)); - sw.start("array3"); - for (int i = 0; i < 1000; i++) { - accessor.setPropertyValue("array", input); - } - sw.stop(); - assertThat(sw.getLastTaskTimeMillis() < 100).as("Took too long").isTrue(); - - accessor.registerCustomEditor(int.class, new CustomNumberEditor(Integer.class, false)); - sw.start("array4"); - for (int i = 0; i < 100; i++) { - accessor.setPropertyValue("array", input); - } - sw.stop(); - assertThat(target.getArray().length).isEqualTo(1024); - assertThat(target.getArray()[0]).isEqualTo(0); - assertThat(sw.getLastTaskTimeMillis() > time1).as("Took too long").isTrue(); - } - @Test public void setPrimitiveArrayPropertyLargeMatchingWithSpecificEditor() { PrimitiveArrayBean target = new PrimitiveArrayBean(); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/ConcurrentBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/ConcurrentBeanFactoryTests.java deleted file mode 100644 index 2e4857b4ccd..00000000000 --- a/spring-beans/src/test/java/org/springframework/beans/factory/ConcurrentBeanFactoryTests.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2002-2019 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 - * - * https://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.beans.factory; - -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; -import org.springframework.beans.propertyeditors.CustomDateEditor; -import org.springframework.core.testfixture.EnabledForTestGroups; -import org.springframework.core.testfixture.TestGroup; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.core.testfixture.io.ResourceTestUtils.qualifiedResource; - -/** - * @author Guillaume Poirier - * @author Juergen Hoeller - * @author Chris Beams - * @since 10.03.2004 - */ -@EnabledForTestGroups(TestGroup.PERFORMANCE) -public class ConcurrentBeanFactoryTests { - - private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd"); - - private static final Date DATE_1, DATE_2; - - static { - try { - DATE_1 = DATE_FORMAT.parse("2004/08/08"); - DATE_2 = DATE_FORMAT.parse("2000/02/02"); - } - catch (ParseException e) { - throw new RuntimeException(e); - } - } - - - private static final Log logger = LogFactory.getLog(ConcurrentBeanFactoryTests.class); - - private BeanFactory factory; - - private final Set set = Collections.synchronizedSet(new HashSet<>()); - - private Throwable ex; - - - @BeforeEach - public void setup() throws Exception { - DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); - new XmlBeanDefinitionReader(factory).loadBeanDefinitions( - qualifiedResource(ConcurrentBeanFactoryTests.class, "context.xml")); - - factory.addPropertyEditorRegistrar( - registry -> registry.registerCustomEditor(Date.class, - new CustomDateEditor((DateFormat) DATE_FORMAT.clone(), false))); - - this.factory = factory; - } - - - @Test - public void testSingleThread() { - for (int i = 0; i < 100; i++) { - performTest(); - } - } - - @Test - public void testConcurrent() { - for (int i = 0; i < 100; i++) { - TestRun run = new TestRun(); - run.setDaemon(true); - set.add(run); - } - for (Iterator it = new HashSet<>(set).iterator(); it.hasNext();) { - TestRun run = it.next(); - run.start(); - } - logger.info("Thread creation over, " + set.size() + " still active."); - synchronized (set) { - while (!set.isEmpty() && ex == null) { - try { - set.wait(); - } - catch (InterruptedException e) { - logger.info(e.toString()); - } - logger.info(set.size() + " threads still active."); - } - } - if (ex != null) { - throw new AssertionError("Unexpected exception", ex); - } - } - - private void performTest() { - ConcurrentBean b1 = (ConcurrentBean) factory.getBean("bean1"); - ConcurrentBean b2 = (ConcurrentBean) factory.getBean("bean2"); - - assertThat(b1.getDate()).isEqualTo(DATE_1); - assertThat(b2.getDate()).isEqualTo(DATE_2); - } - - - private class TestRun extends Thread { - - @Override - public void run() { - try { - for (int i = 0; i < 10000; i++) { - performTest(); - } - } - catch (Throwable e) { - ex = e; - } - finally { - synchronized (set) { - set.remove(this); - set.notifyAll(); - } - } - } - } - - - public static class ConcurrentBean { - - private Date date; - - public Date getDate() { - return date; - } - - public void setDate(Date date) { - this.date = date; - } - } - -} diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java index 56bb5511569..359bf93d2e1 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java @@ -46,7 +46,6 @@ import javax.security.auth.Subject; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; import org.springframework.beans.BeansException; import org.springframework.beans.MutablePropertyValues; @@ -80,7 +79,6 @@ import org.springframework.beans.propertyeditors.CustomNumberEditor; import org.springframework.beans.testfixture.beans.DependenciesBean; import org.springframework.beans.testfixture.beans.DerivedTestBean; import org.springframework.beans.testfixture.beans.ITestBean; -import org.springframework.beans.testfixture.beans.LifecycleBean; import org.springframework.beans.testfixture.beans.NestedTestBean; import org.springframework.beans.testfixture.beans.SideEffectBean; import org.springframework.beans.testfixture.beans.TestBean; @@ -93,13 +91,9 @@ import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; -import org.springframework.core.testfixture.Assume; -import org.springframework.core.testfixture.EnabledForTestGroups; -import org.springframework.core.testfixture.TestGroup; import org.springframework.core.testfixture.io.SerializationTestUtils; import org.springframework.core.testfixture.security.TestPrincipal; import org.springframework.lang.Nullable; -import org.springframework.util.StopWatch; import org.springframework.util.StringValueResolver; import static org.assertj.core.api.Assertions.assertThat; @@ -2352,163 +2346,6 @@ class DefaultListableBeanFactoryTests { assertThat(tb2.getBeanName()).isEqualTo("myBeanName"); } - @Test - @EnabledForTestGroups(TestGroup.PERFORMANCE) - void prototypeCreationIsFastEnough() { - Assume.notLogging(factoryLog); - RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class); - rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE); - lbf.registerBeanDefinition("test", rbd); - lbf.freezeConfiguration(); - StopWatch sw = new StopWatch(); - sw.start("prototype"); - for (int i = 0; i < 100000; i++) { - lbf.getBean("test"); - } - sw.stop(); - // System.out.println(sw.getTotalTimeMillis()); - assertThat(sw.getTotalTimeMillis() < 3000).as("Prototype creation took too long: " + sw.getTotalTimeMillis()).isTrue(); - } - - @Test - @EnabledForTestGroups(TestGroup.PERFORMANCE) - void prototypeCreationWithDependencyCheckIsFastEnough() { - Assume.notLogging(factoryLog); - RootBeanDefinition rbd = new RootBeanDefinition(LifecycleBean.class); - rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE); - rbd.setDependencyCheck(RootBeanDefinition.DEPENDENCY_CHECK_OBJECTS); - lbf.registerBeanDefinition("test", rbd); - lbf.addBeanPostProcessor(new LifecycleBean.PostProcessor()); - lbf.freezeConfiguration(); - StopWatch sw = new StopWatch(); - sw.start("prototype"); - for (int i = 0; i < 100000; i++) { - lbf.getBean("test"); - } - sw.stop(); - // System.out.println(sw.getTotalTimeMillis()); - assertThat(sw.getTotalTimeMillis() < 3000).as("Prototype creation took too long: " + sw.getTotalTimeMillis()).isTrue(); - } - - @Test - @EnabledForTestGroups(TestGroup.PERFORMANCE) - void prototypeCreationWithConstructorArgumentsIsFastEnough() { - Assume.notLogging(factoryLog); - RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class); - rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE); - rbd.getConstructorArgumentValues().addGenericArgumentValue("juergen"); - rbd.getConstructorArgumentValues().addGenericArgumentValue("99"); - lbf.registerBeanDefinition("test", rbd); - lbf.freezeConfiguration(); - StopWatch sw = new StopWatch(); - sw.start("prototype"); - for (int i = 0; i < 100000; i++) { - TestBean tb = (TestBean) lbf.getBean("test"); - assertThat(tb.getName()).isEqualTo("juergen"); - assertThat(tb.getAge()).isEqualTo(99); - } - sw.stop(); - assertThat(sw.getTotalTimeMillis() < 3000).as("Prototype creation took too long: " + sw.getTotalTimeMillis()).isTrue(); - } - - @Test - @EnabledForTestGroups(TestGroup.PERFORMANCE) - void prototypeCreationWithResolvedConstructorArgumentsIsFastEnough() { - Assume.notLogging(factoryLog); - RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class); - rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE); - rbd.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference("spouse")); - lbf.registerBeanDefinition("test", rbd); - lbf.registerBeanDefinition("spouse", new RootBeanDefinition(TestBean.class)); - lbf.freezeConfiguration(); - TestBean spouse = (TestBean) lbf.getBean("spouse"); - StopWatch sw = new StopWatch(); - sw.start("prototype"); - for (int i = 0; i < 100000; i++) { - TestBean tb = (TestBean) lbf.getBean("test"); - assertThat(tb.getSpouse()).isSameAs(spouse); - } - sw.stop(); - // System.out.println(sw.getTotalTimeMillis()); - assertThat(sw.getTotalTimeMillis() < 4000).as("Prototype creation took too long: " + sw.getTotalTimeMillis()).isTrue(); - } - - @Test - @EnabledForTestGroups(TestGroup.PERFORMANCE) - void prototypeCreationWithPropertiesIsFastEnough() { - Assume.notLogging(factoryLog); - RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class); - rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE); - rbd.getPropertyValues().add("name", "juergen"); - rbd.getPropertyValues().add("age", "99"); - lbf.registerBeanDefinition("test", rbd); - lbf.freezeConfiguration(); - StopWatch sw = new StopWatch(); - sw.start("prototype"); - for (int i = 0; i < 100000; i++) { - TestBean tb = (TestBean) lbf.getBean("test"); - assertThat(tb.getName()).isEqualTo("juergen"); - assertThat(tb.getAge()).isEqualTo(99); - } - sw.stop(); - // System.out.println(sw.getTotalTimeMillis()); - assertThat(sw.getTotalTimeMillis() < 4000).as("Prototype creation took too long: " + sw.getTotalTimeMillis()).isTrue(); - } - - @Test - @EnabledForTestGroups(TestGroup.PERFORMANCE) - void prototypeCreationWithResolvedPropertiesIsFastEnough() { - Assume.notLogging(factoryLog); - RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class); - rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE); - rbd.getPropertyValues().add("spouse", new RuntimeBeanReference("spouse")); - lbf.registerBeanDefinition("test", rbd); - lbf.registerBeanDefinition("spouse", new RootBeanDefinition(TestBean.class)); - lbf.freezeConfiguration(); - TestBean spouse = (TestBean) lbf.getBean("spouse"); - StopWatch sw = new StopWatch(); - sw.start("prototype"); - for (int i = 0; i < 100000; i++) { - TestBean tb = (TestBean) lbf.getBean("test"); - assertThat(tb.getSpouse()).isSameAs(spouse); - } - sw.stop(); - // System.out.println(sw.getTotalTimeMillis()); - assertThat(sw.getTotalTimeMillis() < 4000).as("Prototype creation took too long: " + sw.getTotalTimeMillis()).isTrue(); - } - - @Test - @EnabledForTestGroups(TestGroup.PERFORMANCE) - void singletonLookupByNameIsFastEnough() { - Assume.notLogging(factoryLog); - lbf.registerBeanDefinition("test", new RootBeanDefinition(TestBean.class)); - lbf.freezeConfiguration(); - StopWatch sw = new StopWatch(); - sw.start("singleton"); - for (int i = 0; i < 1000000; i++) { - lbf.getBean("test"); - } - sw.stop(); - // System.out.println(sw.getTotalTimeMillis()); - assertThat(sw.getTotalTimeMillis() < 1000).as("Singleton lookup took too long: " + sw.getTotalTimeMillis()).isTrue(); - } - - @Test - @EnabledForTestGroups(TestGroup.PERFORMANCE) - void singletonLookupByTypeIsFastEnough() { - Assume.notLogging(factoryLog); - lbf.registerBeanDefinition("test", new RootBeanDefinition(TestBean.class)); - lbf.freezeConfiguration(); - StopWatch sw = new StopWatch(); - sw.start("singleton"); - for (int i = 0; i < 1000000; i++) { - lbf.getBean(TestBean.class); - } - sw.stop(); - // System.out.println(sw.getTotalTimeMillis()); - assertThat(sw.getTotalTimeMillis() < 1000).as("Singleton lookup took too long: " + sw.getTotalTimeMillis()).isTrue(); - } - @Test void beanPostProcessorWithWrappedObjectAndDisposableBean() { RootBeanDefinition bd = new RootBeanDefinition(BeanWithDisposableBean.class); @@ -2844,55 +2681,6 @@ class DefaultListableBeanFactoryTests { assertThat(holder.getNonPublicEnum()).isEqualTo(NonPublicEnum.VALUE_1); } - /** - * Test that by-type bean lookup caching is working effectively by searching for a - * bean of type B 10K times within a container having 1K additional beans of type A. - * Prior to by-type caching, each bean lookup would traverse the entire container - * (all 1001 beans), performing expensive assignability checks, etc. Now these - * operations are necessary only once, providing a dramatic performance improvement. - * On load-free modern hardware (e.g. an 8-core MPB), this method should complete well - * under the 1000 ms timeout, usually ~= 300ms. With caching removed and on the same - * hardware the method will take ~13000 ms. See SPR-6870. - */ - @Test - @Timeout(1) - @EnabledForTestGroups(TestGroup.PERFORMANCE) - void byTypeLookupIsFastEnough() { - for (int i = 0; i < 1000; i++) { - lbf.registerBeanDefinition("a" + i, new RootBeanDefinition(A.class)); - } - lbf.registerBeanDefinition("b", new RootBeanDefinition(B.class)); - - lbf.freezeConfiguration(); - - for (int i = 0; i < 10000; i++) { - lbf.getBean(B.class); - } - } - - @Test - @Timeout(1) - @EnabledForTestGroups(TestGroup.PERFORMANCE) - void registrationOfManyBeanDefinitionsIsFastEnough() { - lbf.registerBeanDefinition("b", new RootBeanDefinition(B.class)); - // lbf.getBean("b"); - - for (int i = 0; i < 100000; i++) { - lbf.registerBeanDefinition("a" + i, new RootBeanDefinition(A.class)); - } - } - - @Test - @Timeout(1) - @EnabledForTestGroups(TestGroup.PERFORMANCE) - void registrationOfManySingletonsIsFastEnough() { - lbf.registerBeanDefinition("b", new RootBeanDefinition(B.class)); - // lbf.getBean("b"); - - for (int i = 0; i < 100000; i++) { - lbf.registerSingleton("a" + i, new A()); - } - } @SuppressWarnings("deprecation") private int registerBeanDefinitions(Properties p) { @@ -2905,11 +2693,6 @@ class DefaultListableBeanFactoryTests { } - static class A { } - - static class B { } - - public static class NoDependencies { private NoDependencies() { diff --git a/spring-context/src/jmh/java/org/springframework/context/annotation/AnnotationProcessorBenchmark.java b/spring-context/src/jmh/java/org/springframework/context/annotation/AnnotationProcessorBenchmark.java new file mode 100644 index 00000000000..c00cf0ddfae --- /dev/null +++ b/spring-context/src/jmh/java/org/springframework/context/annotation/AnnotationProcessorBenchmark.java @@ -0,0 +1,105 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.context.annotation; + +import javax.annotation.Resource; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.testfixture.beans.ITestBean; +import org.springframework.beans.testfixture.beans.TestBean; +import org.springframework.context.support.GenericApplicationContext; + +/** + * Benchmark for bean annotation processing with various annotations. + * @author Brian Clozel + */ +@BenchmarkMode(Mode.Throughput) +public class AnnotationProcessorBenchmark { + + @State(Scope.Benchmark) + public static class BenchmarkState { + + public GenericApplicationContext context; + + @Param({"ResourceAnnotatedTestBean", "AutowiredAnnotatedTestBean"}) + public String testBeanClass; + + @Param({"true", "false"}) + public boolean overridden; + + @Setup + public void setup() { + RootBeanDefinition rbd; + this.context = new GenericApplicationContext(); + AnnotationConfigUtils.registerAnnotationConfigProcessors(this.context); + this.context.refresh(); + if (this.testBeanClass.equals("ResourceAnnotatedTestBean")) { + rbd = new RootBeanDefinition(ResourceAnnotatedTestBean.class); + } + else { + rbd = new RootBeanDefinition(AutowiredAnnotatedTestBean.class); + } + rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE); + if (this.overridden) { + rbd.getPropertyValues().add("spouse", new RuntimeBeanReference("spouse")); + } + this.context.registerBeanDefinition("test", rbd); + this.context.registerBeanDefinition("spouse", new RootBeanDefinition(TestBean.class)); + } + } + + @Benchmark + public ITestBean prototypeCreation(BenchmarkState state) { + TestBean tb = state.context.getBean("test", TestBean.class); + return tb.getSpouse(); + } + + + private static class ResourceAnnotatedTestBean extends org.springframework.beans.testfixture.beans.TestBean { + + @Override + @Resource + @SuppressWarnings("deprecation") + @org.springframework.beans.factory.annotation.Required + public void setSpouse(ITestBean spouse) { + super.setSpouse(spouse); + } + } + + private static class AutowiredAnnotatedTestBean extends TestBean { + + @Override + @Autowired + @SuppressWarnings("deprecation") + @org.springframework.beans.factory.annotation.Required + public void setSpouse(ITestBean spouse) { + super.setSpouse(spouse); + } + } + +} diff --git a/spring-context/src/jmh/java/org/springframework/context/expression/ApplicationContextExpressionBenchmark.java b/spring-context/src/jmh/java/org/springframework/context/expression/ApplicationContextExpressionBenchmark.java new file mode 100644 index 00000000000..51a568ca30a --- /dev/null +++ b/spring-context/src/jmh/java/org/springframework/context/expression/ApplicationContextExpressionBenchmark.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.context.expression; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.infra.Blackhole; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.testfixture.beans.TestBean; +import org.springframework.context.support.GenericApplicationContext; + +/** + * Benchmark for application context expressions resolution during prototype bean creation. + * @author Brian Clozel + */ +@BenchmarkMode(Mode.Throughput) +public class ApplicationContextExpressionBenchmark { + + @State(Scope.Benchmark) + public static class BenchmarkState { + + public GenericApplicationContext context; + + @Setup + public void setup() { + System.getProperties().put("name", "juergen"); + System.getProperties().put("country", "UK"); + this.context = new GenericApplicationContext(); + RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class); + rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE); + rbd.getConstructorArgumentValues().addGenericArgumentValue("#{systemProperties.name}"); + rbd.getPropertyValues().add("country", "#{systemProperties.country}"); + this.context.registerBeanDefinition("test", rbd); + this.context.refresh(); + } + + @TearDown + public void teardown() { + System.getProperties().remove("country"); + System.getProperties().remove("name"); + } + } + + @Benchmark + public void prototypeCreationWithSystemProperties(BenchmarkState state, Blackhole bh) { + TestBean tb = (TestBean) state.context.getBean("test"); + bh.consume(tb.getName()); + bh.consume(tb.getCountry()); + } +} diff --git a/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java b/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java index 152c9a128d3..0b6d3394ffb 100644 --- a/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java +++ b/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java @@ -42,10 +42,7 @@ import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.config.MethodInvokingFactoryBean; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; -import org.springframework.beans.testfixture.beans.INestedTestBean; import org.springframework.beans.testfixture.beans.ITestBean; -import org.springframework.beans.testfixture.beans.NestedTestBean; import org.springframework.beans.testfixture.beans.TestBean; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -53,10 +50,6 @@ import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.NestedRuntimeException; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.testfixture.Assume; -import org.springframework.core.testfixture.EnabledForTestGroups; -import org.springframework.core.testfixture.TestGroup; import org.springframework.lang.Nullable; import org.springframework.util.StopWatch; @@ -112,72 +105,6 @@ public class AspectJAutoProxyCreatorTests { doTestAspectsAndAdvisorAreApplied(ac, shouldBeWeaved); } - @Test - @EnabledForTestGroups(TestGroup.PERFORMANCE) - public void testAspectsAndAdvisorAppliedToPrototypeIsFastEnough() { - Assume.notLogging(factoryLog); - - ClassPathXmlApplicationContext ac = newContext("aspectsPlusAdvisor.xml"); - - StopWatch sw = new StopWatch(); - sw.start("Prototype Creation"); - for (int i = 0; i < 10000; i++) { - ITestBean shouldBeWeaved = (ITestBean) ac.getBean("adrian2"); - if (i < 10) { - doTestAspectsAndAdvisorAreApplied(ac, shouldBeWeaved); - } - } - sw.stop(); - - // What's a reasonable expectation for _any_ server or developer machine load? - // 9 seconds? - assertStopWatchTimeLimit(sw, 9000); - } - - @Test - @EnabledForTestGroups(TestGroup.PERFORMANCE) - public void testAspectsAndAdvisorNotAppliedToPrototypeIsFastEnough() { - Assume.notLogging(factoryLog); - - ClassPathXmlApplicationContext ac = newContext("aspectsPlusAdvisor.xml"); - - StopWatch sw = new StopWatch(); - sw.start("Prototype Creation"); - for (int i = 0; i < 100000; i++) { - INestedTestBean shouldNotBeWeaved = (INestedTestBean) ac.getBean("i21"); - if (i < 10) { - assertThat(AopUtils.isAopProxy(shouldNotBeWeaved)).isFalse(); - } - } - sw.stop(); - - // What's a reasonable expectation for _any_ server or developer machine load? - // 3 seconds? - assertStopWatchTimeLimit(sw, 6000); - } - - @Test - @EnabledForTestGroups(TestGroup.PERFORMANCE) - public void testAspectsAndAdvisorNotAppliedToManySingletonsIsFastEnough() { - Assume.notLogging(factoryLog); - - GenericApplicationContext ac = new GenericApplicationContext(); - - new XmlBeanDefinitionReader(ac).loadBeanDefinitions(new ClassPathResource(qName("aspectsPlusAdvisor.xml"), - getClass())); - for (int i = 0; i < 10000; i++) { - ac.registerBeanDefinition("singleton" + i, new RootBeanDefinition(NestedTestBean.class)); - } - StopWatch sw = new StopWatch(); - sw.start("Singleton Creation"); - ac.refresh(); - sw.stop(); - - // What's a reasonable expectation for _any_ server or developer machine load? - // 8 seconds? - assertStopWatchTimeLimit(sw, 8000); - } - @Test public void testAspectsAndAdvisorAreAppliedEvenIfComingFromParentFactory() { ClassPathXmlApplicationContext ac = newContext("aspectsPlusAdvisor.xml"); diff --git a/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java b/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java index e076780e223..db27a571fc4 100644 --- a/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java +++ b/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java @@ -69,12 +69,9 @@ import org.springframework.beans.testfixture.beans.ITestBean; import org.springframework.beans.testfixture.beans.Person; import org.springframework.beans.testfixture.beans.SerializablePerson; import org.springframework.beans.testfixture.beans.TestBean; -import org.springframework.core.testfixture.EnabledForTestGroups; -import org.springframework.core.testfixture.TestGroup; import org.springframework.core.testfixture.TimeStamped; import org.springframework.core.testfixture.io.SerializationTestUtils; import org.springframework.lang.Nullable; -import org.springframework.util.StopWatch; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -157,36 +154,6 @@ public abstract class AbstractAopProxyTests { assertThat(tb.getName()).isEqualTo(name); } - /** - * This is primarily a test for the efficiency of our - * usage of CGLIB. If we create too many classes with - * CGLIB this will be slow or will run out of memory. - */ - @Test - @EnabledForTestGroups(TestGroup.PERFORMANCE) - public void testManyProxies() { - int howMany = 10000; - StopWatch sw = new StopWatch(); - sw.start("Create " + howMany + " proxies"); - testManyProxies(howMany); - sw.stop(); - assertThat(sw.getTotalTimeMillis() < 5000).as("Proxy creation was too slow").isTrue(); - } - - private void testManyProxies(int howMany) { - int age1 = 33; - TestBean target1 = new TestBean(); - target1.setAge(age1); - ProxyFactory pf1 = new ProxyFactory(target1); - pf1.addAdvice(new NopInterceptor()); - pf1.addAdvice(new NopInterceptor()); - ITestBean[] proxies = new ITestBean[howMany]; - for (int i = 0; i < howMany; i++) { - proxies[i] = (ITestBean) createAopProxy(pf1).getProxy(); - assertThat(proxies[i].getAge()).isEqualTo(age1); - } - } - @Test public void testSerializationAdviceAndTargetNotSerializable() throws Exception { TestBean tb = new TestBean(); diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationProcessorPerformanceTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationProcessorPerformanceTests.java deleted file mode 100644 index 4b564506fa8..00000000000 --- a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationProcessorPerformanceTests.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2002-2019 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 - * - * https://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.context.annotation; - -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.annotation.Resource; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.awaitility.Awaitility; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.beans.testfixture.beans.ITestBean; -import org.springframework.beans.testfixture.beans.TestBean; -import org.springframework.context.support.GenericApplicationContext; -import org.springframework.core.testfixture.Assume; -import org.springframework.core.testfixture.EnabledForTestGroups; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.core.testfixture.TestGroup.PERFORMANCE; - -/** - * @author Juergen Hoeller - * @author Chris Beams - * @author Sam Brannen - * @since 2.5 - */ -@EnabledForTestGroups(PERFORMANCE) -public class AnnotationProcessorPerformanceTests { - - private static final Log factoryLog = LogFactory.getLog(DefaultListableBeanFactory.class); - - - @BeforeAll - public static void commonAssumptions() { - Assume.notLogging(factoryLog); - } - - @Test - public void prototypeCreationWithResourcePropertiesIsFastEnough() { - GenericApplicationContext ctx = createContext(); - - RootBeanDefinition rbd = new RootBeanDefinition(ResourceAnnotatedTestBean.class); - rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE); - ctx.registerBeanDefinition("test", rbd); - ctx.registerBeanDefinition("spouse", new RootBeanDefinition(TestBean.class)); - - assertFastEnough(ctx); - } - - @Test - public void prototypeCreationWithOverriddenResourcePropertiesIsFastEnough() { - GenericApplicationContext ctx = createContext(); - - RootBeanDefinition rbd = new RootBeanDefinition(ResourceAnnotatedTestBean.class); - rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE); - rbd.getPropertyValues().add("spouse", new RuntimeBeanReference("spouse")); - ctx.registerBeanDefinition("test", rbd); - ctx.registerBeanDefinition("spouse", new RootBeanDefinition(TestBean.class)); - - assertFastEnough(ctx); - } - - @Test - public void prototypeCreationWithAutowiredPropertiesIsFastEnough() { - GenericApplicationContext ctx = createContext(); - - RootBeanDefinition rbd = new RootBeanDefinition(AutowiredAnnotatedTestBean.class); - rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE); - ctx.registerBeanDefinition("test", rbd); - ctx.registerBeanDefinition("spouse", new RootBeanDefinition(TestBean.class)); - - assertFastEnough(ctx); - } - - @Test - public void prototypeCreationWithOverriddenAutowiredPropertiesIsFastEnough() { - GenericApplicationContext ctx = createContext(); - - RootBeanDefinition rbd = new RootBeanDefinition(AutowiredAnnotatedTestBean.class); - rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE); - rbd.getPropertyValues().add("spouse", new RuntimeBeanReference("spouse")); - ctx.registerBeanDefinition("test", rbd); - ctx.registerBeanDefinition("spouse", new RootBeanDefinition(TestBean.class)); - - assertFastEnough(ctx); - } - - private GenericApplicationContext createContext() { - GenericApplicationContext ctx = new GenericApplicationContext(); - AnnotationConfigUtils.registerAnnotationConfigProcessors(ctx); - ctx.refresh(); - return ctx; - } - - private void assertFastEnough(GenericApplicationContext ctx) { - AtomicBoolean done = new AtomicBoolean(); - TestBean spouse = ctx.getBean("spouse", TestBean.class); - Executors.newSingleThreadExecutor().submit(() -> { - for (int i = 0; i < 100_000; i++) { - TestBean tb = ctx.getBean("test", TestBean.class); - assertThat(tb.getSpouse()).isSameAs(spouse); - } - done.set(true); - }); - - // "fast enough" is of course relative, but we're using 6 seconds with the hope - // that these tests typically pass on the CI server. - Awaitility.await() - .atMost(6, TimeUnit.SECONDS) - .pollInterval(100, TimeUnit.MILLISECONDS) - .untilTrue(done); - } - - - private static class ResourceAnnotatedTestBean extends TestBean { - - @Override - @Resource - @SuppressWarnings("deprecation") - @org.springframework.beans.factory.annotation.Required - public void setSpouse(ITestBean spouse) { - super.setSpouse(spouse); - } - } - - private static class AutowiredAnnotatedTestBean extends TestBean { - - @Override - @Autowired - @SuppressWarnings("deprecation") - @org.springframework.beans.factory.annotation.Required - public void setSpouse(ITestBean spouse) { - super.setSpouse(spouse); - } - } - -} diff --git a/spring-context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java b/spring-context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java index 905cf736726..e3c9921a504 100644 --- a/spring-context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java +++ b/spring-context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java @@ -52,14 +52,10 @@ import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.support.EncodedResource; -import org.springframework.core.testfixture.Assume; -import org.springframework.core.testfixture.EnabledForTestGroups; import org.springframework.core.testfixture.io.SerializationTestUtils; import org.springframework.util.FileCopyUtils; -import org.springframework.util.StopWatch; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.core.testfixture.TestGroup.PERFORMANCE; /** * @author Juergen Hoeller @@ -247,37 +243,6 @@ class ApplicationContextExpressionTests { } } - @Test - @EnabledForTestGroups(PERFORMANCE) - void prototypeCreationIsFastEnough() { - Assume.notLogging(factoryLog); - GenericApplicationContext ac = new GenericApplicationContext(); - RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class); - rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE); - rbd.getConstructorArgumentValues().addGenericArgumentValue("#{systemProperties.name}"); - rbd.getPropertyValues().add("country", "#{systemProperties.country}"); - ac.registerBeanDefinition("test", rbd); - ac.refresh(); - StopWatch sw = new StopWatch(); - sw.start("prototype"); - System.getProperties().put("name", "juergen"); - System.getProperties().put("country", "UK"); - try { - for (int i = 0; i < 100000; i++) { - TestBean tb = (TestBean) ac.getBean("test"); - assertThat(tb.getName()).isEqualTo("juergen"); - assertThat(tb.getCountry()).isEqualTo("UK"); - } - sw.stop(); - } - finally { - System.getProperties().remove("country"); - System.getProperties().remove("name"); - } - assertThat(sw.getTotalTimeMillis() < 6000).as("Prototype creation took too long: " + sw.getTotalTimeMillis()).isTrue(); - ac.close(); - } - @Test void systemPropertiesSecurityManager() { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(); diff --git a/spring-core/src/jmh/java/org/springframework/util/ReflectionUtilsUniqueDeclaredMethodsBenchmark.java b/spring-core/src/jmh/java/org/springframework/util/ReflectionUtilsUniqueDeclaredMethodsBenchmark.java new file mode 100644 index 00000000000..6f401d1d0eb --- /dev/null +++ b/spring-core/src/jmh/java/org/springframework/util/ReflectionUtilsUniqueDeclaredMethodsBenchmark.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.util; + +import java.lang.reflect.Method; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; + +/** + * Benchmark for finding declared methods on a class using {@link ReflectionUtils}. + * This benchmark is using {@link Mode#SingleShotTime} since we want to benchmark + * cold JVM iterations. + * @author Brian Clozel + */ +@BenchmarkMode(Mode.SingleShotTime) +public class ReflectionUtilsUniqueDeclaredMethodsBenchmark { + + @Benchmark + public Method[] findMethods() { + return ReflectionUtils.getUniqueDeclaredMethods(C.class); + } + + @SuppressWarnings("unused") + class C { + void m00() { } void m01() { } void m02() { } void m03() { } void m04() { } + void m05() { } void m06() { } void m07() { } void m08() { } void m09() { } + void m10() { } void m11() { } void m12() { } void m13() { } void m14() { } + void m15() { } void m16() { } void m17() { } void m18() { } void m19() { } + void m20() { } void m21() { } void m22() { } void m23() { } void m24() { } + void m25() { } void m26() { } void m27() { } void m28() { } void m29() { } + void m30() { } void m31() { } void m32() { } void m33() { } void m34() { } + void m35() { } void m36() { } void m37() { } void m38() { } void m39() { } + void m40() { } void m41() { } void m42() { } void m43() { } void m44() { } + void m45() { } void m46() { } void m47() { } void m48() { } void m49() { } + void m50() { } void m51() { } void m52() { } void m53() { } void m54() { } + void m55() { } void m56() { } void m57() { } void m58() { } void m59() { } + void m60() { } void m61() { } void m62() { } void m63() { } void m64() { } + void m65() { } void m66() { } void m67() { } void m68() { } void m69() { } + void m70() { } void m71() { } void m72() { } void m73() { } void m74() { } + void m75() { } void m76() { } void m77() { } void m78() { } void m79() { } + void m80() { } void m81() { } void m82() { } void m83() { } void m84() { } + void m85() { } void m86() { } void m87() { } void m88() { } void m89() { } + void m90() { } void m91() { } void m92() { } void m93() { } void m94() { } + void m95() { } void m96() { } void m97() { } void m98() { } void m99() { } + } + +} diff --git a/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java b/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java index 5d9d3867769..53859222465 100644 --- a/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java @@ -27,12 +27,10 @@ import java.util.List; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.core.testfixture.EnabledForTestGroups; import org.springframework.tests.sample.objects.TestObject; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.springframework.core.testfixture.TestGroup.PERFORMANCE; /** * @author Rob Harrop @@ -325,42 +323,6 @@ class ReflectionUtilsTests { assertThat(ObjectUtils.containsElement(methods, Parent.class.getMethod("m1"))).isFalse(); } - @Test - @EnabledForTestGroups(PERFORMANCE) - void getUniqueDeclaredMethods_isFastEnough() { - @SuppressWarnings("unused") - class C { - void m00() { } void m01() { } void m02() { } void m03() { } void m04() { } - void m05() { } void m06() { } void m07() { } void m08() { } void m09() { } - void m10() { } void m11() { } void m12() { } void m13() { } void m14() { } - void m15() { } void m16() { } void m17() { } void m18() { } void m19() { } - void m20() { } void m21() { } void m22() { } void m23() { } void m24() { } - void m25() { } void m26() { } void m27() { } void m28() { } void m29() { } - void m30() { } void m31() { } void m32() { } void m33() { } void m34() { } - void m35() { } void m36() { } void m37() { } void m38() { } void m39() { } - void m40() { } void m41() { } void m42() { } void m43() { } void m44() { } - void m45() { } void m46() { } void m47() { } void m48() { } void m49() { } - void m50() { } void m51() { } void m52() { } void m53() { } void m54() { } - void m55() { } void m56() { } void m57() { } void m58() { } void m59() { } - void m60() { } void m61() { } void m62() { } void m63() { } void m64() { } - void m65() { } void m66() { } void m67() { } void m68() { } void m69() { } - void m70() { } void m71() { } void m72() { } void m73() { } void m74() { } - void m75() { } void m76() { } void m77() { } void m78() { } void m79() { } - void m80() { } void m81() { } void m82() { } void m83() { } void m84() { } - void m85() { } void m86() { } void m87() { } void m88() { } void m89() { } - void m90() { } void m91() { } void m92() { } void m93() { } void m94() { } - void m95() { } void m96() { } void m97() { } void m98() { } void m99() { } - } - - StopWatch sw = new StopWatch(); - sw.start(); - Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(C.class); - sw.stop(); - long totalMs = sw.getTotalTimeMillis(); - assertThat(methods.length).isGreaterThan(100); - assertThat(totalMs).isLessThan(10L); - } - @Test void getDeclaredMethodsReturnsCopy() { Method[] m1 = ReflectionUtils.getDeclaredMethods(A.class); diff --git a/spring-expression/src/jmh/java/org/springframework/expression/spel/SpelBenchmark.java b/spring-expression/src/jmh/java/org/springframework/expression/spel/SpelBenchmark.java new file mode 100644 index 00000000000..1f12a1739bf --- /dev/null +++ b/spring-expression/src/jmh/java/org/springframework/expression/spel/SpelBenchmark.java @@ -0,0 +1,106 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.expression.spel; + +import java.util.HashMap; +import java.util.Map; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; + +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +/** + * Benchmarks for parsing and executing SpEL expressions. + * @author Brian Clozel + */ +@BenchmarkMode(Mode.Throughput) +public class SpelBenchmark { + + @State(Scope.Benchmark) + public static class BenchmarkData { + + public ExpressionParser parser = new SpelExpressionParser(); + + public EvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext(); + + } + + @Benchmark + public Object propertyAccessParseAndExecution(BenchmarkData data) { + Expression expr = data.parser.parseExpression("placeOfBirth.city"); + return expr.getValue(data.eContext); + } + + @Benchmark + public Object methodAccessParseAndExecution(BenchmarkData data) { + Expression expr = data.parser.parseExpression("getPlaceOfBirth().getCity()"); + return expr.getValue(data.eContext); + } + + @State(Scope.Benchmark) + public static class CachingBenchmarkData extends BenchmarkData { + + public Expression propertyExpression; + + public Expression methodExpression; + + public CachingBenchmarkData() { + this.propertyExpression = this.parser.parseExpression("placeOfBirth.city"); + this.methodExpression = this.parser.parseExpression("getPlaceOfBirth().getCity()"); + } + } + + @Benchmark + public Object cachingPropertyAccessParseAndExecution(CachingBenchmarkData data) { + return data.propertyExpression.getValue(data.eContext); + } + + @Benchmark + public Object cachingMethodAccessParseAndExecution(CachingBenchmarkData data) { + return data.methodExpression.getValue(data.eContext); + } + + @State(Scope.Benchmark) + public static class ValueBenchmarkData { + + public EvaluationContext context; + + public Expression expression; + + public ValueBenchmarkData() { + Map map = new HashMap<>(); + map.put("key", "value"); + this.context = new StandardEvaluationContext(map); + ExpressionParser spelExpressionParser = new SpelExpressionParser(); + this.expression = spelExpressionParser.parseExpression("#root['key']"); + } + } + + @Benchmark + public Object getValueFromMap(ValueBenchmarkData data) { + return data.expression.getValue(data.context); + } + +} diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java index 40a6fa7cb25..832a6365d90 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -21,7 +21,6 @@ import java.util.Map; import org.junit.jupiter.api.Test; -import org.springframework.core.testfixture.EnabledForTestGroups; import org.springframework.expression.AccessException; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; @@ -30,10 +29,8 @@ import org.springframework.expression.PropertyAccessor; import org.springframework.expression.TypedValue; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.util.StopWatch; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.core.testfixture.TestGroup.PERFORMANCE; /** * Testing variations on map access. @@ -75,8 +72,8 @@ public class MapAccessTests extends AbstractExpressionTests { } @Test - public void testGetValue(){ - Map props1 = new HashMap<>(); + public void testGetValue() { + Map props1 = new HashMap<>(); props1.put("key1", "value1"); props1.put("key2", "value2"); props1.put("key3", "value3"); @@ -98,25 +95,6 @@ public class MapAccessTests extends AbstractExpressionTests { assertThat(expr.getValue(map)).isEqualTo("value"); } - @Test - @EnabledForTestGroups(PERFORMANCE) - public void testGetValuePerformance() throws Exception { - Map map = new HashMap<>(); - map.put("key", "value"); - EvaluationContext context = new StandardEvaluationContext(map); - - ExpressionParser spelExpressionParser = new SpelExpressionParser(); - Expression expr = spelExpressionParser.parseExpression("#root['key']"); - - StopWatch s = new StopWatch(); - s.start(); - for (int i = 0; i < 10000; i++) { - expr.getValue(context); - } - s.stop(); - assertThat(s.getTotalTimeMillis()).isLessThan(200L); - } - public static class TestBean { @@ -166,11 +144,11 @@ public class MapAccessTests extends AbstractExpressionTests { this.priority = priority; } - public Map getProperties() { + public Map getProperties() { return properties; } - public void setProperties(Map properties) { + public void setProperties(Map properties) { this.properties = properties; } } @@ -185,7 +163,7 @@ public class MapAccessTests extends AbstractExpressionTests { @Override public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { - return new TypedValue(((Map) target).get(name)); + return new TypedValue(((Map) target).get(name)); } @Override @@ -196,7 +174,7 @@ public class MapAccessTests extends AbstractExpressionTests { @Override @SuppressWarnings("unchecked") public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException { - ((Map) target).put(name, newValue); + ((Map) target).put(name, newValue); } @Override diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/PerformanceTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/PerformanceTests.java deleted file mode 100644 index 164e849f9f3..00000000000 --- a/spring-expression/src/test/java/org/springframework/expression/spel/PerformanceTests.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2002-2019 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 - * - * https://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.expression.spel; - -import org.junit.jupiter.api.Test; - -import org.springframework.core.testfixture.EnabledForTestGroups; -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.spel.standard.SpelExpressionParser; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.springframework.core.testfixture.TestGroup.PERFORMANCE; - -///CLOVER:OFF - -/** - * Tests the evaluation of real expressions in a real context. - * - * @author Andy Clement - */ -@EnabledForTestGroups(PERFORMANCE) -public class PerformanceTests { - - public static final int ITERATIONS = 10000; - public static final boolean report = true; - - private static ExpressionParser parser = new SpelExpressionParser(); - private static EvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext(); - - private static final boolean DEBUG = false; - - @Test - public void testPerformanceOfPropertyAccess() throws Exception { - long starttime = 0; - long endtime = 0; - - // warmup - for (int i = 0; i < ITERATIONS; i++) { - Expression expr = parser.parseExpression("placeOfBirth.city"); - assertThat(expr).isNotNull(); - expr.getValue(eContext); - } - - starttime = System.currentTimeMillis(); - for (int i = 0; i < ITERATIONS; i++) { - Expression expr = parser.parseExpression("placeOfBirth.city"); - assertThat(expr).isNotNull(); - expr.getValue(eContext); - } - endtime = System.currentTimeMillis(); - long freshParseTime = endtime - starttime; - if (DEBUG) { - System.out.println("PropertyAccess: Time for parsing and evaluation x 10000: "+freshParseTime+"ms"); - } - - Expression expr = parser.parseExpression("placeOfBirth.city"); - assertThat(expr).isNotNull(); - starttime = System.currentTimeMillis(); - for (int i = 0; i < ITERATIONS; i++) { - expr.getValue(eContext); - } - endtime = System.currentTimeMillis(); - long reuseTime = endtime - starttime; - if (DEBUG) { - System.out.println("PropertyAccess: Time for just evaluation x 10000: "+reuseTime+"ms"); - } - if (reuseTime > freshParseTime) { - System.out.println("Fresh parse every time, ITERATIONS iterations = " + freshParseTime + "ms"); - System.out.println("Reuse SpelExpression, ITERATIONS iterations = " + reuseTime + "ms"); - fail("Should have been quicker to reuse!"); - } - } - - @Test - public void testPerformanceOfMethodAccess() throws Exception { - long starttime = 0; - long endtime = 0; - - // warmup - for (int i = 0; i < ITERATIONS; i++) { - Expression expr = parser.parseExpression("getPlaceOfBirth().getCity()"); - assertThat(expr).isNotNull(); - expr.getValue(eContext); - } - - starttime = System.currentTimeMillis(); - for (int i = 0; i < ITERATIONS; i++) { - Expression expr = parser.parseExpression("getPlaceOfBirth().getCity()"); - assertThat(expr).isNotNull(); - expr.getValue(eContext); - } - endtime = System.currentTimeMillis(); - long freshParseTime = endtime - starttime; - if (DEBUG) { - System.out.println("MethodExpression: Time for parsing and evaluation x 10000: "+freshParseTime+"ms"); - } - - Expression expr = parser.parseExpression("getPlaceOfBirth().getCity()"); - assertThat(expr).isNotNull(); - starttime = System.currentTimeMillis(); - for (int i = 0; i < ITERATIONS; i++) { - expr.getValue(eContext); - } - endtime = System.currentTimeMillis(); - long reuseTime = endtime - starttime; - if (DEBUG) { - System.out.println("MethodExpression: Time for just evaluation x 10000: "+reuseTime+"ms"); - } - - if (reuseTime > freshParseTime) { - System.out.println("Fresh parse every time, ITERATIONS iterations = " + freshParseTime + "ms"); - System.out.println("Reuse SpelExpression, ITERATIONS iterations = " + reuseTime + "ms"); - fail("Should have been quicker to reuse!"); - } - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java index bd74b3d6f08..bfdf86a2098 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java @@ -30,7 +30,6 @@ import com.gargoylesoftware.htmlunit.util.Cookie; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Configuration; -import org.springframework.core.testfixture.TestGroup; import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -79,10 +78,6 @@ class MockMvcWebClientBuilderTests { WebClient client = MockMvcWebClientBuilder.mockMvcSetup(this.mockMvc).build(); assertMockMvcUsed(client, "http://localhost/test"); - - if (TestGroup.PERFORMANCE.isActive()) { - assertMockMvcNotUsed(client, "https://spring.io/"); - } } @Test @@ -91,10 +86,6 @@ class MockMvcWebClientBuilderTests { WebClient client = MockMvcWebClientBuilder.mockMvcSetup(this.mockMvc).withDelegate(otherClient).build(); assertMockMvcUsed(client, "http://localhost/test"); - - if (TestGroup.PERFORMANCE.isActive()) { - assertMockMvcNotUsed(client, "https://spring.io/"); - } } @Test // SPR-14066 diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java index 8c8146d0991..904a0f8dead 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java @@ -25,7 +25,6 @@ import org.junit.jupiter.api.Test; import org.openqa.selenium.htmlunit.HtmlUnitDriver; import org.springframework.context.annotation.Configuration; -import org.springframework.core.testfixture.TestGroup; import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -75,10 +74,6 @@ class MockMvcHtmlUnitDriverBuilderTests { this.driver = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(this.mockMvc).withDelegate(otherDriver).build(); assertMockMvcUsed("http://localhost/test"); - - if (TestGroup.PERFORMANCE.isActive()) { - assertMockMvcNotUsed("https://example.com/"); - } } @Test @@ -86,10 +81,6 @@ class MockMvcHtmlUnitDriverBuilderTests { this.driver = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(this.mockMvc).build(); assertMockMvcUsed("http://localhost/test"); - - if (TestGroup.PERFORMANCE.isActive()) { - assertMockMvcNotUsed("https://example.com/"); - } } @Test diff --git a/spring-web/src/jmh/java/org/springframework/web/bind/ServletRequestUtilsBenchmark.java b/spring-web/src/jmh/java/org/springframework/web/bind/ServletRequestUtilsBenchmark.java new file mode 100644 index 00000000000..f23f000afc4 --- /dev/null +++ b/spring-web/src/jmh/java/org/springframework/web/bind/ServletRequestUtilsBenchmark.java @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.web.bind; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; + +import org.springframework.web.testfixture.servlet.MockHttpServletRequest; + + +/** + * Benchmarks for extracting parameters from {@libnk ServletRequest}. + * @author Brian Clozel + */ +@BenchmarkMode(Mode.Throughput) +public class ServletRequestUtilsBenchmark { + + @State(Scope.Benchmark) + public static class BenchmarkData { + + public MockHttpServletRequest request = new MockHttpServletRequest(); + + public String parameterName = "nonExistingParam"; + } + + @Benchmark + public int intParameterWithDefaultValue(BenchmarkData data) { + return ServletRequestUtils.getIntParameter(data.request, data.parameterName, 0); + } + + @Benchmark + public long longParameterWithDefaultValue(BenchmarkData data) { + return ServletRequestUtils.getLongParameter(data.request, data.parameterName, 0); + } + + @Benchmark + public float floatParameterWithDefaultValue(BenchmarkData data) { + return ServletRequestUtils.getFloatParameter(data.request, data.parameterName, 0f); + } + + @Benchmark + public double doubleParameterWithDefaultValue(BenchmarkData data) { + return ServletRequestUtils.getDoubleParameter(data.request, data.parameterName, 0d); + } + + @Benchmark + public boolean booleanParameterWithDefaultValue(BenchmarkData data) { + return ServletRequestUtils.getBooleanParameter(data.request, data.parameterName, false); + } + + + @Benchmark + public String stringParameterWithDefaultValue(BenchmarkData data) { + return ServletRequestUtils.getStringParameter(data.request, data.parameterName, "defaultValue"); + } + +} diff --git a/spring-web/src/test/java/org/springframework/web/bind/ServletRequestUtilsTests.java b/spring-web/src/test/java/org/springframework/web/bind/ServletRequestUtilsTests.java index dcc4527cf8a..d0f108eb9d7 100644 --- a/spring-web/src/test/java/org/springframework/web/bind/ServletRequestUtilsTests.java +++ b/spring-web/src/test/java/org/springframework/web/bind/ServletRequestUtilsTests.java @@ -18,13 +18,10 @@ package org.springframework.web.bind; import org.junit.jupiter.api.Test; -import org.springframework.core.testfixture.EnabledForTestGroups; -import org.springframework.util.StopWatch; import org.springframework.web.testfixture.servlet.MockHttpServletRequest; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.springframework.core.testfixture.TestGroup.PERFORMANCE; /** * @author Juergen Hoeller @@ -270,82 +267,4 @@ class ServletRequestUtilsTests { assertThat(ServletRequestUtils.getRequiredStringParameter(request, "paramEmpty")).isEmpty(); } - @Test - @EnabledForTestGroups(PERFORMANCE) - void testGetIntParameterWithDefaultValueHandlingIsFastEnough() { - StopWatch sw = new StopWatch(); - sw.start(); - for (int i = 0; i < 1000000; i++) { - ServletRequestUtils.getIntParameter(request, "nonExistingParam", 0); - } - sw.stop(); - System.out.println(sw.getTotalTimeMillis()); - assertThat(sw.getTotalTimeMillis() < 250).as("getStringParameter took too long: " + sw.getTotalTimeMillis()).isTrue(); - } - - @Test - @EnabledForTestGroups(PERFORMANCE) - void testGetLongParameterWithDefaultValueHandlingIsFastEnough() { - StopWatch sw = new StopWatch(); - sw.start(); - for (int i = 0; i < 1000000; i++) { - ServletRequestUtils.getLongParameter(request, "nonExistingParam", 0); - } - sw.stop(); - System.out.println(sw.getTotalTimeMillis()); - assertThat(sw.getTotalTimeMillis() < 250).as("getStringParameter took too long: " + sw.getTotalTimeMillis()).isTrue(); - } - - @Test - @EnabledForTestGroups(PERFORMANCE) - void testGetFloatParameterWithDefaultValueHandlingIsFastEnough() { - StopWatch sw = new StopWatch(); - sw.start(); - for (int i = 0; i < 1000000; i++) { - ServletRequestUtils.getFloatParameter(request, "nonExistingParam", 0f); - } - sw.stop(); - System.out.println(sw.getTotalTimeMillis()); - assertThat(sw.getTotalTimeMillis() < 250).as("getStringParameter took too long: " + sw.getTotalTimeMillis()).isTrue(); - } - - @Test - @EnabledForTestGroups(PERFORMANCE) - void testGetDoubleParameterWithDefaultValueHandlingIsFastEnough() { - StopWatch sw = new StopWatch(); - sw.start(); - for (int i = 0; i < 1000000; i++) { - ServletRequestUtils.getDoubleParameter(request, "nonExistingParam", 0d); - } - sw.stop(); - System.out.println(sw.getTotalTimeMillis()); - assertThat(sw.getTotalTimeMillis() < 250).as("getStringParameter took too long: " + sw.getTotalTimeMillis()).isTrue(); - } - - @Test - @EnabledForTestGroups(PERFORMANCE) - void testGetBooleanParameterWithDefaultValueHandlingIsFastEnough() { - StopWatch sw = new StopWatch(); - sw.start(); - for (int i = 0; i < 1000000; i++) { - ServletRequestUtils.getBooleanParameter(request, "nonExistingParam", false); - } - sw.stop(); - System.out.println(sw.getTotalTimeMillis()); - assertThat(sw.getTotalTimeMillis() < 250).as("getStringParameter took too long: " + sw.getTotalTimeMillis()).isTrue(); - } - - @Test - @EnabledForTestGroups(PERFORMANCE) - void testGetStringParameterWithDefaultValueHandlingIsFastEnough() { - StopWatch sw = new StopWatch(); - sw.start(); - for (int i = 0; i < 1000000; i++) { - ServletRequestUtils.getStringParameter(request, "nonExistingParam", "defaultValue"); - } - sw.stop(); - System.out.println(sw.getTotalTimeMillis()); - assertThat(sw.getTotalTimeMillis() < 250).as("getStringParameter took too long: " + sw.getTotalTimeMillis()).isTrue(); - } - }