From bf488192222d668927df0344c29a203e66af8ec8 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Fri, 28 Jul 2023 14:05:17 +0200 Subject: [PATCH] Implement @ConditionalOnThreading Closes gh-36624 --- .../condition/ConditionalOnThreading.java | 46 ++++++++++ .../condition/OnThreadingCondition.java | 51 +++++++++++ .../boot/autoconfigure/thread/Threading.java | 59 ++++++++++++ .../ConditionalOnThreadingTests.java | 91 +++++++++++++++++++ 4 files changed, 247 insertions(+) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnThreading.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnThreadingCondition.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thread/Threading.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnThreadingTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnThreading.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnThreading.java new file mode 100644 index 0000000000..18da6ceddb --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnThreading.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2023 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.boot.autoconfigure.condition; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.boot.autoconfigure.thread.Threading; +import org.springframework.context.annotation.Conditional; + +/** + * {@link Conditional @Conditional} that matches when the specified threading is active. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Conditional(OnThreadingCondition.class) +public @interface ConditionalOnThreading { + + /** + * The {@link Threading threading} that must be active. + * @return the expected threading + */ + Threading value(); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnThreadingCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnThreadingCondition.java new file mode 100644 index 0000000000..7856a63431 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnThreadingCondition.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2023 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.boot.autoconfigure.condition; + +import java.util.Map; + +import org.springframework.boot.autoconfigure.thread.Threading; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.env.Environment; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * {@link Condition} that checks for a required {@link Threading}. + * + * @author Moritz Halbritter + * @see ConditionalOnThreading + */ +class OnThreadingCondition extends SpringBootCondition { + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + Map attributes = metadata.getAnnotationAttributes(ConditionalOnThreading.class.getName()); + Threading threading = (Threading) attributes.get("value"); + return getMatchOutcome(context.getEnvironment(), threading); + } + + private ConditionOutcome getMatchOutcome(Environment environment, Threading threading) { + String name = threading.name(); + ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnThreading.class); + if (threading.isActive(environment)) { + return ConditionOutcome.match(message.foundExactly(name)); + } + return ConditionOutcome.noMatch(message.didNotFind(name).atAll()); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thread/Threading.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thread/Threading.java new file mode 100644 index 0000000000..1f32fa1170 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thread/Threading.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2023 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.boot.autoconfigure.thread; + +import org.springframework.boot.system.JavaVersion; +import org.springframework.core.env.Environment; + +/** + * Threading of the application. + * + * @author Moritz Halbritter + * @since 3.2.0 + */ +public enum Threading { + + /** + * Platform threads. Active if virtual threads are not active. + */ + PLATFORM { + @Override + public boolean isActive(Environment environment) { + return !VIRTUAL.isActive(environment); + } + }, + /** + * Virtual threads. Active if {@code spring.threads.virtual.enabled} is {@code true} + * and running on Java 21 or later. + */ + VIRTUAL { + @Override + public boolean isActive(Environment environment) { + boolean virtualThreadsEnabled = environment.getProperty("spring.threads.virtual.enabled", boolean.class, + false); + return virtualThreadsEnabled && JavaVersion.getJavaVersion().isEqualOrNewerThan(JavaVersion.TWENTY_ONE); + } + }; + + /** + * Determines whether the threading is active. + * @param environment the environment + * @return whether the threading is active + */ + public abstract boolean isActive(Environment environment); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnThreadingTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnThreadingTests.java new file mode 100644 index 0000000000..a455b22f0f --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnThreadingTests.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012-2023 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.boot.autoconfigure.condition; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; + +import org.springframework.boot.autoconfigure.thread.Threading; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConditionalOnThreading}. + * + * @author Moritz Halbritter + */ +class ConditionalOnThreadingTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(BasicConfiguration.class); + + @Test + @EnabledForJreRange(max = JRE.JAVA_20) + void platformThreadsOnJdkBelow21IfVirtualThreadsPropertyIsEnabled() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true") + .run((context) -> assertThat(context.getBean(ThreadType.class)).isEqualTo(ThreadType.PLATFORM)); + } + + @Test + @EnabledForJreRange(max = JRE.JAVA_20) + void platformThreadsOnJdkBelow21IfVirtualThreadsPropertyIsDisabled() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=false") + .run((context) -> assertThat(context.getBean(ThreadType.class)).isEqualTo(ThreadType.PLATFORM)); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void virtualThreadsOnJdk21IfVirtualThreadsPropertyIsEnabled() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true") + .run((context) -> assertThat(context.getBean(ThreadType.class)).isEqualTo(ThreadType.VIRTUAL)); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void platformThreadsOnJdk21IfVirtualThreadsPropertyIsDisabled() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=false") + .run((context) -> assertThat(context.getBean(ThreadType.class)).isEqualTo(ThreadType.PLATFORM)); + } + + private enum ThreadType { + + PLATFORM, VIRTUAL + + } + + @Configuration(proxyBeanMethods = false) + static class BasicConfiguration { + + @Bean + @ConditionalOnThreading(Threading.VIRTUAL) + ThreadType virtual() { + return ThreadType.VIRTUAL; + } + + @Bean + @ConditionalOnThreading(Threading.PLATFORM) + ThreadType platform() { + return ThreadType.PLATFORM; + } + + } + +}