From 03ac8e6b42597d2887cb0f6a23521c3705b793fd Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 30 Oct 2019 06:53:25 +0000 Subject: [PATCH 1/8] Logging decorator for WebSocketStompClient handler Closes gh-23793 --- .../web/socket/messaging/WebSocketStompClient.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/WebSocketStompClient.java b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/WebSocketStompClient.java index dfdcc1830ba4..59507459fd8e 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/WebSocketStompClient.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/WebSocketStompClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -57,6 +57,7 @@ import org.springframework.web.socket.WebSocketMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.client.WebSocketClient; +import org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator; import org.springframework.web.socket.sockjs.transport.SockJsSession; import org.springframework.web.util.UriComponentsBuilder; @@ -265,7 +266,9 @@ public ListenableFuture connect(URI url, @Nullable WebSocketHttpHe Assert.notNull(url, "'url' must not be null"); ConnectionHandlingStompSession session = createSession(connectHeaders, sessionHandler); WebSocketTcpConnectionHandlerAdapter adapter = new WebSocketTcpConnectionHandlerAdapter(session); - getWebSocketClient().doHandshake(adapter, handshakeHeaders, url).addCallback(adapter); + getWebSocketClient() + .doHandshake(new LoggingWebSocketHandlerDecorator(adapter), handshakeHeaders, url) + .addCallback(adapter); return session.getSessionFuture(); } From 3895d21b7ddc5ce378d31ca4befdc83450e16521 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 30 Oct 2019 07:03:51 +0000 Subject: [PATCH 2/8] Fix failing test after previous commit See gh-23793 --- .../socket/messaging/WebSocketStompClientTests.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/messaging/WebSocketStompClientTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/messaging/WebSocketStompClientTests.java index f65ef7ed0c03..4d849bde949c 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/messaging/WebSocketStompClientTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/messaging/WebSocketStompClientTests.java @@ -46,6 +46,7 @@ import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.client.WebSocketClient; +import org.springframework.web.socket.handler.WebSocketHandlerDecorator; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; @@ -319,9 +320,12 @@ private WebSocketHandler connect() { @SuppressWarnings("unchecked") private TcpConnection getTcpConnection() throws Exception { - WebSocketHandler webSocketHandler = connect(); - webSocketHandler.afterConnectionEstablished(this.webSocketSession); - return (TcpConnection) webSocketHandler; + WebSocketHandler handler = connect(); + handler.afterConnectionEstablished(this.webSocketSession); + if (handler instanceof WebSocketHandlerDecorator) { + handler = ((WebSocketHandlerDecorator) handler).getLastHandler(); + } + return (TcpConnection) handler; } private void testInactivityTaskScheduling(Runnable runnable, long delay, long sleepTime) From 34cfbe5d2629245f7140d8d9ed5f71b3583d0141 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 30 Oct 2019 12:10:45 +0000 Subject: [PATCH 3/8] ResponseStatusException associated headers A ResponseStatus exception now exposes extra method to return headers for the response. This is used in ResponseStatusExceptionHandler to apply the headers to the response. Closes gh-23741 --- .../web/server/MethodNotAllowedException.java | 15 ++++++++++- .../server/NotAcceptableStatusException.java | 10 +++++++ .../web/server/ResponseStatusException.java | 17 ++++++++++-- .../ResponseStatusExceptionHandler.java | 25 ++++++++++++------ .../ResponseStatusExceptionHandlerTests.java | 26 +++++++++++++++++++ 5 files changed, 82 insertions(+), 11 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/server/MethodNotAllowedException.java b/spring-web/src/main/java/org/springframework/web/server/MethodNotAllowedException.java index 4c10051fcbf6..adee5ff24bd1 100644 --- a/spring-web/src/main/java/org/springframework/web/server/MethodNotAllowedException.java +++ b/spring-web/src/main/java/org/springframework/web/server/MethodNotAllowedException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -19,12 +19,14 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.Map; import java.util.Set; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Exception for errors that fit response status 405 (method not allowed). @@ -55,6 +57,16 @@ public MethodNotAllowedException(String method, @Nullable Collection } + /** + * Return a Map with an "Allow" header. + * @since 5.1.11 + */ + @Override + public Map getHeaders() { + return Collections.singletonMap("Allow", + StringUtils.collectionToDelimitedString(this.supportedMethods, ", ")); + } + /** * Return the HTTP method for the failed request. */ @@ -68,4 +80,5 @@ public String getHttpMethod() { public Set getSupportedMethods() { return this.supportedMethods; } + } diff --git a/spring-web/src/main/java/org/springframework/web/server/NotAcceptableStatusException.java b/spring-web/src/main/java/org/springframework/web/server/NotAcceptableStatusException.java index a93651f97220..67d1f47b9215 100644 --- a/spring-web/src/main/java/org/springframework/web/server/NotAcceptableStatusException.java +++ b/spring-web/src/main/java/org/springframework/web/server/NotAcceptableStatusException.java @@ -18,6 +18,7 @@ import java.util.Collections; import java.util.List; +import java.util.Map; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -51,6 +52,15 @@ public NotAcceptableStatusException(List supportedMediaTypes) { } + /** + * Return a Map with an "Accept" header. + * @since 5.1.11 + */ + @Override + public Map getHeaders() { + return Collections.singletonMap("Accept", MediaType.toString(this.supportedMediaTypes)); + } + /** * Return the list of supported content types in cases when the Accept * header is parsed but not supported, or an empty list otherwise. diff --git a/spring-web/src/main/java/org/springframework/web/server/ResponseStatusException.java b/spring-web/src/main/java/org/springframework/web/server/ResponseStatusException.java index af5e14c60b12..a0245686a081 100644 --- a/spring-web/src/main/java/org/springframework/web/server/ResponseStatusException.java +++ b/spring-web/src/main/java/org/springframework/web/server/ResponseStatusException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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,9 @@ package org.springframework.web.server; +import java.util.Collections; +import java.util.Map; + import org.springframework.core.NestedExceptionUtils; import org.springframework.core.NestedRuntimeException; import org.springframework.http.HttpStatus; @@ -72,12 +75,21 @@ public ResponseStatusException(HttpStatus status, @Nullable String reason, @Null /** - * The HTTP status that fits the exception (never {@code null}). + * Return the HTTP status associated with this exception. */ public HttpStatus getStatus() { return this.status; } + /** + * Return response headers associated with the exception, possibly required + * for the given status code (e.g. "Allow", "Accept"). + * @since 5.1.11 + */ + public Map getHeaders() { + return Collections.emptyMap(); + } + /** * The reason explaining the exception (potentially {@code null} or empty). */ @@ -86,6 +98,7 @@ public String getReason() { return this.reason; } + @Override public String getMessage() { String msg = this.status + (this.reason != null ? " \"" + this.reason + "\"" : ""); diff --git a/spring-web/src/main/java/org/springframework/web/server/handler/ResponseStatusExceptionHandler.java b/spring-web/src/main/java/org/springframework/web/server/handler/ResponseStatusExceptionHandler.java index f8388669cb9c..263e9cf1afe0 100644 --- a/spring-web/src/main/java/org/springframework/web/server/handler/ResponseStatusExceptionHandler.java +++ b/spring-web/src/main/java/org/springframework/web/server/handler/ResponseStatusExceptionHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -22,6 +22,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.lang.Nullable; import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ServerWebExchange; @@ -62,8 +63,7 @@ public void setWarnLogCategory(String loggerName) { @Override public Mono handle(ServerWebExchange exchange, Throwable ex) { - HttpStatus status = resolveStatus(ex); - if (status == null || !exchange.getResponse().setStatusCode(status)) { + if (!updateResponse(exchange.getResponse(), ex)) { return Mono.error(ex); } @@ -86,16 +86,25 @@ private String formatError(Throwable ex, ServerHttpRequest request) { return "Resolved [" + reason + "] for HTTP " + request.getMethod() + " " + path; } - @Nullable - private HttpStatus resolveStatus(Throwable ex) { + private boolean updateResponse(ServerHttpResponse response, Throwable ex) { + boolean result = false; HttpStatus status = determineStatus(ex); - if (status == null) { + if (status != null) { + if (response.setStatusCode(status)) { + if (ex instanceof ResponseStatusException) { + ((ResponseStatusException) ex).getHeaders() + .forEach((name, value) -> response.getHeaders().add(name, value)); + } + result = true; + } + } + else { Throwable cause = ex.getCause(); if (cause != null) { - status = resolveStatus(cause); + result = updateResponse(response, cause); } } - return status; + return result; } /** diff --git a/spring-web/src/test/java/org/springframework/web/server/handler/ResponseStatusExceptionHandlerTests.java b/spring-web/src/test/java/org/springframework/web/server/handler/ResponseStatusExceptionHandlerTests.java index e760e0cfd986..1048dbb226c9 100644 --- a/spring-web/src/test/java/org/springframework/web/server/handler/ResponseStatusExceptionHandlerTests.java +++ b/spring-web/src/test/java/org/springframework/web/server/handler/ResponseStatusExceptionHandlerTests.java @@ -17,15 +17,21 @@ package org.springframework.web.server.handler; import java.time.Duration; +import java.util.Arrays; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; +import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse; import org.springframework.mock.web.test.server.MockServerWebExchange; +import org.springframework.web.server.MethodNotAllowedException; +import org.springframework.web.server.NotAcceptableStatusException; import org.springframework.web.server.ResponseStatusException; import static org.assertj.core.api.Assertions.assertThat; @@ -67,6 +73,26 @@ public void handleNestedResponseStatusException() { assertThat(this.exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); } + @Test // gh-23741 + public void handleMethodNotAllowed() { + Throwable ex = new MethodNotAllowedException(HttpMethod.PATCH, Arrays.asList(HttpMethod.POST, HttpMethod.PUT)); + this.handler.handle(this.exchange, ex).block(Duration.ofSeconds(5)); + + MockServerHttpResponse response = this.exchange.getResponse(); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.METHOD_NOT_ALLOWED); + assertThat(response.getHeaders().getAllow()).containsOnly(HttpMethod.POST, HttpMethod.PUT); + } + + @Test // gh-23741 + public void handleResponseStatusExceptionWithHeaders() { + Throwable ex = new NotAcceptableStatusException(Arrays.asList(MediaType.TEXT_PLAIN, MediaType.TEXT_HTML)); + this.handler.handle(this.exchange, ex).block(Duration.ofSeconds(5)); + + MockServerHttpResponse response = this.exchange.getResponse(); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_ACCEPTABLE); + assertThat(response.getHeaders().getAccept()).containsOnly(MediaType.TEXT_PLAIN, MediaType.TEXT_HTML); + } + @Test public void unresolvedException() { Throwable expected = new IllegalStateException(); From 422c26832bd82d5ce94b76dc60ff12b5b17a70e6 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 30 Oct 2019 12:38:26 +0000 Subject: [PATCH 4/8] Minor follow-up to previous commit See gh-23741 --- .../web/server/MethodNotAllowedException.java | 12 +++++++----- .../web/server/NotAcceptableStatusException.java | 5 ++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/server/MethodNotAllowedException.java b/spring-web/src/main/java/org/springframework/web/server/MethodNotAllowedException.java index adee5ff24bd1..14dcd2a62b8f 100644 --- a/spring-web/src/main/java/org/springframework/web/server/MethodNotAllowedException.java +++ b/spring-web/src/main/java/org/springframework/web/server/MethodNotAllowedException.java @@ -26,6 +26,7 @@ import org.springframework.http.HttpStatus; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** @@ -39,7 +40,7 @@ public class MethodNotAllowedException extends ResponseStatusException { private final String method; - private final Set supportedMethods; + private final Set httpMethods; public MethodNotAllowedException(HttpMethod method, Collection supportedMethods) { @@ -53,7 +54,7 @@ public MethodNotAllowedException(String method, @Nullable Collection supportedMethods = Collections.emptySet(); } this.method = method; - this.supportedMethods = Collections.unmodifiableSet(new HashSet<>(supportedMethods)); + this.httpMethods = Collections.unmodifiableSet(new HashSet<>(supportedMethods)); } @@ -63,8 +64,9 @@ public MethodNotAllowedException(String method, @Nullable Collection */ @Override public Map getHeaders() { - return Collections.singletonMap("Allow", - StringUtils.collectionToDelimitedString(this.supportedMethods, ", ")); + return !CollectionUtils.isEmpty(this.httpMethods) ? + Collections.singletonMap("Allow", StringUtils.collectionToDelimitedString(this.httpMethods, ", ")) : + Collections.emptyMap(); } /** @@ -78,7 +80,7 @@ public String getHttpMethod() { * Return the list of supported HTTP methods. */ public Set getSupportedMethods() { - return this.supportedMethods; + return this.httpMethods; } } diff --git a/spring-web/src/main/java/org/springframework/web/server/NotAcceptableStatusException.java b/spring-web/src/main/java/org/springframework/web/server/NotAcceptableStatusException.java index 67d1f47b9215..aa8a01d46a68 100644 --- a/spring-web/src/main/java/org/springframework/web/server/NotAcceptableStatusException.java +++ b/spring-web/src/main/java/org/springframework/web/server/NotAcceptableStatusException.java @@ -22,6 +22,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.util.CollectionUtils; /** * Exception for errors that fit response status 406 (not acceptable). @@ -58,7 +59,9 @@ public NotAcceptableStatusException(List supportedMediaTypes) { */ @Override public Map getHeaders() { - return Collections.singletonMap("Accept", MediaType.toString(this.supportedMediaTypes)); + return !CollectionUtils.isEmpty(this.supportedMediaTypes) ? + Collections.singletonMap("Accept", MediaType.toString(this.supportedMediaTypes)) : + Collections.emptyMap(); } /** From dba7bf7ec1d3629a4f9ae0458b9c62176c94b139 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 30 Oct 2019 14:30:26 +0100 Subject: [PATCH 5/8] Avoid unnecessary setAccessible call in AttributeMethods Closes gh-23829 --- .../core/annotation/AttributeMethods.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java b/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java index 13f0d64e035a..f5c06ff051e2 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java @@ -25,6 +25,7 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ConcurrentReferenceHashMap; +import org.springframework.util.ReflectionUtils; /** * Provides a quick way to access the attribute methods of an {@link Annotation} @@ -73,15 +74,11 @@ private AttributeMethods(@Nullable Class annotationType, M if (method.getDefaultValue() != null) { foundDefaultValueMethod = true; } - if (type.isAnnotation() || - (type.isArray() && type.getComponentType().isAnnotation())) { + if (type.isAnnotation() || (type.isArray() && type.getComponentType().isAnnotation())) { foundNestedAnnotation = true; } - method.setAccessible(true); - this.canThrowTypeNotPresentException[i] = - type == Class.class || - type == Class[].class || - type.isEnum(); + ReflectionUtils.makeAccessible(method); + this.canThrowTypeNotPresentException[i] = (type == Class.class || type == Class[].class || type.isEnum()); } this.hasDefaultValueMethod = foundDefaultValueMethod; this.hasNestedAnnotation = foundNestedAnnotation; From 42e7ade110745c0f3eaa34f9d171fc484170a4fe Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 30 Oct 2019 14:31:09 +0100 Subject: [PATCH 6/8] Tolerate unidirectional alias declaration for annotation attribute pair Closes gh-23834 --- .../annotation/AnnotationTypeMapping.java | 26 ++-- .../AnnotatedElementUtilsTests.java | 8 +- .../AnnotationTypeMappingsTests.java | 117 ++++++++---------- .../core/annotation/AnnotationUtilsTests.java | 34 +---- .../annotation/MergedAnnotationsTests.java | 51 +------- 5 files changed, 69 insertions(+), 167 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java index e2478a7eb947..b439926e2d73 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java @@ -27,7 +27,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.function.BiFunction; @@ -179,30 +178,25 @@ private Method resolveAliasTarget(Method attribute, AliasFor aliasFor, boolean c } if (isAliasPair(target) && checkAliasPair) { AliasFor targetAliasFor = target.getAnnotation(AliasFor.class); - if (targetAliasFor == null) { - throw new AnnotationConfigurationException(String.format( - "%s must be declared as an @AliasFor '%s'.", - StringUtils.capitalize(AttributeMethods.describe(target)), - attribute.getName())); - } - Method mirror = resolveAliasTarget(target, targetAliasFor, false); - if (!mirror.equals(attribute)) { - throw new AnnotationConfigurationException(String.format( - "%s must be declared as an @AliasFor '%s', not '%s'.", - StringUtils.capitalize(AttributeMethods.describe(target)), - attribute.getName(), mirror.getName())); + if (targetAliasFor != null) { + Method mirror = resolveAliasTarget(target, targetAliasFor, false); + if (!mirror.equals(attribute)) { + throw new AnnotationConfigurationException(String.format( + "%s must be declared as an @AliasFor '%s', not '%s'.", + StringUtils.capitalize(AttributeMethods.describe(target)), + attribute.getName(), mirror.getName())); + } } } return target; } private boolean isAliasPair(Method target) { - return target.getDeclaringClass().equals(this.annotationType); + return (this.annotationType == target.getDeclaringClass()); } private boolean isCompatibleReturnType(Class attributeType, Class targetType) { - return Objects.equals(attributeType, targetType) || - Objects.equals(attributeType, targetType.getComponentType()); + return (attributeType == targetType || attributeType == targetType.getComponentType()); } private void processAliases() { diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java index 4c0c108bbe2d..650078d10591 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java @@ -1200,7 +1200,7 @@ static class MetaCycleAnnotatedClass { @AliasFor("basePackages") String[] value() default {}; - @AliasFor("value") + // Intentionally no alias declaration for "value" String[] basePackages() default {}; Filter[] excludeFilters() default {}; @@ -1485,7 +1485,7 @@ class ForAnnotationsClass { } @Retention(RetentionPolicy.RUNTIME) - static @interface ValueAttribute { + @interface ValueAttribute { String[] value(); @@ -1493,7 +1493,7 @@ class ForAnnotationsClass { @Retention(RetentionPolicy.RUNTIME) @ValueAttribute("FromValueAttributeMeta") - static @interface ValueAttributeMeta { + @interface ValueAttributeMeta { @AliasFor("alias") String[] value() default {}; @@ -1505,7 +1505,7 @@ class ForAnnotationsClass { @Retention(RetentionPolicy.RUNTIME) @ValueAttributeMeta("FromValueAttributeMetaMeta") - static @interface ValueAttributeMetaMeta { + @interface ValueAttributeMetaMeta { } @ValueAttributeMetaMeta diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationTypeMappingsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationTypeMappingsTests.java index 6e70e6c7f304..fd3f12b7cc15 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationTypeMappingsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationTypeMappingsTests.java @@ -158,15 +158,6 @@ void forAnnotationTypeWhenAliasForWithIncompatibleReturnTypes() { + "] must declare the same return type."); } - @Test - void forAnnotationTypeWhenAliasForToSelfNonAnnotatedAttribute() { - assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() -> - AnnotationTypeMappings.forAnnotationType(AliasForToSelfNonAnnotatedAttribute.class)) - .withMessage("Attribute 'other' in annotation [" - + AliasForToSelfNonAnnotatedAttribute.class.getName() - + "] must be declared as an @AliasFor 'test'."); - } - @Test void forAnnotationTypeWhenAliasForToSelfAnnotatedToOtherAttribute() { assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() -> @@ -554,67 +545,67 @@ private Object extractFromMap(Method attribute, Object map) { } @Retention(RetentionPolicy.RUNTIME) - static @interface SimpleAnnotation { + @interface SimpleAnnotation { } @Retention(RetentionPolicy.RUNTIME) @Inherited @UsesSunMisc - static @interface WithSpringLangAnnotation { + @interface WithSpringLangAnnotation { } @Retention(RetentionPolicy.RUNTIME) @A @B - static @interface MetaAnnotated { + @interface MetaAnnotated { } @Retention(RetentionPolicy.RUNTIME) @AA @AB - static @interface A { + @interface A { } @Retention(RetentionPolicy.RUNTIME) - static @interface AA { + @interface AA { } @Retention(RetentionPolicy.RUNTIME) @ABC - static @interface AB { + @interface AB { } @Retention(RetentionPolicy.RUNTIME) - static @interface ABC { + @interface ABC { } @Retention(RetentionPolicy.RUNTIME) - static @interface B { + @interface B { } @Retention(RetentionPolicy.RUNTIME) @Repeating @Repeating - static @interface WithRepeatedMetaAnnotations { + @interface WithRepeatedMetaAnnotations { } @Retention(RetentionPolicy.RUNTIME) @Repeatable(Repeatings.class) - static @interface Repeating { + @interface Repeating { } @Retention(RetentionPolicy.RUNTIME) - static @interface Repeatings { + @interface Repeatings { Repeating[] value(); @@ -622,24 +613,24 @@ private Object extractFromMap(Method attribute, Object map) { @Retention(RetentionPolicy.RUNTIME) @SelfAnnotated - static @interface SelfAnnotated { + @interface SelfAnnotated { } @Retention(RetentionPolicy.RUNTIME) @LoopB - static @interface LoopA { + @interface LoopA { } @Retention(RetentionPolicy.RUNTIME) @LoopA - static @interface LoopB { + @interface LoopB { } @Retention(RetentionPolicy.RUNTIME) - static @interface AliasForWithBothValueAndAttribute { + @interface AliasForWithBothValueAndAttribute { @AliasFor(value = "bar", attribute = "foo") String test(); @@ -647,7 +638,7 @@ private Object extractFromMap(Method attribute, Object map) { } @Retention(RetentionPolicy.RUNTIME) - static @interface AliasForToSelfNonExistingAttribute { + @interface AliasForToSelfNonExistingAttribute { @AliasFor("missing") String test() default ""; @@ -658,7 +649,7 @@ private Object extractFromMap(Method attribute, Object map) { @Retention(RetentionPolicy.RUNTIME) @AliasForToOtherNonExistingAttributeTarget - static @interface AliasForToOtherNonExistingAttribute { + @interface AliasForToOtherNonExistingAttribute { @AliasFor(annotation = AliasForToOtherNonExistingAttributeTarget.class, attribute = "missing") String test() default ""; @@ -666,14 +657,14 @@ private Object extractFromMap(Method attribute, Object map) { } @Retention(RetentionPolicy.RUNTIME) - static @interface AliasForToOtherNonExistingAttributeTarget { + @interface AliasForToOtherNonExistingAttributeTarget { String other() default ""; } @Retention(RetentionPolicy.RUNTIME) - static @interface AliasForToSelf { + @interface AliasForToSelf { @AliasFor("test") String test() default ""; @@ -682,7 +673,7 @@ private Object extractFromMap(Method attribute, Object map) { @Retention(RetentionPolicy.RUNTIME) @AliasForWithArrayCompatibleReturnTypesTarget - static @interface AliasForWithArrayCompatibleReturnTypes { + @interface AliasForWithArrayCompatibleReturnTypes { @AliasFor(annotation = AliasForWithArrayCompatibleReturnTypesTarget.class) String test() default ""; @@ -690,14 +681,14 @@ private Object extractFromMap(Method attribute, Object map) { } @Retention(RetentionPolicy.RUNTIME) - static @interface AliasForWithArrayCompatibleReturnTypesTarget { + @interface AliasForWithArrayCompatibleReturnTypesTarget { String[] test() default {}; } @Retention(RetentionPolicy.RUNTIME) - static @interface AliasForWithIncompatibleReturnTypes { + @interface AliasForWithIncompatibleReturnTypes { @AliasFor(annotation = AliasForWithIncompatibleReturnTypesTarget.class) String[] test() default {}; @@ -705,24 +696,14 @@ private Object extractFromMap(Method attribute, Object map) { } @Retention(RetentionPolicy.RUNTIME) - static @interface AliasForWithIncompatibleReturnTypesTarget { - - String test() default ""; - - } - - @Retention(RetentionPolicy.RUNTIME) - static @interface AliasForToSelfNonAnnotatedAttribute { + @interface AliasForWithIncompatibleReturnTypesTarget { - @AliasFor("other") String test() default ""; - String other() default ""; - } @Retention(RetentionPolicy.RUNTIME) - static @interface AliasForToSelfAnnotatedToOtherAttribute { + @interface AliasForToSelfAnnotatedToOtherAttribute { @AliasFor("b") String a() default ""; @@ -736,7 +717,7 @@ private Object extractFromMap(Method attribute, Object map) { } @Retention(RetentionPolicy.RUNTIME) - static @interface AliasForNonMetaAnnotated { + @interface AliasForNonMetaAnnotated { @AliasFor(annotation = AliasForNonMetaAnnotatedTarget.class) String test() default ""; @@ -744,14 +725,14 @@ private Object extractFromMap(Method attribute, Object map) { } @Retention(RetentionPolicy.RUNTIME) - static @interface AliasForNonMetaAnnotatedTarget { + @interface AliasForNonMetaAnnotatedTarget { String test() default ""; } @Retention(RetentionPolicy.RUNTIME) - static @interface AliasForSelfWithDifferentDefaults { + @interface AliasForSelfWithDifferentDefaults { @AliasFor("b") String a() default "a"; @@ -762,7 +743,7 @@ private Object extractFromMap(Method attribute, Object map) { } @Retention(RetentionPolicy.RUNTIME) - static @interface AliasForSelfWithMissingDefault { + @interface AliasForSelfWithMissingDefault { @AliasFor("b") String a() default "a"; @@ -774,7 +755,7 @@ private Object extractFromMap(Method attribute, Object map) { @Retention(RetentionPolicy.RUNTIME) @AliasWithExplicitMirrorAndDifferentDefaultsTarget - static @interface AliasWithExplicitMirrorAndDifferentDefaults { + @interface AliasWithExplicitMirrorAndDifferentDefaults { @AliasFor(annotation = AliasWithExplicitMirrorAndDifferentDefaultsTarget.class, attribute = "a") String a() default "x"; @@ -788,7 +769,7 @@ private Object extractFromMap(Method attribute, Object map) { } @Retention(RetentionPolicy.RUNTIME) - static @interface AliasWithExplicitMirrorAndDifferentDefaultsTarget { + @interface AliasWithExplicitMirrorAndDifferentDefaultsTarget { String a() default ""; @@ -796,7 +777,7 @@ private Object extractFromMap(Method attribute, Object map) { @Retention(RetentionPolicy.RUNTIME) @MappedTarget - static @interface Mapped { + @interface Mapped { String convention() default ""; @@ -806,7 +787,7 @@ private Object extractFromMap(Method attribute, Object map) { } @Retention(RetentionPolicy.RUNTIME) - static @interface MappedTarget { + @interface MappedTarget { String convention() default ""; @@ -815,7 +796,7 @@ private Object extractFromMap(Method attribute, Object map) { } @Retention(RetentionPolicy.RUNTIME) - static @interface AliasPair { + @interface AliasPair { @AliasFor("b") String a() default ""; @@ -827,7 +808,7 @@ private Object extractFromMap(Method attribute, Object map) { @Retention(RetentionPolicy.RUNTIME) @ImplicitMirrorsTarget - static @interface ImplicitMirrors { + @interface ImplicitMirrors { @AliasFor(annotation = ImplicitMirrorsTarget.class, attribute = "c") String a() default ""; @@ -838,7 +819,7 @@ private Object extractFromMap(Method attribute, Object map) { } @Retention(RetentionPolicy.RUNTIME) - static @interface ImplicitMirrorsTarget { + @interface ImplicitMirrorsTarget { @AliasFor("d") String c() default ""; @@ -850,7 +831,7 @@ private Object extractFromMap(Method attribute, Object map) { @Retention(RetentionPolicy.RUNTIME) @ThreeDeepB - static @interface ThreeDeepA { + @interface ThreeDeepA { @AliasFor(annotation = ThreeDeepB.class, attribute = "b1") String a1() default ""; @@ -871,7 +852,7 @@ private Object extractFromMap(Method attribute, Object map) { @Retention(RetentionPolicy.RUNTIME) @ThreeDeepC - static @interface ThreeDeepB { + @interface ThreeDeepB { @AliasFor(annotation = ThreeDeepC.class, attribute = "c1") String b1() default ""; @@ -882,7 +863,7 @@ private Object extractFromMap(Method attribute, Object map) { } @Retention(RetentionPolicy.RUNTIME) - static @interface ThreeDeepC { + @interface ThreeDeepC { String c1() default ""; @@ -892,7 +873,7 @@ private Object extractFromMap(Method attribute, Object map) { @Retention(RetentionPolicy.RUNTIME) @DefinedAttributesTarget(a = "test") - static @interface DefinedAttributes { + @interface DefinedAttributes { @AliasFor(annotation = DefinedAttributesTarget.class, attribute = "b") String value(); @@ -900,7 +881,7 @@ private Object extractFromMap(Method attribute, Object map) { } @Retention(RetentionPolicy.RUNTIME) - static @interface DefinedAttributesTarget { + @interface DefinedAttributesTarget { String a(); @@ -935,7 +916,7 @@ static class WithDefaultValueAliasPair { @Retention(RetentionPolicy.RUNTIME) @MulipleRoutesToAliasB - static @interface MulipleRoutesToAliasA { + @interface MulipleRoutesToAliasA { @AliasFor(annotation = MulipleRoutesToAliasB.class, attribute = "b2") String a1() default ""; @@ -944,7 +925,7 @@ static class WithDefaultValueAliasPair { @Retention(RetentionPolicy.RUNTIME) @MulipleRoutesToAliasC - static @interface MulipleRoutesToAliasB { + @interface MulipleRoutesToAliasB { @AliasFor(annotation = MulipleRoutesToAliasC.class, attribute = "c2") String b1() default ""; @@ -958,7 +939,7 @@ static class WithDefaultValueAliasPair { } @Retention(RetentionPolicy.RUNTIME) - static @interface MulipleRoutesToAliasC { + @interface MulipleRoutesToAliasC { @AliasFor("c2") String c1() default ""; @@ -970,14 +951,14 @@ static class WithDefaultValueAliasPair { @Retention(RetentionPolicy.RUNTIME) @ConventionToExplicitAliasesTarget - static @interface ConventionToExplicitAliases { + @interface ConventionToExplicitAliases { String test() default ""; } @Retention(RetentionPolicy.RUNTIME) - static @interface ConventionToExplicitAliasesTarget { + @interface ConventionToExplicitAliasesTarget { @AliasFor("test") String value() default ""; @@ -988,28 +969,28 @@ static class WithDefaultValueAliasPair { } @Retention(RetentionPolicy.RUNTIME) - static @interface ClassValue { + @interface ClassValue { Class value(); } @Retention(RetentionPolicy.RUNTIME) - static @interface ClassValueWithDefault { + @interface ClassValueWithDefault { Class value() default InputStream.class; } @Retention(RetentionPolicy.RUNTIME) - static @interface ClassArrayValueWithDefault { + @interface ClassArrayValueWithDefault { Class[] value() default { InputStream.class, OutputStream.class }; } @Retention(RetentionPolicy.RUNTIME) - static @interface NestedValue { + @interface NestedValue { ClassValue value() default @ClassValue(InputStream.class); diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java index 461b300a38b7..f0edf9c47400 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java @@ -556,16 +556,6 @@ void getRepeatableAnnotationsDeclaredOnMethod() throws Exception { assertThat(values).isEqualTo(asList("A", "B", "C", "meta1")); } - @Test - void getRepeatableAnnotationsDeclaredOnClassWithMissingAttributeAliasDeclaration() throws Exception { - assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() -> - getRepeatableAnnotations(BrokenConfigHierarchyTestCase.class, BrokenContextConfig.class, BrokenHierarchy.class)) - .withMessageStartingWith("Attribute 'value' in") - .withMessageContaining(BrokenContextConfig.class.getName()) - .withMessageContaining("@AliasFor 'location'"); - - } - @Test void getRepeatableAnnotationsDeclaredOnClassWithAttributeAliases() { final List expectedLocations = asList("A", "B"); @@ -1415,17 +1405,6 @@ public void handleMappedWithDifferentPathAndValueAttributes() { Class klass() default Object.class; } - @Retention(RetentionPolicy.RUNTIME) - @interface BrokenContextConfig { - - // Intentionally missing: - // @AliasFor("location") - String value() default ""; - - @AliasFor("value") - String location() default ""; - } - /** * Mock of {@code org.springframework.test.context.ContextHierarchy}. */ @@ -1434,19 +1413,10 @@ public void handleMappedWithDifferentPathAndValueAttributes() { ContextConfig[] value(); } - @Retention(RetentionPolicy.RUNTIME) - @interface BrokenHierarchy { - BrokenContextConfig[] value(); - } - @Hierarchy({@ContextConfig("A"), @ContextConfig(location = "B")}) static class ConfigHierarchyTestCase { } - @BrokenHierarchy(@BrokenContextConfig) - static class BrokenConfigHierarchyTestCase { - } - @ContextConfig("simple.xml") static class SimpleConfigTestCase { } @@ -1825,13 +1795,13 @@ interface ContextConfigMismatch { @Retention(RetentionPolicy.RUNTIME) @Repeatable(TestRepeatableContainer.class) - static @interface TestRepeatable { + @interface TestRepeatable { String value(); } @Retention(RetentionPolicy.RUNTIME) - static @interface TestRepeatableContainer { + @interface TestRepeatableContainer { TestRepeatable[] value(); } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java index 5b6e77ed8af7..02fe39f0f33f 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java @@ -1308,18 +1308,6 @@ void getRepeatableDeclaredOnMethod() throws Exception { assertThat(values).containsExactly("A", "B", "C", "meta1"); } - @Test - void getRepeatableDeclaredOnClassWithMissingAttributeAliasDeclaration() { - RepeatableContainers containers = RepeatableContainers.of( - BrokenContextConfiguration.class, BrokenHierarchy.class); - assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() -> - MergedAnnotations.from(BrokenHierarchyClass.class, SearchStrategy.TYPE_HIERARCHY, containers, - AnnotationFilter.PLAIN).get(BrokenHierarchy.class)) - .withMessageStartingWith("Attribute 'value' in") - .withMessageContaining(BrokenContextConfiguration.class.getName()) - .withMessageContaining("@AliasFor 'location'"); - } - @Test void getRepeatableDeclaredOnClassWithAttributeAliases() { assertThat(MergedAnnotations.from(HierarchyClass.class).stream( @@ -1329,8 +1317,7 @@ void getRepeatableDeclaredOnClassWithAttributeAliases() { MergedAnnotations annotations = MergedAnnotations.from(HierarchyClass.class, SearchStrategy.DIRECT, containers, AnnotationFilter.NONE); assertThat(annotations.stream(TestConfiguration.class).map( - annotation -> annotation.getString("location"))).containsExactly("A", - "B"); + annotation -> annotation.getString("location"))).containsExactly("A", "B"); assertThat(annotations.stream(TestConfiguration.class).map( annotation -> annotation.getString("value"))).containsExactly("A", "B"); } @@ -1488,17 +1475,6 @@ void synthesizeWhenAttributeAliasForNonexistentAttribute() throws Exception { .withMessageContaining("declares an alias for 'bar' which is not present"); } - @Test - void synthesizeWhenAttributeAliasWithoutMirroredAliasFor() throws Exception { - AliasForWithoutMirroredAliasFor annotation = AliasForWithoutMirroredAliasForClass.class.getAnnotation( - AliasForWithoutMirroredAliasFor.class); - assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() -> - MergedAnnotation.from(annotation)) - .withMessageStartingWith("Attribute 'bar' in") - .withMessageContaining(AliasForWithoutMirroredAliasFor.class.getName()) - .withMessageContaining("@AliasFor 'foo'"); - } - @Test void synthesizeWhenAttributeAliasWithMirroredAliasForWrongAttribute() throws Exception { @@ -3055,15 +3031,6 @@ public void handleMappedWithDifferentPathAndValueAttributes() { } } - @Retention(RetentionPolicy.RUNTIME) - @interface BrokenContextConfiguration { - - String value() default ""; - - @AliasFor("value") - String location() default ""; - } - @Retention(RetentionPolicy.RUNTIME) @interface TestConfiguration { @@ -3082,20 +3049,10 @@ public void handleMappedWithDifferentPathAndValueAttributes() { TestConfiguration[] value(); } - @Retention(RetentionPolicy.RUNTIME) - @interface BrokenHierarchy { - - BrokenContextConfiguration[] value(); - } - @Hierarchy({ @TestConfiguration("A"), @TestConfiguration(location = "B") }) static class HierarchyClass { } - @BrokenHierarchy(@BrokenContextConfiguration) - static class BrokenHierarchyClass { - } - @TestConfiguration("simple.xml") static class TestConfigurationClass { } @@ -3544,7 +3501,7 @@ static class DefaultOverrideExplicitAliasRootMetaMetaClass { } @Retention(RetentionPolicy.RUNTIME) - static @interface ValueAttribute { + @interface ValueAttribute { String[] value(); @@ -3552,7 +3509,7 @@ static class DefaultOverrideExplicitAliasRootMetaMetaClass { @Retention(RetentionPolicy.RUNTIME) @ValueAttribute("FromValueAttributeMeta") - static @interface ValueAttributeMeta { + @interface ValueAttributeMeta { String[] value() default {}; @@ -3560,7 +3517,7 @@ static class DefaultOverrideExplicitAliasRootMetaMetaClass { @Retention(RetentionPolicy.RUNTIME) @ValueAttributeMeta("FromValueAttributeMetaMeta") - static @interface ValueAttributeMetaMeta { + @interface ValueAttributeMetaMeta { } From 19107649d23f370b30ef7e3d99528fe814addb94 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 30 Oct 2019 14:31:34 +0100 Subject: [PATCH 7/8] Revise concurrent JAXBContext creation towards computeIfAbsent Closes gh-23879 --- .../http/codec/xml/Jaxb2XmlDecoder.java | 2 +- .../http/codec/xml/Jaxb2XmlEncoder.java | 2 +- .../http/codec/xml/JaxbContextContainer.java | 27 ++++++++++--------- .../AbstractJaxb2HttpMessageConverter.java | 15 ++++------- 4 files changed, 22 insertions(+), 24 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/codec/xml/Jaxb2XmlDecoder.java b/spring-web/src/main/java/org/springframework/http/codec/xml/Jaxb2XmlDecoder.java index 36d2319f0334..4e341a9d0ed0 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/xml/Jaxb2XmlDecoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/xml/Jaxb2XmlDecoder.java @@ -221,7 +221,7 @@ private Object unmarshal(List events, Class outputClass) { } } - private Unmarshaller initUnmarshaller(Class outputClass) throws JAXBException { + private Unmarshaller initUnmarshaller(Class outputClass) throws CodecException, JAXBException { Unmarshaller unmarshaller = this.jaxbContexts.createUnmarshaller(outputClass); return this.unmarshallerProcessor.apply(unmarshaller); } diff --git a/spring-web/src/main/java/org/springframework/http/codec/xml/Jaxb2XmlEncoder.java b/spring-web/src/main/java/org/springframework/http/codec/xml/Jaxb2XmlEncoder.java index c48869e6b9a5..150ad691f2e4 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/xml/Jaxb2XmlEncoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/xml/Jaxb2XmlEncoder.java @@ -140,7 +140,7 @@ public DataBuffer encodeValue(Object value, DataBufferFactory bufferFactory, } } - private Marshaller initMarshaller(Class clazz) throws JAXBException { + private Marshaller initMarshaller(Class clazz) throws CodecException, JAXBException { Marshaller marshaller = this.jaxbContexts.createMarshaller(clazz); marshaller.setProperty(Marshaller.JAXB_ENCODING, StandardCharsets.UTF_8.name()); marshaller = this.marshallerProcessor.apply(marshaller); diff --git a/spring-web/src/main/java/org/springframework/http/codec/xml/JaxbContextContainer.java b/spring-web/src/main/java/org/springframework/http/codec/xml/JaxbContextContainer.java index 2c205c1ddebc..49441c498c38 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/xml/JaxbContextContainer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/xml/JaxbContextContainer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2019 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,12 +24,13 @@ import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; -import org.springframework.util.Assert; +import org.springframework.core.codec.CodecException; /** * Holder for {@link JAXBContext} instances. * * @author Arjen Poutsma + * @author Juergen Hoeller * @since 5.0 */ final class JaxbContextContainer { @@ -37,24 +38,26 @@ final class JaxbContextContainer { private final ConcurrentMap, JAXBContext> jaxbContexts = new ConcurrentHashMap<>(64); - public Marshaller createMarshaller(Class clazz) throws JAXBException { + public Marshaller createMarshaller(Class clazz) throws CodecException, JAXBException { JAXBContext jaxbContext = getJaxbContext(clazz); return jaxbContext.createMarshaller(); } - public Unmarshaller createUnmarshaller(Class clazz) throws JAXBException { + public Unmarshaller createUnmarshaller(Class clazz) throws CodecException, JAXBException { JAXBContext jaxbContext = getJaxbContext(clazz); return jaxbContext.createUnmarshaller(); } - private JAXBContext getJaxbContext(Class clazz) throws JAXBException { - Assert.notNull(clazz, "Class must not be null"); - JAXBContext jaxbContext = this.jaxbContexts.get(clazz); - if (jaxbContext == null) { - jaxbContext = JAXBContext.newInstance(clazz); - this.jaxbContexts.putIfAbsent(clazz, jaxbContext); - } - return jaxbContext; + private JAXBContext getJaxbContext(Class clazz) throws CodecException { + return this.jaxbContexts.computeIfAbsent(clazz, key -> { + try { + return JAXBContext.newInstance(clazz); + } + catch (JAXBException ex) { + throw new CodecException( + "Could not create JAXBContext for class [" + clazz + "]: " + ex.getMessage(), ex); + } + }); } } diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/AbstractJaxb2HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/AbstractJaxb2HttpMessageConverter.java index 913839b001df..811536c31657 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/xml/AbstractJaxb2HttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/xml/AbstractJaxb2HttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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 javax.xml.bind.Unmarshaller; import org.springframework.http.converter.HttpMessageConversionException; -import org.springframework.util.Assert; /** * Abstract base class for {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverters} @@ -106,19 +105,15 @@ protected void customizeUnmarshaller(Unmarshaller unmarshaller) { * @throws HttpMessageConversionException in case of JAXB errors */ protected final JAXBContext getJaxbContext(Class clazz) { - Assert.notNull(clazz, "Class must not be null"); - JAXBContext jaxbContext = this.jaxbContexts.get(clazz); - if (jaxbContext == null) { + return this.jaxbContexts.computeIfAbsent(clazz, key -> { try { - jaxbContext = JAXBContext.newInstance(clazz); - this.jaxbContexts.putIfAbsent(clazz, jaxbContext); + return JAXBContext.newInstance(clazz); } catch (JAXBException ex) { throw new HttpMessageConversionException( - "Could not instantiate JAXBContext for class [" + clazz + "]: " + ex.getMessage(), ex); + "Could not create JAXBContext for class [" + clazz + "]: " + ex.getMessage(), ex); } - } - return jaxbContext; + }); } } From 19ff7d84ab2763f86d5761af6238755e35532970 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 30 Oct 2019 14:31:46 +0100 Subject: [PATCH 8/8] Polishing --- .../context/annotation/DeferredImportSelector.java | 12 +++++++----- .../core/annotation/RepeatableContainers.java | 3 +-- ...SynthesizedMergedAnnotationInvocationHandler.java | 3 +-- .../java/org/springframework/util/unit/DataUnit.java | 4 +--- .../messaging/simp/user/MultiServerUserRegistry.java | 1 - .../java/org/springframework/http/MediaType.java | 8 ++++---- 6 files changed, 14 insertions(+), 17 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/annotation/DeferredImportSelector.java b/spring-context/src/main/java/org/springframework/context/annotation/DeferredImportSelector.java index 1dd9e5b511fc..6bca9156781c 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/DeferredImportSelector.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/DeferredImportSelector.java @@ -16,8 +16,6 @@ package org.springframework.context.annotation; -import java.util.Objects; - import org.springframework.core.type.AnnotationMetadata; import org.springframework.lang.Nullable; @@ -108,13 +106,17 @@ public boolean equals(@Nullable Object other) { return false; } Entry entry = (Entry) other; - return (Objects.equals(this.metadata, entry.metadata) && - Objects.equals(this.importClassName, entry.importClassName)); + return (this.metadata.equals(entry.metadata) && this.importClassName.equals(entry.importClassName)); } @Override public int hashCode() { - return Objects.hash(this.metadata, this.importClassName); + return (this.metadata.hashCode() * 31 + this.importClassName.hashCode()); + } + + @Override + public String toString() { + return this.importClassName; } } } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java b/spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java index 01cb98c3facc..a235a581b072 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java @@ -20,7 +20,6 @@ import java.lang.annotation.Repeatable; import java.lang.reflect.Method; import java.util.Map; -import java.util.Objects; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -83,7 +82,7 @@ public boolean equals(@Nullable Object other) { if (other == null || getClass() != other.getClass()) { return false; } - return Objects.equals(this.parent, ((RepeatableContainers) other).parent); + return ObjectUtils.nullSafeEquals(this.parent, ((RepeatableContainers) other).parent); } @Override diff --git a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java index 89eab8386f9c..fe1131bf16c4 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java @@ -23,7 +23,6 @@ import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.NoSuchElementException; -import java.util.Objects; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -90,7 +89,7 @@ public Object invoke(Object proxy, Method method, Object[] args) { } private boolean isAnnotationTypeMethod(Method method) { - return (Objects.equals(method.getName(), "annotationType") && method.getParameterCount() == 0); + return (method.getName().equals("annotationType") && method.getParameterCount() == 0); } /** diff --git a/spring-core/src/main/java/org/springframework/util/unit/DataUnit.java b/spring-core/src/main/java/org/springframework/util/unit/DataUnit.java index 3ade6a289b08..8b5dc4354d85 100644 --- a/spring-core/src/main/java/org/springframework/util/unit/DataUnit.java +++ b/spring-core/src/main/java/org/springframework/util/unit/DataUnit.java @@ -16,8 +16,6 @@ package org.springframework.util.unit; -import java.util.Objects; - /** * A standard set of {@link DataSize} units. * @@ -92,7 +90,7 @@ DataSize size() { */ public static DataUnit fromSuffix(String suffix) { for (DataUnit candidate : values()) { - if (Objects.equals(candidate.suffix, suffix)) { + if (candidate.suffix.equals(suffix)) { return candidate; } } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/MultiServerUserRegistry.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/MultiServerUserRegistry.java index 02c624913895..d33e8548b0f4 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/MultiServerUserRegistry.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/MultiServerUserRegistry.java @@ -560,7 +560,6 @@ public Map findSessions(String userName) { } return map; } - } } diff --git a/spring-web/src/main/java/org/springframework/http/MediaType.java b/spring-web/src/main/java/org/springframework/http/MediaType.java index dbface9e5451..f44b3148220d 100644 --- a/spring-web/src/main/java/org/springframework/http/MediaType.java +++ b/spring-web/src/main/java/org/springframework/http/MediaType.java @@ -109,7 +109,7 @@ public class MediaType extends MimeType implements Serializable { /** * Public constant media type for {@code application/json;charset=UTF-8}. - * @deprecated Deprecated as of Spring Framework 5.2 in favor of {@link #APPLICATION_JSON} + * @deprecated as of 5.2 in favor of {@link #APPLICATION_JSON} * since major browsers like Chrome * * now comply with the specification and interpret correctly UTF-8 special @@ -120,7 +120,7 @@ public class MediaType extends MimeType implements Serializable { /** * A String equivalent of {@link MediaType#APPLICATION_JSON_UTF8}. - * @deprecated Deprecated as of Spring Framework 5.2 in favor of {@link #APPLICATION_JSON_VALUE} + * @deprecated as of 5.2 in favor of {@link #APPLICATION_JSON_VALUE} * since major browsers like Chrome * * now comply with the specification and interpret correctly UTF-8 special @@ -170,7 +170,7 @@ public class MediaType extends MimeType implements Serializable { * @since 5.0 * @see * Problem Details for HTTP APIs, 6.1. application/problem+json - * @deprecated Deprecated as of Spring Framework 5.2 in favor of {@link #APPLICATION_PROBLEM_JSON} + * @deprecated as of 5.2 in favor of {@link #APPLICATION_PROBLEM_JSON} * since major browsers like Chrome * * now comply with the specification and interpret correctly UTF-8 special @@ -182,7 +182,7 @@ public class MediaType extends MimeType implements Serializable { /** * A String equivalent of {@link MediaType#APPLICATION_PROBLEM_JSON_UTF8}. * @since 5.0 - * @deprecated Deprecated as of Spring Framework 5.2 in favor of {@link #APPLICATION_PROBLEM_JSON_VALUE} + * @deprecated as of 5.2 in favor of {@link #APPLICATION_PROBLEM_JSON_VALUE} * since major browsers like Chrome * * now comply with the specification and interpret correctly UTF-8 special