From a133aae8d6a70b2452288802bd4a26eea693ad6f Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Tue, 9 May 2023 10:59:05 +0200 Subject: [PATCH 1/2] Document @ResponseStatus behavior This commit documents that the ResponseStatus annotation does not override the status set through other means. Closes gh-30305 See gh-18019 --- .../ROOT/pages/web/webmvc/mvc-servlet/viewresolver.adoc | 3 --- .../springframework/web/bind/annotation/ResponseStatus.java | 6 +++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/viewresolver.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/viewresolver.adoc index 96d9ceb063e..b6728feb674 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/viewresolver.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/viewresolver.adoc @@ -86,9 +86,6 @@ name (such as `redirect:/myapp/some/resource`) redirects relative to the current Servlet context, while a name such as `redirect:https://myhost.com/some/arbitrary/path` redirects to an absolute URL. -Note that, if a controller method is annotated with the `@ResponseStatus`, the annotation -value takes precedence over the response status set by `RedirectView`. - [[mvc-redirecting-forward-prefix]] == Forwarding diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/ResponseStatus.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/ResponseStatus.java index f69fef63fc7..129605eec4a 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/ResponseStatus.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/ResponseStatus.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-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. @@ -30,8 +30,8 @@ import org.springframework.http.HttpStatus; * {@link #reason} that should be returned. * *

The status code is applied to the HTTP response when the handler - * method is invoked and overrides status information set by other means, - * like {@code ResponseEntity} or {@code "redirect:"}. + * method is invoked, but does not override status information set by other + * means, such as {@code ResponseEntity} or {@code "redirect:"}. * *

Warning: when using this annotation on an exception * class, or when setting the {@code reason} attribute of this annotation, From c1f6d7197b100268463182830d0d8395ef78ed15 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Fri, 5 May 2023 15:49:40 +0200 Subject: [PATCH 2/2] Add support for InjectionPoint with AOT This commit reviews BeanInstanceSupplier to reuse more code from ConstructorResolver. Previously, the autowired argument resolution was partially duplicated and this commit introduces a new common path via RegisteredBean#resolveAutowiredArgument. Closes gh-30401 --- .../factory/aot/BeanInstanceSupplier.java | 35 ++++----------- .../factory/support/ConstructorResolver.java | 15 ++++--- .../beans/factory/support/RegisteredBean.java | 10 +++++ .../ApplicationContextAotGeneratorTests.java | 12 +++++ .../InjectionPointConfiguration.java | 44 +++++++++++++++++++ 5 files changed, 82 insertions(+), 34 deletions(-) create mode 100644 spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/InjectionPointConfiguration.java diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java index a160d986f89..d408a7b6fdb 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java @@ -16,7 +16,6 @@ package org.springframework.beans.factory.aot; -import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Method; @@ -31,8 +30,6 @@ import org.springframework.beans.BeanInstantiationException; import org.springframework.beans.BeansException; import org.springframework.beans.TypeConverter; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.InjectionPoint; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConstructorArgumentValues; @@ -44,7 +41,6 @@ import org.springframework.beans.factory.support.InstanceSupplier; import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.SimpleInstantiationStrategy; -import org.springframework.core.CollectionFactory; import org.springframework.core.MethodParameter; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -239,7 +235,7 @@ public final class BeanInstanceSupplier extends AutowiredElementResolver impl return resolveArguments(registeredBean, this.lookup.get(registeredBean)); } - private AutowiredArguments resolveArguments(RegisteredBean registeredBean,Executable executable) { + private AutowiredArguments resolveArguments(RegisteredBean registeredBean, Executable executable) { Assert.isInstanceOf(AbstractAutowireCapableBeanFactory.class, registeredBean.getBeanFactory()); String beanName = registeredBean.getBeanName(); Class beanClass = registeredBean.getBeanClass(); @@ -264,8 +260,8 @@ public final class BeanInstanceSupplier extends AutowiredElementResolver impl dependencyDescriptor, shortcut, beanClass); } ValueHolder argumentValue = argumentValues.getIndexedArgumentValue(i, null); - resolved[i - startIndex] = resolveArgument(beanFactory, beanName, - autowiredBeans, parameter, dependencyDescriptor, argumentValue); + resolved[i - startIndex] = resolveArgument(registeredBean,autowiredBeans, + dependencyDescriptor, argumentValue); } registerDependentBeans(beanFactory, beanName, autowiredBeans); return AutowiredArguments.of(resolved); @@ -311,36 +307,21 @@ public final class BeanInstanceSupplier extends AutowiredElementResolver impl } @Nullable - private Object resolveArgument(AbstractAutowireCapableBeanFactory beanFactory, - String beanName, Set autowiredBeans, MethodParameter parameter, + private Object resolveArgument(RegisteredBean registeredBean, Set autowiredBeans, DependencyDescriptor dependencyDescriptor, @Nullable ValueHolder argumentValue) { - TypeConverter typeConverter = beanFactory.getTypeConverter(); - Class parameterType = parameter.getParameterType(); + TypeConverter typeConverter = registeredBean.getBeanFactory().getTypeConverter(); + Class parameterType = dependencyDescriptor.getMethodParameter().getParameterType(); if (argumentValue != null) { return (!argumentValue.isConverted()) ? typeConverter.convertIfNecessary(argumentValue.getValue(), parameterType) : argumentValue.getConvertedValue(); } try { - try { - return beanFactory.resolveDependency(dependencyDescriptor, beanName, autowiredBeans, typeConverter); - } - catch (NoSuchBeanDefinitionException ex) { - if (parameterType.isArray()) { - return Array.newInstance(parameterType.getComponentType(), 0); - } - if (CollectionFactory.isApproximableCollectionType(parameterType)) { - return CollectionFactory.createCollection(parameterType, 0); - } - if (CollectionFactory.isApproximableMapType(parameterType)) { - return CollectionFactory.createMap(parameterType, 0); - } - throw ex; - } + return registeredBean.resolveAutowiredArgument(dependencyDescriptor, typeConverter, autowiredBeans); } catch (BeansException ex) { - throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(parameter), ex); + throw new UnsatisfiedDependencyException(null, registeredBean.getBeanName(), dependencyDescriptor, ex); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java index 8c378959594..878e6c2cb46 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java @@ -788,8 +788,8 @@ class ConstructorResolver { "] - did you specify the correct bean references as arguments?"); } try { - Object autowiredArgument = resolveAutowiredArgument( - methodParam, beanName, autowiredBeanNames, converter, fallback); + Object autowiredArgument = resolveAutowiredArgument(new DependencyDescriptor(methodParam, true), + beanName, autowiredBeanNames, converter, fallback); args.rawArguments[paramIndex] = autowiredArgument; args.arguments[paramIndex] = autowiredArgument; args.preparedArguments[paramIndex] = autowiredArgumentMarker; @@ -831,7 +831,8 @@ class ConstructorResolver { Object argValue = argsToResolve[argIndex]; MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex); if (argValue == autowiredArgumentMarker) { - argValue = resolveAutowiredArgument(methodParam, beanName, null, converter, true); + argValue = resolveAutowiredArgument(new DependencyDescriptor(methodParam, true), + beanName, null, converter, true); } else if (argValue instanceof BeanMetadataElement) { argValue = valueResolver.resolveValueIfNecessary("constructor argument", argValue); @@ -872,20 +873,20 @@ class ConstructorResolver { * Template method for resolving the specified argument which is supposed to be autowired. */ @Nullable - protected Object resolveAutowiredArgument(MethodParameter param, String beanName, + protected Object resolveAutowiredArgument(DependencyDescriptor descriptor, String beanName, @Nullable Set autowiredBeanNames, TypeConverter typeConverter, boolean fallback) { - Class paramType = param.getParameterType(); + Class paramType = descriptor.getMethodParameter().getParameterType(); if (InjectionPoint.class.isAssignableFrom(paramType)) { InjectionPoint injectionPoint = currentInjectionPoint.get(); if (injectionPoint == null) { - throw new IllegalStateException("No current InjectionPoint available for " + param); + throw new IllegalStateException("No current InjectionPoint available for " + descriptor); } return injectionPoint; } try { return this.beanFactory.resolveDependency( - new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter); + descriptor, beanName, autowiredBeanNames, typeConverter); } catch (NoUniqueBeanDefinitionException ex) { throw ex; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RegisteredBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RegisteredBean.java index c8c5487153c..96ab8521767 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/RegisteredBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RegisteredBean.java @@ -17,13 +17,16 @@ package org.springframework.beans.factory.support; import java.lang.reflect.Executable; +import java.util.Set; import java.util.function.BiFunction; import java.util.function.Supplier; +import org.springframework.beans.TypeConverter; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.core.ResolvableType; import org.springframework.core.style.ToStringCreator; import org.springframework.lang.Nullable; @@ -209,6 +212,13 @@ public final class RegisteredBean { .resolveConstructorOrFactoryMethod(getBeanName(), getMergedBeanDefinition()); } + @Nullable + public Object resolveAutowiredArgument(DependencyDescriptor descriptor, TypeConverter typeConverter, + Set autowiredBeans) { + return new ConstructorResolver((AbstractAutowireCapableBeanFactory) getBeanFactory()) + .resolveAutowiredArgument(descriptor, getBeanName(), autowiredBeans, typeConverter, true); + } + @Override public String toString() { diff --git a/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java b/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java index e3b0df91538..01a210cd0d5 100644 --- a/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java +++ b/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java @@ -59,6 +59,7 @@ import org.springframework.context.testfixture.context.annotation.CglibConfigura import org.springframework.context.testfixture.context.annotation.ConfigurableCglibConfiguration; import org.springframework.context.testfixture.context.annotation.GenericTemplateConfiguration; import org.springframework.context.testfixture.context.annotation.InitDestroyComponent; +import org.springframework.context.testfixture.context.annotation.InjectionPointConfiguration; import org.springframework.context.testfixture.context.annotation.LazyAutowiredFieldComponent; import org.springframework.context.testfixture.context.annotation.LazyAutowiredMethodComponent; import org.springframework.context.testfixture.context.annotation.LazyConstructorArgumentComponent; @@ -315,6 +316,17 @@ class ApplicationContextAotGeneratorTests { }); } + @Test + void processAheadOfTimeWithInjectionPoint() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.registerBean(InjectionPointConfiguration.class); + testCompiledResult(applicationContext, (initializer, compiled) -> { + GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); + assertThat(freshApplicationContext.getBean("classToString")) + .isEqualTo(InjectionPointConfiguration.class.getName()); + }); + } + @Nested @CompileWithForkedClassLoader class ConfigurationClassCglibProxy { diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/InjectionPointConfiguration.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/InjectionPointConfiguration.java new file mode 100644 index 00000000000..bb7910b4690 --- /dev/null +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/InjectionPointConfiguration.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-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.context.testfixture.context.annotation; + +import org.springframework.beans.factory.InjectionPoint; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; + +@Configuration(proxyBeanMethods = false) +public class InjectionPointConfiguration { + + @Bean + public String classToString(Class callingClass) { + return callingClass.getName(); + } + + + @Configuration(proxyBeanMethods = false) + public static class BeansConfiguration { + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public Class callingClass(InjectionPoint injectionPoint) { + return injectionPoint.getMember().getDeclaringClass(); + } + } + +}