# 열거형이 세운 규칙과 상수 /** * The web capabilities that are not part of Stable, each behind its own flag. * *
One flag per capability, not one for "advanced". They have nothing in common operationally: * virtual threads change how every request is scheduled, streaming changes how long a response * holds a connection, XML adds a parser with a decades-long history of entity-expansion attacks. A * single switch would make those one decision, and a deployment that wanted the first would be * given the third. * *
Every constant is off unless named. */ 선언된 상수 수 : 12 24: MVC_VIRTUAL_THREADS, 27: WEBFLUX_BLOCKING_BRIDGE, 30: JSON_MERGE_PATCH, 33: JSON_PATCH, 36: SSE, 39: NDJSON, 42: JSON_SEQUENCE, 45: FUNCTIONAL_WEBFLUX, 48: CBOR, 51: XML, 54: OPENAPI_32, 57: RATELIMIT_DRAFT_HEADERS; 각 상수가 발행하는 속성 이름 : /** The property that turns this on. */ public String propertyName() { return "backend.web.advanced." + name().toLowerCase(Locale.ROOT).replace('_', '-') + ".enabled"; } # main 에 그 접두사가 나오는 자리 전부. 만드는 쪽과 읽는 쪽이 섞여 있다 VirtualThreadProfile.java:76 return "backend.web.advanced.virtual-threads.enabled"; MvcStreamingExecutorConfiguration.java:37 prefix = "backend.web.advanced.ndjson", VirtualThreadSettings.java:10 * Settings bound from {@code backend.web.advanced.mvc-virtual-threads.*}. VirtualThreadSettings.java:17 @ConfigurationProperties(prefix = "backend.web.advanced.mvc-virtual-threads") VirtualThreadMvcConfiguration.java:33 prefix = "backend.web.advanced.mvc-virtual-threads", WebAdvancedFeature.java:61 return "backend.web.advanced." + name().toLowerCase(Locale.ROOT).replace('_', '-') + ".enabled"; 그중 게이트나 바인딩인 셋 : 17:@ConfigurationProperties(prefix = "backend.web.advanced.mvc-virtual-threads") 33: prefix = "backend.web.advanced.mvc-virtual-threads", 34- name = "enabled", 35- havingValue = "true") 36-public class VirtualThreadMvcConfiguration { 37: prefix = "backend.web.advanced.ndjson", 38- name = "enabled", 39- havingValue = "true") 40-public class MvcStreamingExecutorConfiguration { 이름을 만들기만 하는 둘 : public String propertyName() { return "backend.web.advanced." + name().toLowerCase(Locale.ROOT).replace('_', '-') + ".enabled"; /** The property that turns this on. */ public static String propertyName() { return "backend.web.advanced.virtual-threads.enabled"; } # 두 스위치를 켜면 각각 무엇이 생기는가 *
Named {@code applicationTaskExecutor} because that is the bean Spring Boot's MVC async * support looks for. A differently named bean is created, is never used, and leaves the container * default in place — which is the failure mode where the whole capability is switched on and * nothing changes. */ @Bean("applicationTaskExecutor") @ConditionalOnMissingBean(name = "applicationTaskExecutor") 그런데 그 이름을 출하 조립 경로가 이미 쥐고 있다 : 16: * with a bounded pool; naming the bean {@value #EXECUTOR_BEAN_NAME} backs off Boot's auto-config 19:@Configuration 25: public static final String EXECUTOR_BEAN_NAME = "applicationTaskExecutor"; 40: @Bean(name = EXECUTOR_BEAN_NAME) 41: @Primary 42: ThreadPoolTaskExecutor applicationTaskExecutor( 52: executor.setRejectedExecutionHandler(new LoggingAbortPolicy(EXECUTOR_BEAN_NAME, metrics)); 57: EXECUTOR_BEAN_NAME, () -> executor.getThreadPoolExecutor().getQueue().size()); 컴포넌트 스캔 제외 정규식에 bootstrap.async 가 있는가 : 0 @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @ConditionalOnProperty( prefix = "backend.web.advanced.ndjson", name = "enabled", havingValue = "true") public class MvcStreamingExecutorConfiguration { 72: return new MvcStreamWriter(encoder, StreamFraming.NDJSON); 84: return new MvcStreamWriter(encoder, StreamFraming.JSON_SEQUENCE); 그 두 미디어 타입을 내는 main 라우트 : 0 MvcStreamWriter 를 advanced 밖에서 부르는 main 코드 : 0 # 레인이 무엇을 고르고 그 안에서 무엇을 단언하는가 tasks.register('webAdvancedTest', Test) { group = 'verification' description = 'Runs every web Advanced capability contract.' testClassesDirs = sourceSets.test.output.classesDirs classpath = sourceSets.test.runtimeClasspath useJUnitPlatform { includeTags 'web-advanced' } failOnNoDiscoveredTests = true 그 태그를 단 시험 클래스 수 : 22 그 파일에서 속성을 참으로 두는 자리. 셋은 만들어지는 빈을, 둘은 기동 실패를 단언한다 : 43: "backend.web.advanced.mvc-virtual-threads.enabled=true", 72: .withPropertyValues("backend.web.advanced.ndjson.enabled=true") 92: "backend.web.advanced.mvc-virtual-threads.enabled=true", 117: "backend.web.advanced.mvc-virtual-threads.enabled=true", 136: .withPropertyValues("backend.web.advanced.mvc-virtual-threads.enabled=true") 그중 켠 뒤 빈을 확인하는 자리 : .withPropertyValues("backend.web.advanced.ndjson.enabled=true") .withUserConfiguration(MvcStreamingExecutorConfiguration.class) .run( context -> assertThat(context) .hasNotFailed() .hasSingleBean(WebStreamPolicy.class) .hasSingleBean(WebStreamRegistry.class) .hasSingleBean(StreamRecordEncoder.class) .hasBean("mvcNdjsonWriter") .hasBean("mvcJsonSequenceWriter")); # 롤백 시험 일곱이 무엇을 단언하는가 49: void nothingIsCreatedWithNoFlags() { 67: void explicitFalseCreatesNothing() { 86: void flagsReportStablePreservation() { 103: void codecsOffLeaveOnlyJson() { 119: void draftHeadersOffWriteNothing() { 134: void functionalRoutingOffServesNothing() { 146: void everyGateDemandsRollbackEvidence() { 속성 상태를 다루는 둘 — 하나는 값을 두지 않고, 하나는 거짓으로 둔다 : void nothingIsCreatedWithNoFlags() { // No property values at all — the shape a Stable deployment actually has. runner .withUserConfiguration( .withPropertyValues( "backend.web.advanced.ndjson.enabled=false", "backend.web.advanced.mvc-virtual-threads.enabled=false") .withUserConfiguration( 플래그 값을 쓰는 유일한 시험 전문 : void flagsReportStablePreservation() { assertThat( dev.caskeleton.adapter.inbound.web.advanced.WebAdvancedFeatureFlags.none() .stableBehaviourPreserved()) .isTrue(); for (WebAdvancedFeature feature : WebAdvancedFeature.values()) { boolean preserved = dev.caskeleton.adapter.inbound.web.advanced.WebAdvancedFeatureFlags.of(feature) .stableBehaviourPreserved(); assertThat(preserved) .as("%s claims to leave unrelated requests alone", feature) .isEqualTo(!feature.affectsUnrelatedRequests()); } } 44: public boolean stableBehaviourPreserved() { 45- return enabled.stream().noneMatch(WebAdvancedFeature::affectsUnrelatedRequests); 46- } # 롤백 검증이 대상으로 삼는 값 22: public static WebAdvancedFeatureFlags none() { 27: public static WebAdvancedFeatureFlags of(WebAdvancedFeature... features) { 그 타입을 부르는 자리 전부 : test · WebAdvancedReleaseTest.java:21 WebAdvancedFeatureFlags flags = WebAdvancedFeatureFlags.none(); test · WebAdvancedReleaseTest.java:37 assertThat(WebAdvancedFeatureFlags.of(WebAdvancedFeature.CBOR).stableBehaviourPreserved()) test · WebAdvancedReleaseTest.java:40 WebAdvancedFeatureFlags.of(WebAdvancedFeature.MVC_VIRTUAL_THREADS) test · WebAdvancedRollbackIT.java:88 dev.caskeleton.adapter.inbound.web.advanced.WebAdvancedFeatureFlags.none() test · WebAdvancedRollbackIT.java:93 dev.caskeleton.adapter.inbound.web.advanced.WebAdvancedFeatureFlags.of(feature) # 그 값을 돌리는 레인과, 속성 이름을 단언하는 시험 .github/workflows/web-advanced-nightly.yml:46: :adapter:inbound:web:webAdvancedTest .github/workflows/web-advanced-release.yml:53: :adapter:inbound:web:webAdvancedTest test · ResumeTokenTest.java:187 assertThat(WebSocketAdvancedCapability.RESUME.propertyName()) main · WebSocketAdvancedCapability.java:75 public String propertyName() { test · WebAdvancedReleaseTest.java:48 assertThat(WebAdvancedFeature.JSON_MERGE_PATCH.propertyName()) main · VirtualThreadProfile.java:75 public static String propertyName() { main · WebAdvancedFeature.java:60 public String propertyName() {