From eabe946a5385b690228c1141bad3fc131ac7f1b3 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 21 Sep 2021 17:41:56 +0200 Subject: [PATCH 1/4] Skip readStream optimization for compatibility with misbehaving InputStreams Closes gh-27429 --- .../src/main/java/org/springframework/asm/ClassReader.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spring-core/src/main/java/org/springframework/asm/ClassReader.java b/spring-core/src/main/java/org/springframework/asm/ClassReader.java index d53564c2a9..e5c2113993 100644 --- a/spring-core/src/main/java/org/springframework/asm/ClassReader.java +++ b/spring-core/src/main/java/org/springframework/asm/ClassReader.java @@ -324,7 +324,9 @@ public class ClassReader { } outputStream.flush(); if (readCount == 1) { - return data; + // SPRING PATCH: some misbehaving InputStreams return -1 but still write to buffer (gh-27429) + // return data; + // END OF PATCH } return outputStream.toByteArray(); } finally { From 49d003857d1dc98e5f1fa0274e9e5eb6b09a2f78 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 21 Sep 2021 17:42:19 +0200 Subject: [PATCH 2/4] Skip CGLIB class validation in case of optimize flag Closes gh-27439 --- .../springframework/aop/framework/CglibAopProxy.java | 4 ++-- .../org/springframework/aop/framework/ProxyConfig.java | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java index e2b822816d..43d747f90c 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -237,7 +237,7 @@ class CglibAopProxy implements AopProxy, Serializable { * validates it if not. */ private void validateClassIfNecessary(Class proxySuperClass, @Nullable ClassLoader proxyClassLoader) { - if (logger.isWarnEnabled()) { + if (!this.advised.isOptimize() && logger.isInfoEnabled()) { synchronized (validatedClasses) { if (!validatedClasses.containsKey(proxySuperClass)) { doValidateClass(proxySuperClass, proxyClassLoader, diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyConfig.java b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyConfig.java index 38665cafbc..3b6010f8f5 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyConfig.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 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. @@ -73,11 +73,9 @@ public class ProxyConfig implements Serializable { * The exact meaning of "aggressive optimizations" will differ * between proxies, but there is usually some tradeoff. * Default is "false". - *

For example, optimization will usually mean that advice changes won't - * take effect after a proxy has been created. For this reason, optimization - * is disabled by default. An optimize value of "true" may be ignored - * if other settings preclude optimization: for example, if "exposeProxy" - * is set to "true" and that's not compatible with the optimization. + *

With Spring's current proxy options, this flag effectively + * enforces CGLIB proxies (similar to {@link #setProxyTargetClass}) + * but without any class validation checks (for final methods etc). */ public void setOptimize(boolean optimize) { this.optimize = optimize; From 0dc5d2794fa6716f26c228a8cef8aa60a8d4a4ae Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 21 Sep 2021 17:42:50 +0200 Subject: [PATCH 3/4] Avoid early ConversionService determination in StandardBeanExpressionResolver Closes gh-27446 --- .../StandardBeanExpressionResolver.java | 11 +++++----- .../spel/support/StandardTypeConverter.java | 22 ++++++++++++++----- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java b/spring-context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java index c2a0235d19..b58795014b 100644 --- a/spring-context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java +++ b/spring-context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -24,6 +24,7 @@ import org.springframework.beans.factory.BeanExpressionException; import org.springframework.beans.factory.config.BeanExpressionContext; import org.springframework.beans.factory.config.BeanExpressionResolver; import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.ParserContext; @@ -156,10 +157,10 @@ public class StandardBeanExpressionResolver implements BeanExpressionResolver { sec.addPropertyAccessor(new EnvironmentAccessor()); sec.setBeanResolver(new BeanFactoryResolver(evalContext.getBeanFactory())); sec.setTypeLocator(new StandardTypeLocator(evalContext.getBeanFactory().getBeanClassLoader())); - ConversionService conversionService = evalContext.getBeanFactory().getConversionService(); - if (conversionService != null) { - sec.setTypeConverter(new StandardTypeConverter(conversionService)); - } + sec.setTypeConverter(new StandardTypeConverter(() -> { + ConversionService cs = evalContext.getBeanFactory().getConversionService(); + return (cs != null ? cs : DefaultConversionService.getSharedInstance()); + })); customizeEvaluationContext(sec); this.evaluationCache.put(evalContext, sec); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java index 8d1a250288..eeb1c09397 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 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. @@ -16,6 +16,8 @@ package org.springframework.expression.spel.support; +import java.util.function.Supplier; + import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; @@ -37,7 +39,7 @@ import org.springframework.util.Assert; */ public class StandardTypeConverter implements TypeConverter { - private final ConversionService conversionService; + private final Supplier conversionService; /** @@ -45,7 +47,7 @@ public class StandardTypeConverter implements TypeConverter { * @see DefaultConversionService#getSharedInstance() */ public StandardTypeConverter() { - this.conversionService = DefaultConversionService.getSharedInstance(); + this.conversionService = DefaultConversionService::getSharedInstance; } /** @@ -54,20 +56,30 @@ public class StandardTypeConverter implements TypeConverter { */ public StandardTypeConverter(ConversionService conversionService) { Assert.notNull(conversionService, "ConversionService must not be null"); + this.conversionService = () -> conversionService; + } + + /** + * Create a StandardTypeConverter for the given ConversionService. + * @param conversionService a Supplier for the ConversionService to delegate to + * @since 5.3.11 + */ + public StandardTypeConverter(Supplier conversionService) { + Assert.notNull(conversionService, "Supplier must not be null"); this.conversionService = conversionService; } @Override public boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) { - return this.conversionService.canConvert(sourceType, targetType); + return this.conversionService.get().canConvert(sourceType, targetType); } @Override @Nullable public Object convertValue(@Nullable Object value, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) { try { - return this.conversionService.convert(value, sourceType, targetType); + return this.conversionService.get().convert(value, sourceType, targetType); } catch (ConversionException ex) { throw new SpelEvaluationException(ex, SpelMessage.TYPE_CONVERSION_ERROR, From 1f8c233dfcef62781467cbb58a774933208f94cf Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 21 Sep 2021 17:43:03 +0200 Subject: [PATCH 4/4] Polishing --- spring-context/spring-context.gradle | 2 +- ...nClassPostConstructAndAutowiringTests.java | 5 +- .../core/ReactiveAdapterRegistry.java | 72 +++++++++---------- 3 files changed, 38 insertions(+), 41 deletions(-) diff --git a/spring-context/spring-context.gradle b/spring-context/spring-context.gradle index 221e0f12fe..fe3faea9f9 100644 --- a/spring-context/spring-context.gradle +++ b/spring-context/spring-context.gradle @@ -34,8 +34,8 @@ dependencies { testImplementation("org.codehaus.groovy:groovy-test") testImplementation("org.codehaus.groovy:groovy-xml") testImplementation("org.apache.commons:commons-pool2") - testImplementation("javax.inject:javax.inject-tck") testImplementation("org.awaitility:awaitility") + testImplementation("javax.inject:javax.inject-tck") testRuntimeOnly("javax.xml.bind:jaxb-api") testRuntimeOnly("org.glassfish:javax.el") // Substitute for javax.management:jmxremote_optional:1.0.1_04 (not available on Maven Central) diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostConstructAndAutowiringTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostConstructAndAutowiringTests.java index 0aaf4c00a0..420de4c5bd 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostConstructAndAutowiringTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostConstructAndAutowiringTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -25,7 +25,6 @@ import org.springframework.beans.testfixture.beans.TestBean; import static org.assertj.core.api.Assertions.assertThat; - /** * Tests cornering the issue reported in SPR-8080. If the product of a @Bean method * was @Autowired into a configuration class while at the same time the declaring @@ -34,7 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat; * 'currently in creation' status of the autowired bean and result in creating multiple * instances of the given @Bean, violating container scoping / singleton semantics. * - * This is resolved through no longer relying on 'currently in creation' status, but + *

This is resolved through no longer relying on 'currently in creation' status, but * rather on a thread local that informs the enhanced bean method implementation whether * the factory is the caller or not. * diff --git a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java index cfeaffcaf7..cbed55a289 100644 --- a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java +++ b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java @@ -77,11 +77,11 @@ public class ReactiveAdapterRegistry { static { ClassLoader classLoader = ReactiveAdapterRegistry.class.getClassLoader(); reactorPresent = ClassUtils.isPresent("reactor.core.publisher.Flux", classLoader); + flowPublisherPresent = ClassUtils.isPresent("java.util.concurrent.Flow.Publisher", classLoader); rxjava1Present = ClassUtils.isPresent("rx.Observable", classLoader) && ClassUtils.isPresent("rx.RxReactiveStreams", classLoader); rxjava2Present = ClassUtils.isPresent("io.reactivex.Flowable", classLoader); rxjava3Present = ClassUtils.isPresent("io.reactivex.rxjava3.core.Flowable", classLoader); - flowPublisherPresent = ClassUtils.isPresent("java.util.concurrent.Flow.Publisher", classLoader); kotlinCoroutinesPresent = ClassUtils.isPresent("kotlinx.coroutines.reactor.MonoKt", classLoader); mutinyPresent = ClassUtils.isPresent("io.smallrye.mutiny.Multi", classLoader); } @@ -97,13 +97,16 @@ public class ReactiveAdapterRegistry { // Reactor if (reactorPresent) { new ReactorRegistrar().registerAdapters(this); + if (flowPublisherPresent) { + // Java 9+ Flow.Publisher + new ReactorJdkFlowAdapterRegistrar().registerAdapter(this); + } } // RxJava1 (deprecated) if (rxjava1Present) { new RxJava1Registrar().registerAdapters(this); } - // RxJava2 if (rxjava2Present) { new RxJava2Registrar().registerAdapters(this); @@ -113,13 +116,6 @@ public class ReactiveAdapterRegistry { new RxJava3Registrar().registerAdapters(this); } - // Java 9+ Flow.Publisher - if (flowPublisherPresent) { - new ReactorJdkFlowAdapterRegistrar().registerAdapter(this); - } - // If not present, do nothing for the time being... - // We can fall back on "reactive-streams-flow-bridge" (once released) - // Kotlin Coroutines if (reactorPresent && kotlinCoroutinesPresent) { new CoroutinesRegistrar().registerAdapters(this); @@ -253,6 +249,35 @@ public class ReactiveAdapterRegistry { } + private static class ReactorJdkFlowAdapterRegistrar { + + void registerAdapter(ReactiveAdapterRegistry registry) { + // Reflectively access optional JDK 9+ API (for runtime compatibility with JDK 8) + + try { + String publisherName = "java.util.concurrent.Flow.Publisher"; + Class publisherClass = ClassUtils.forName(publisherName, getClass().getClassLoader()); + + String adapterName = "reactor.adapter.JdkFlowAdapter"; + Class flowAdapterClass = ClassUtils.forName(adapterName, getClass().getClassLoader()); + + Method toFluxMethod = flowAdapterClass.getMethod("flowPublisherToFlux", publisherClass); + Method toFlowMethod = flowAdapterClass.getMethod("publisherToFlowPublisher", Publisher.class); + Object emptyFlow = ReflectionUtils.invokeMethod(toFlowMethod, null, Flux.empty()); + + registry.registerReactiveType( + ReactiveTypeDescriptor.multiValue(publisherClass, () -> emptyFlow), + source -> (Publisher) ReflectionUtils.invokeMethod(toFluxMethod, null, source), + publisher -> ReflectionUtils.invokeMethod(toFlowMethod, null, publisher) + ); + } + catch (Throwable ex) { + // Ignore + } + } + } + + private static class RxJava1Registrar { void registerAdapters(ReactiveAdapterRegistry registry) { @@ -307,6 +332,7 @@ public class ReactiveAdapterRegistry { } } + private static class RxJava3Registrar { void registerAdapters(ReactiveAdapterRegistry registry) { @@ -347,34 +373,6 @@ public class ReactiveAdapterRegistry { } } - private static class ReactorJdkFlowAdapterRegistrar { - - void registerAdapter(ReactiveAdapterRegistry registry) { - // TODO: remove reflection when build requires JDK 9+ - - try { - String publisherName = "java.util.concurrent.Flow.Publisher"; - Class publisherClass = ClassUtils.forName(publisherName, getClass().getClassLoader()); - - String adapterName = "reactor.adapter.JdkFlowAdapter"; - Class flowAdapterClass = ClassUtils.forName(adapterName, getClass().getClassLoader()); - - Method toFluxMethod = flowAdapterClass.getMethod("flowPublisherToFlux", publisherClass); - Method toFlowMethod = flowAdapterClass.getMethod("publisherToFlowPublisher", Publisher.class); - Object emptyFlow = ReflectionUtils.invokeMethod(toFlowMethod, null, Flux.empty()); - - registry.registerReactiveType( - ReactiveTypeDescriptor.multiValue(publisherClass, () -> emptyFlow), - source -> (Publisher) ReflectionUtils.invokeMethod(toFluxMethod, null, source), - publisher -> ReflectionUtils.invokeMethod(toFlowMethod, null, publisher) - ); - } - catch (Throwable ex) { - // Ignore - } - } - } - /** * ReactiveAdapter variant that wraps adapted Publishers as {@link Flux} or