From 4b01370f54f284f377d2c3dbc725a886fa105e97 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 8 Oct 2021 20:41:18 +0200 Subject: [PATCH 1/2] UriTemplateRequestEntity overrides equals/hashCode Closes gh-27531 --- .../springframework/http/RequestEntity.java | 35 +++++++++++++----- .../http/RequestEntityTests.java | 36 ++++++++++++++++++- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/RequestEntity.java b/spring-web/src/main/java/org/springframework/http/RequestEntity.java index 4c77123332c..6dd27d02db2 100644 --- a/spring-web/src/main/java/org/springframework/http/RequestEntity.java +++ b/spring-web/src/main/java/org/springframework/http/RequestEntity.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. @@ -194,15 +194,15 @@ public class RequestEntity extends HttpEntity { return false; } RequestEntity otherEntity = (RequestEntity) other; - return (ObjectUtils.nullSafeEquals(getMethod(), otherEntity.getMethod()) && - ObjectUtils.nullSafeEquals(getUrl(), otherEntity.getUrl())); + return (ObjectUtils.nullSafeEquals(this.method, otherEntity.method) && + ObjectUtils.nullSafeEquals(this.url, otherEntity.url)); } @Override public int hashCode() { int hashCode = super.hashCode(); hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.method); - hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(getUrl()); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.url); return hashCode; } @@ -544,13 +544,13 @@ public class RequestEntity extends HttpEntity { private final URI uri; @Nullable - String uriTemplate; + private final String uriTemplate; @Nullable - private Object[] uriVarsArray; + private final Object[] uriVarsArray; @Nullable - Map uriVarsMap; + private final Map uriVarsMap; DefaultBodyBuilder(HttpMethod method, URI url) { this.method = method; @@ -661,7 +661,7 @@ public class RequestEntity extends HttpEntity { return buildInternal(body, type); } - private RequestEntity buildInternal(@Nullable T body, @Nullable Type type) { + private RequestEntity buildInternal(@Nullable T body, @Nullable Type type) { if (this.uri != null) { return new RequestEntity<>(body, this.headers, this.method, this.uri, type); } @@ -716,6 +716,25 @@ public class RequestEntity extends HttpEntity { return this.uriVarsMap; } + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + if (!super.equals(other)) { + return false; + } + UriTemplateRequestEntity otherEntity = (UriTemplateRequestEntity) other; + return (ObjectUtils.nullSafeEquals(this.uriTemplate, otherEntity.uriTemplate) && + ObjectUtils.nullSafeEquals(this.uriVarsArray, otherEntity.uriVarsArray) && + ObjectUtils.nullSafeEquals(this.uriVarsMap, otherEntity.uriVarsMap)); + } + + @Override + public int hashCode() { + return (29 * super.hashCode() + ObjectUtils.nullSafeHashCode(this.uriTemplate)); + } + @Override public String toString() { return format(getMethod(), getUriTemplate(), getBody(), getHeaders()); diff --git a/spring-web/src/test/java/org/springframework/http/RequestEntityTests.java b/spring-web/src/test/java/org/springframework/http/RequestEntityTests.java index e165ef8082d..1c979728f9b 100644 --- a/spring-web/src/test/java/org/springframework/http/RequestEntityTests.java +++ b/spring-web/src/test/java/org/springframework/http/RequestEntityTests.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. @@ -20,6 +20,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -175,4 +176,37 @@ class RequestEntityTests { assertThat(entity.getType()).isEqualTo(typeReference.getType()); } + @Test + void equalityWithUrl() { + RequestEntity requestEntity1 = RequestEntity.method(HttpMethod.GET, "http://test.api/path/").build(); + RequestEntity requestEntity2 = RequestEntity.method(HttpMethod.GET, "http://test.api/path/").build(); + RequestEntity requestEntity3 = RequestEntity.method(HttpMethod.GET, "http://test.api/pathX/").build(); + + assertThat(requestEntity1).isEqualTo(requestEntity2); + assertThat(requestEntity2).isEqualTo(requestEntity1); + assertThat(requestEntity1).isNotEqualTo(requestEntity3); + assertThat(requestEntity3).isNotEqualTo(requestEntity2); + assertThat(requestEntity1.hashCode()).isEqualTo(requestEntity2.hashCode()); + assertThat(requestEntity1.hashCode()).isNotEqualTo(requestEntity3.hashCode()); + } + + @Test // gh-27531 + void equalityWithUriTemplate() { + Map vars = Collections.singletonMap("id", "1"); + + RequestEntity requestEntity1 = + RequestEntity.method(HttpMethod.GET, "http://test.api/path/{id}", vars).build(); + RequestEntity requestEntity2 = + RequestEntity.method(HttpMethod.GET, "http://test.api/path/{id}", vars).build(); + RequestEntity requestEntity3 = + RequestEntity.method(HttpMethod.GET, "http://test.api/pathX/{id}", vars).build(); + + assertThat(requestEntity1).isEqualTo(requestEntity2); + assertThat(requestEntity2).isEqualTo(requestEntity1); + assertThat(requestEntity1).isNotEqualTo(requestEntity3); + assertThat(requestEntity3).isNotEqualTo(requestEntity2); + assertThat(requestEntity1.hashCode()).isEqualTo(requestEntity2.hashCode()); + assertThat(requestEntity1.hashCode()).isNotEqualTo(requestEntity3.hashCode()); + } + } From 87aaf5049b81bce2a8bbb37e7aef69128f79a1e5 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 8 Oct 2021 20:41:51 +0200 Subject: [PATCH 2/2] Polishing --- .../org/springframework/core/ReactiveAdapterRegistry.java | 2 +- .../web/bind/MethodArgumentNotValidException.java | 4 ++-- .../result/method/AbstractHandlerMethodMapping.java | 6 ++---- .../mvc/support/DefaultHandlerExceptionResolver.java | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) 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 0193dff6c25..9e56b5d5e37 100644 --- a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java +++ b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java @@ -41,7 +41,7 @@ import org.springframework.util.ReflectionUtils; /** * A registry of adapters to adapt Reactive Streams {@link Publisher} to/from * various async/reactive types such as {@code CompletableFuture}, RxJava - * {@code Observable}, and others. + * {@code Flowable}, and others. * *

By default, depending on classpath availability, adapters are registered * for Reactor, RxJava 3, {@link CompletableFuture}, {@code Flow.Publisher}, diff --git a/spring-web/src/main/java/org/springframework/web/bind/MethodArgumentNotValidException.java b/spring-web/src/main/java/org/springframework/web/bind/MethodArgumentNotValidException.java index e51f136e475..3638ac1411b 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/MethodArgumentNotValidException.java +++ b/spring-web/src/main/java/org/springframework/web/bind/MethodArgumentNotValidException.java @@ -56,8 +56,8 @@ public class MethodArgumentNotValidException extends BindException { @Override public String getMessage() { StringBuilder sb = new StringBuilder("Validation failed for argument [") - .append(this.parameter.getParameterIndex()).append("] in ") - .append(this.parameter.getExecutable().toGenericString()); + .append(this.parameter.getParameterIndex()).append("] in ") + .append(this.parameter.getExecutable().toGenericString()); BindingResult bindingResult = getBindingResult(); if (bindingResult.getErrorCount() > 1) { sb.append(" with ").append(bindingResult.getErrorCount()).append(" errors"); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java index 4487162cc9e..7d59209955d 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java @@ -157,12 +157,10 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap */ @Override public void afterPropertiesSet() { - initHandlerMethods(); - // Total includes detected mappings + explicit registrations via registerMapping.. - int total = this.getHandlerMethods().size(); - + // Total includes detected mappings + explicit registrations via registerMapping + int total = getHandlerMethods().size(); if ((logger.isTraceEnabled() && total == 0) || (logger.isDebugEnabled() && total > 0) ) { logger.debug(total + " mappings in " + formatMappingName()); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java index acb57f66521..47bb3de3256 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java @@ -520,7 +520,7 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes /** * Handle the case where an async request timed out. *

The default implementation sends an HTTP 503 error. - * @param ex the {@link AsyncRequestTimeoutException }to be handled + * @param ex the {@link AsyncRequestTimeoutException} to be handled * @param request current HTTP request * @param response current HTTP response * @param handler the executed handler, or {@code null} if none chosen