From 1cc767e90b3068a888aeed17a18daf1d5aa1fdff Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Wed, 22 Jan 2025 11:09:17 +0000 Subject: [PATCH 1/3] Polishing in ExtendedServletRequestDataBinder --- .../springframework/validation/DataBinder.java | 5 ++++- .../ExtendedServletRequestDataBinder.java | 12 +++++++++--- .../ExtendedServletRequestDataBinderTests.java | 17 +++++++++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) 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 274331a89c8..086baefad9f 100644 --- a/spring-context/src/main/java/org/springframework/validation/DataBinder.java +++ b/spring-context/src/main/java/org/springframework/validation/DataBinder.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. @@ -1412,6 +1412,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-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 648be6b30c6..b0acd640aea 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); } } @@ -173,6 +173,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. @@ -209,8 +213,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(); From 9b58df885792c1ad05b22ada77b86f0b7bd479e5 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Thu, 23 Jan 2025 14:02:27 +0000 Subject: [PATCH 2/3] Update HandlerMethod#createWithResolvedBean Avoid re-creating the instance unless it is a bean name that needs to be resolved through the BeanFactory. Closes gh-34277 --- .../web/method/HandlerMethod.java | 18 +++++++++++------- .../web/method/HandlerMethodTests.java | 9 ++++++++- 2 files changed, 19 insertions(+), 8 deletions(-) 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 41a53a5e3aa..0ef3a1bc001 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 @@ -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. @@ -317,15 +317,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); From a6b6d19545cdd94aa46686f187ffe3a530613573 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Thu, 23 Jan 2025 15:11:43 +0000 Subject: [PATCH 3/3] Update localAddress Javadoc in WebSocketSession Closes gh-34304 --- .../org/springframework/web/socket/WebSocketSession.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 00878291d21..7c3943a04e8 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. @@ -73,6 +73,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();