diff --git a/spring-context/src/main/java/org/springframework/validation/DataBinder.java b/spring-context/src/main/java/org/springframework/validation/DataBinder.java index 912dad1321d..82eb52ec5fa 100644 --- a/spring-context/src/main/java/org/springframework/validation/DataBinder.java +++ b/spring-context/src/main/java/org/springframework/validation/DataBinder.java @@ -1379,6 +1379,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { /** * Return the names of all property values. + *

Useful for proactive checks whether there are property values nested + * further below the path for a constructor arg. If not then the + * constructor arg can be considered missing and not to be instantiated. * @since 6.1.2 */ Set getNames(); diff --git a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java index 48f7f17803e..2db063ab7da 100644 --- a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java +++ b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java @@ -309,15 +309,19 @@ public class HandlerMethod extends AnnotatedMethod { } /** - * If the provided instance contains a bean name rather than an object instance, - * the bean name is resolved before a {@link HandlerMethod} is created and returned. + * If the {@link #getBean() handler} is a bean name rather than the actual + * handler instance, resolve the bean name through Spring configuration + * (e.g. for prototype beans), and return a new {@link HandlerMethod} + * instance with the resolved handler. + *

If the {@link #getBean() handler} is not String, return the same instance. */ public HandlerMethod createWithResolvedBean() { - Object handler = this.bean; - if (this.bean instanceof String beanName) { - Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory"); - handler = this.beanFactory.getBean(beanName); + if (!(this.bean instanceof String beanName)) { + return this; } + + Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory"); + Object handler = this.beanFactory.getBean(beanName); Assert.notNull(handler, "No handler instance"); return new HandlerMethod(this, handler, false); } diff --git a/spring-web/src/test/java/org/springframework/web/method/HandlerMethodTests.java b/spring-web/src/test/java/org/springframework/web/method/HandlerMethodTests.java index 678b9587f65..660dfa06d6e 100644 --- a/spring-web/src/test/java/org/springframework/web/method/HandlerMethodTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/HandlerMethodTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -72,6 +72,13 @@ class HandlerMethodTests { testValidateReturnValue(target, List.of("getPerson"), false); } + @Test // gh-34277 + void createWithResolvedBeanSameInstance() { + MyClass target = new MyClass(); + HandlerMethod handlerMethod = getHandlerMethod(target, "addPerson"); + assertThat(handlerMethod.createWithResolvedBean()).isSameAs(handlerMethod); + } + private static void testValidateArgs(Object target, List methodNames, boolean expected) { for (String methodName : methodNames) { assertThat(getHandlerMethod(target, methodName).shouldValidateArguments()).isEqualTo(expected); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinder.java index 657a2aa2fec..88b511ee371 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinder.java @@ -125,7 +125,7 @@ public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder { String name = names.nextElement(); Object value = getHeaderValue(httpRequest, name); if (value != null) { - name = StringUtils.uncapitalize(name.replace("-", "")); + name = normalizeHeaderName(name); addValueIfNotPresent(mpvs, "Header", name, value); } } @@ -171,6 +171,10 @@ public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder { return values; } + private static String normalizeHeaderName(String name) { + return StringUtils.uncapitalize(name.replace("-", "")); + } + /** * Resolver of values that looks up URI path variables. @@ -206,8 +210,10 @@ public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder { if (request instanceof HttpServletRequest httpServletRequest) { Enumeration enumeration = httpServletRequest.getHeaderNames(); while (enumeration.hasMoreElements()) { - String headerName = enumeration.nextElement(); - set.add(headerName.replaceAll("-", "")); + String name = enumeration.nextElement(); + if (headerPredicate.test(name)) { + set.add(normalizeHeaderName(name)); + } } } return set; diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinderTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinderTests.java index 02dc5656994..3164fba945f 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinderTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinderTests.java @@ -119,6 +119,23 @@ class ExtendedServletRequestDataBinderTests { assertThat(mpvs).isEmpty(); } + @Test + void headerPredicateWithConstructorArgs() { + ExtendedServletRequestDataBinder binder = new ExtendedServletRequestDataBinder(null); + binder.addHeaderPredicate(name -> !name.equalsIgnoreCase("Some-Int-Array")); + binder.setTargetType(ResolvableType.forClass(DataBean.class)); + binder.setNameResolver(new BindParamNameResolver()); + + request.addHeader("Some-Int-Array", "1"); + request.addHeader("Some-Int-Array", "2"); + + binder.construct(request); + + DataBean bean = (DataBean) binder.getTarget(); + + assertThat(bean.someIntArray()).isNull(); + } + @Test void headerPredicate() { TestBinder binder = new TestBinder(); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketSession.java index 802880f59ec..aa3026857c3 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketSession.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 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. @@ -72,6 +72,12 @@ public interface WebSocketSession extends Closeable { /** * Return the address on which the request was received. + *

Note: The localAddress is not always possible to access, + * which is the case with the Standard WebSocket client. In 6.2.x + * {@link org.springframework.web.socket.client.standard.StandardWebSocketClient} + * returns an address based on the local host and the port of the target + * address (not the same as the local port). In 7.0, the same will return + * {@code null} instead. */ @Nullable InetSocketAddress getLocalAddress();